mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
@@ -1,5 +1,7 @@
|
|||||||
## 🎉 [v3.3.0] 更新日志
|
## 🎉 [v3.3.0] 更新日志
|
||||||
|
|
||||||
|
本次更新带来了显著的内存优化,全新的脚本扩展功能和主题系统,此外还对 UI 界面进行了全面的重构和优化。
|
||||||
|
|
||||||
### 🚀 性能优化
|
### 🚀 性能优化
|
||||||
|
|
||||||
- 减少了 **60-70%** 的闲置内存占用和 **20%** 的打开窗口时内存占用
|
- 减少了 **60-70%** 的闲置内存占用和 **20%** 的打开窗口时内存占用
|
||||||
@@ -7,9 +9,18 @@
|
|||||||
|
|
||||||
### ✨ 新增功能
|
### ✨ 新增功能
|
||||||
|
|
||||||
|
#### 🛠 脚本扩展
|
||||||
|
|
||||||
|
使用自定义的`javascript`脚本来扩展 PicList 的功能,无需本机安装node环境,更加轻量和灵活。
|
||||||
|
|
||||||
|
- 全新的脚本系统,支持图床上传脚本和多种类型的通用脚本
|
||||||
|
- 支持在上传前后、相册添加/删除前后等多个阶段运行脚本,满足更多自定义需求
|
||||||
|
- 脚本管理页面,支持新建、编辑、删除和启用/禁用脚本
|
||||||
|
|
||||||
#### ⚙️ 核心功能
|
#### ⚙️ 核心功能
|
||||||
|
|
||||||
- 现在支持软件内编辑配置文件并保存
|
- 现在支持软件内编辑配置文件并保存
|
||||||
|
- 高级自定义图床现在支持使用脚本上传
|
||||||
- Windows 便携模式,无需安装运行,数据存储在程序目录下的 `data` 文件夹中,且支持自动更新。
|
- Windows 便携模式,无需安装运行,数据存储在程序目录下的 `data` 文件夹中,且支持自动更新。
|
||||||
- Linux 新增 `rpm` 安装包。
|
- Linux 新增 `rpm` 安装包。
|
||||||
- 新增图床编辑卡片页面,解决多配置切换时的混乱问题。
|
- 新增图床编辑卡片页面,解决多配置切换时的混乱问题。
|
||||||
@@ -28,6 +39,7 @@
|
|||||||
- 文件浏览侧边栏名称超出宽度时支持滚动显示完整名称。
|
- 文件浏览侧边栏名称超出宽度时支持滚动显示完整名称。
|
||||||
- 优化了视频播放页面的显示效果。
|
- 优化了视频播放页面的显示效果。
|
||||||
- 相册页面支持显示已选数量、匹配的 URL 列表和记忆过滤器状态。
|
- 相册页面支持显示已选数量、匹配的 URL 列表和记忆过滤器状态。
|
||||||
|
- 优化了设置页面的布局分布
|
||||||
- 支持浏览完整插件列表、查看详情及一键安装。
|
- 支持浏览完整插件列表、查看详情及一键安装。
|
||||||
- 新增新手引导页面,首次运行自动弹出。
|
- 新增新手引导页面,首次运行自动弹出。
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,45 @@
|
|||||||
## 🎉 [v3.3.0] Release Notes
|
## 🎉 [v3.3.0] Release Notes
|
||||||
|
|
||||||
### 🚀 Performance Optimization
|
This update brings significant memory optimizations, a brand new script extension feature, and a theme system. Additionally, the UI has been comprehensively redesigned and optimized.
|
||||||
|
|
||||||
- Reduced **60-70%** idle memory usage and **20%** memory usage when opening windows.
|
### 🚀 Performance Improvements
|
||||||
- Optimized loading speed and browsing performance for multiple pages
|
|
||||||
|
- Reduced idle memory usage by **60-70%** and memory usage when opening windows by **20%**
|
||||||
|
- Optimized loading speed and browsing performance on multiple pages
|
||||||
|
|
||||||
### ✨ New Features
|
### ✨ New Features
|
||||||
|
|
||||||
|
#### 🛠 Script Extensions
|
||||||
|
|
||||||
|
Use custom `javascript` scripts to extend PicList's functionality without the need to install a local Node environment, making it lighter and more flexible.
|
||||||
|
|
||||||
|
- A brand new script system that supports image hosting upload scripts and various types of general scripts
|
||||||
|
- Supports running scripts at multiple stages such as before and after uploading, before and after adding/removing albums, meeting more customization needs
|
||||||
|
- Script management page that supports creating, editing, deleting, and enabling/disabling scripts
|
||||||
|
|
||||||
#### ⚙️ Core Features
|
#### ⚙️ Core Features
|
||||||
|
|
||||||
- Now supports manually disabling GPU acceleration to resolve black screen or flickering issues caused by some hardware compatibility.
|
- Now supports editing and saving configuration files within the software
|
||||||
- Added advanced animation settings for a better UI interaction experience.
|
- Advanced custom image hosting now supports script uploads
|
||||||
- Windows portable mode, no installation required, data is stored in the `data` folder in the program directory, and supports automatic updates.
|
- Windows portable mode, no installation required, data is stored in the `data` folder in the program directory, and supports automatic updates.
|
||||||
- Added `rpm` installation package for Linux.
|
- Added `rpm` installation package for Linux.
|
||||||
- Added image hosting editing card page to solve confusion when switching multiple configurations.
|
- Added image hosting editing card page to resolve confusion when switching multiple configurations.
|
||||||
- Added list mode support for the file browsing page.
|
- Added list mode support to the file browsing page.
|
||||||
|
- Now supports manually disabling GPU acceleration to resolve black screen or flickering issues caused by some hardware compatibility.
|
||||||
|
- Added advanced animation settings for a better UI interaction experience when enabled.
|
||||||
|
|
||||||
#### 🎨 UI Interface
|
#### 🎨 UI Interface
|
||||||
|
|
||||||
- Refactored almost all business pages and optimized dozens of UI details.
|
- Redesigned almost all business pages and optimized dozens of UI details.
|
||||||
- Integrated theme repository [PicList ThemeHub](https://github.com/Kuingsmile/PicList-ThemeHub), supporting custom downloads.
|
- Integrated theme repository [PicList ThemeHub](https://github.com/Kuingsmile/PicList-ThemeHub), supporting custom downloads.
|
||||||
- Provided 12 built-in themes (such as bilibili, 二次元, 极夜紫 styles).
|
- Provided 12 built-in themes (such as bilibili, 二次元, 极夜紫 styles).
|
||||||
- Redesigned all pages of the management feature
|
- Redesigned all pages of the management function
|
||||||
- Optimized album page card styles, clearer boundaries, and improved selection box visual effects.
|
- Optimized album page card styles, clearer boundaries, improved selection box visual effects.
|
||||||
- Optimized the display of multiple pages on narrow screens to avoid content overflow.
|
- Optimized the display of multiple pages under narrow screens to avoid content overflow.
|
||||||
- Supported scrolling to display the full name when the file browsing sidebar name exceeds the width.
|
- Supported scrolling to display the full name when the file browsing sidebar name exceeds the width.
|
||||||
- The album page supports displaying the number of selected items, matching URL lists, and remembering filter states.
|
- Optimized the display effect of the video playback page.
|
||||||
|
- The album page supports displaying the number of selected items, the list of matching URLs, and remembering the filter state.
|
||||||
|
- Optimized the layout distribution of the settings page.
|
||||||
- Supports browsing the complete plugin list, viewing details, and one-click installation.
|
- Supports browsing the complete plugin list, viewing details, and one-click installation.
|
||||||
- Added a new user guide page that automatically pops up on the first run.
|
- Added a new user guide page that automatically pops up on the first run.
|
||||||
|
|
||||||
@@ -36,9 +50,10 @@
|
|||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
- Fixed the issue of abnormal display of the sorting dropdown box on the management page.
|
- Fixed the issue where the sort dropdown box on the management page displayed abnormally.
|
||||||
- Fixed the issue where the image hosting list on the management page did not correctly highlight the currently selected item.
|
- Fixed the issue where the image hosting list on the management page did not correctly highlight the currently selected item.
|
||||||
- Fixed the display issue of the task page in dark mode.
|
- Fixed the issue where the markdown preview content on the management page did not render correctly.
|
||||||
|
- Fixed the display anomaly on the task page in dark mode.
|
||||||
- Fixed the issue where the "Set as Default Image Hosting" button status on the image hosting settings page was not updated in a timely manner.
|
- Fixed the issue where the "Set as Default Image Hosting" button status on the image hosting settings page was not updated in a timely manner.
|
||||||
- Fixed the issue where the independent watermark setting button status on the image hosting was not synchronized in the preprocessing settings page.
|
- Fixed the issue where the button status of the independent watermark setting for image hosting in the preprocessing settings page was not synchronized.
|
||||||
- Fixed the issue where bottom elements of some pages were obscured.
|
- Fixed the issue where bottom elements on some pages were obscured.
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { configPaths } from '~/utils/configPaths'
|
|||||||
import { IPasteStyle, IWindowList } from '~/utils/enum'
|
import { IPasteStyle, IWindowList } from '~/utils/enum'
|
||||||
import { isMacOSVersionGreaterThanOrEqualTo } from '~/utils/getMacOSVersion'
|
import { isMacOSVersionGreaterThanOrEqualTo } from '~/utils/getMacOSVersion'
|
||||||
import pasteTemplate from '~/utils/pasteTemplate'
|
import pasteTemplate from '~/utils/pasteTemplate'
|
||||||
|
import { runScriptInStage } from '~/utils/runScript'
|
||||||
import { hideMiniWindow, openMainWindow, openMiniWindow } from '~/utils/windowHelper'
|
import { hideMiniWindow, openMainWindow, openMiniWindow } from '~/utils/windowHelper'
|
||||||
|
|
||||||
import menubarPng from '../../../../../resources/menubar.png?asset&asarUnpack'
|
import menubarPng from '../../../../../resources/menubar.png?asset&asarUnpack'
|
||||||
@@ -308,8 +309,8 @@ export function createTray(tooltip: string) {
|
|||||||
const rawInput = cloneDeep(files)
|
const rawInput = cloneDeep(files)
|
||||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||||
const res = await uploader.setWebContents(trayWindow?.webContents).uploadReturnCtx(files)
|
const res = await uploader.setWebContents(trayWindow?.webContents).uploadReturnCtx(files)
|
||||||
const imgs = res[0] ? res[0] : false
|
const imgs = res.ctx?.output ? res.ctx.output : false
|
||||||
const backImgs = res[1] ? res[1] : false
|
const backImgs = res.backupCtx?.output ? res.backupCtx.output : false
|
||||||
const deleteLocalFile = allConfig.settings?.deleteLocalFile || false
|
const deleteLocalFile = allConfig.settings?.deleteLocalFile || false
|
||||||
if (imgs !== false) {
|
if (imgs !== false) {
|
||||||
const pasteText: string[] = []
|
const pasteText: string[] = []
|
||||||
@@ -334,7 +335,8 @@ export function createTray(tooltip: string) {
|
|||||||
notification.show()
|
notification.show()
|
||||||
}, i * 100)
|
}, i * 100)
|
||||||
}
|
}
|
||||||
await GalleryDB.getInstance().insert(imgs[i])
|
const inserted = await GalleryDB.getInstance().insert(imgs[i])
|
||||||
|
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
|
||||||
}
|
}
|
||||||
handleCopyUrl(pasteText.join('\n'))
|
handleCopyUrl(pasteText.join('\n'))
|
||||||
trayWindow?.webContents.send('dragFiles', imgs)
|
trayWindow?.webContents.send('dragFiles', imgs)
|
||||||
|
|||||||
@@ -1,46 +1,56 @@
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
import { themesDir } from '@core/datastore/dirs'
|
import { themesDir } from '@core/datastore/dirs'
|
||||||
import * as fsWalk from '@nodelib/fs.walk'
|
|
||||||
import AdmZip from 'adm-zip'
|
import AdmZip from 'adm-zip'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
|
|
||||||
import { randomStringGenerator } from '@/manage/utils/common'
|
import { randomStringGenerator } from '@/manage/utils/common'
|
||||||
|
import logger from '~/apis/core/picgo/logger'
|
||||||
import { IWindowList } from '~/utils/enum'
|
import { IWindowList } from '~/utils/enum'
|
||||||
|
|
||||||
import windowManager from '../window/windowManager'
|
import windowManager from '../window/windowManager'
|
||||||
|
|
||||||
export async function resolveThemes(): Promise<{ key: string; label: string }[]> {
|
export async function resolveThemes(): Promise<{ key: string; label: string }[]> {
|
||||||
const files = fsWalk.walkSync(themesDir(), {
|
const dir = themesDir()
|
||||||
followSymbolicLinks: true,
|
const entries = await fs.readdir(dir, { withFileTypes: true })
|
||||||
fs,
|
const themePromises = entries
|
||||||
stats: true,
|
.filter(entry => entry.isFile() && entry.name.endsWith('.css'))
|
||||||
throwErrorOnBrokenSymbolicLink: false,
|
.map(async entry => {
|
||||||
})
|
const filePath = path.join(dir, entry.name)
|
||||||
const result: string[] = []
|
|
||||||
files.forEach(item => {
|
let label = entry.name
|
||||||
if (item.stats?.isFile()) {
|
let fd
|
||||||
result.push(path.basename(item.path))
|
try {
|
||||||
}
|
fd = await fs.open(filePath, 'r')
|
||||||
})
|
const buffer = Buffer.alloc(256)
|
||||||
const themes = await Promise.all(
|
const { bytesRead } = await fs.read(fd, buffer, 0, 256, 0)
|
||||||
result
|
const content = buffer.toString('utf8', 0, bytesRead)
|
||||||
.filter(file => file.endsWith('.css'))
|
const match = content.match(/^\/\*\s*(.*?)\s*\*\//)
|
||||||
.map(async file => {
|
if (match && match[1]) {
|
||||||
const css = (await fs.readFile(path.join(themesDir(), file), 'utf-8')) || ''
|
label = match[1].trim()
|
||||||
let name = file
|
|
||||||
if (css.startsWith('/*')) {
|
|
||||||
name = css.split('\n')[0].replace('/*', '').replace('*/', '').trim() || file
|
|
||||||
}
|
}
|
||||||
return { key: file, label: name }
|
} catch (e: any) {
|
||||||
}),
|
logger.error(e)
|
||||||
)
|
} finally {
|
||||||
if (themes.find(theme => theme.key === 'default.css')) {
|
if (fd !== undefined) await fs.close(fd)
|
||||||
return themes
|
}
|
||||||
|
|
||||||
|
return { key: entry.name, label }
|
||||||
|
})
|
||||||
|
|
||||||
|
const themes = await Promise.all(themePromises)
|
||||||
|
|
||||||
|
const hasDefault = themes.some(t => t.key === 'default.css')
|
||||||
|
if (!hasDefault) {
|
||||||
|
themes.unshift({ key: 'default.css', label: '默认' })
|
||||||
} else {
|
} else {
|
||||||
return [{ key: 'default.css', label: '默认' }, ...themes]
|
const idx = themes.findIndex(t => t.key === 'default.css')
|
||||||
|
const [defaultTheme] = themes.splice(idx, 1)
|
||||||
|
themes.unshift(defaultTheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return themes
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchThemes(): Promise<boolean> {
|
export async function fetchThemes(): Promise<boolean> {
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common'
|
|||||||
import { configPaths } from '~/utils/configPaths'
|
import { configPaths } from '~/utils/configPaths'
|
||||||
import { IPasteStyle, IWindowList } from '~/utils/enum'
|
import { IPasteStyle, IWindowList } from '~/utils/enum'
|
||||||
import pasteTemplate from '~/utils/pasteTemplate'
|
import pasteTemplate from '~/utils/pasteTemplate'
|
||||||
|
import { runScriptInStage } from '~/utils/runScript'
|
||||||
|
|
||||||
const handleClipboardUploadingReturnCtx = async (img?: IUploadOption): Promise<(ImgInfo[] | false)[]> => {
|
const handleClipboardUploadingReturnCtx = async (img?: IUploadOption): Promise<IuploadReturnCtxResult> => {
|
||||||
const useBuiltinClipboardConfig = picgo.getConfig<boolean | undefined>(configPaths.settings.useBuiltinClipboard)
|
const useBuiltinClipboardConfig = picgo.getConfig<boolean | undefined>(configPaths.settings.useBuiltinClipboard)
|
||||||
const useBuiltinClipboard = useBuiltinClipboardConfig === undefined ? true : !!useBuiltinClipboardConfig
|
const useBuiltinClipboard = useBuiltinClipboardConfig === undefined ? true : !!useBuiltinClipboardConfig
|
||||||
const win = windowManager.getAvailableWindow()
|
const win = windowManager.getAvailableWindow()
|
||||||
@@ -26,8 +27,8 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
|
|||||||
let img: ImgInfo[] | false = false
|
let img: ImgInfo[] | false = false
|
||||||
let backImg: ImgInfo[] | false = false
|
let backImg: ImgInfo[] | false = false
|
||||||
const res = await handleClipboardUploadingReturnCtx()
|
const res = await handleClipboardUploadingReturnCtx()
|
||||||
img = res[0] ? res[0] : false
|
img = res.ctx?.output ? res.ctx.output : false
|
||||||
backImg = res[1] ? res[1] : false
|
backImg = res.backupCtx?.output ? res.backupCtx.output : false
|
||||||
const allConfig = picgo.getConfig<any>() || {}
|
const allConfig = picgo.getConfig<any>() || {}
|
||||||
if (img !== false) {
|
if (img !== false) {
|
||||||
if (img.length > 0) {
|
if (img.length > 0) {
|
||||||
@@ -50,6 +51,7 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
|
|||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
const inserted = await GalleryDB.getInstance().insert(img[0])
|
const inserted = await GalleryDB.getInstance().insert(img[0])
|
||||||
|
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
|
||||||
// trayWindow just be created in mac/windows, not in linux
|
// trayWindow just be created in mac/windows, not in linux
|
||||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||||
@@ -93,8 +95,8 @@ export const uploadChoosedFiles = async (
|
|||||||
let imgs: ImgInfo[] | false = false
|
let imgs: ImgInfo[] | false = false
|
||||||
let backImgs: ImgInfo[] | false = false
|
let backImgs: ImgInfo[] | false = false
|
||||||
const res = await uploader.setWebContents(webContents).uploadReturnCtx(input)
|
const res = await uploader.setWebContents(webContents).uploadReturnCtx(input)
|
||||||
imgs = res[0] ? res[0] : false
|
imgs = res.ctx?.output ? res.ctx.output : false
|
||||||
backImgs = res[1] ? res[1] : false
|
backImgs = res.backupCtx?.output ? res.backupCtx.output : false
|
||||||
const result = []
|
const result = []
|
||||||
const allConfig = picgo.getConfig<any>() || {}
|
const allConfig = picgo.getConfig<any>() || {}
|
||||||
if (imgs !== false) {
|
if (imgs !== false) {
|
||||||
@@ -140,6 +142,7 @@ export const uploadChoosedFiles = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const inserted = await GalleryDB.getInstance().insert(imgs[i])
|
const inserted = await GalleryDB.getInstance().insert(imgs[i])
|
||||||
|
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
|
||||||
result.push({
|
result.push({
|
||||||
url: handleUrlEncodeWithSetting(inserted.imgUrl!),
|
url: handleUrlEncodeWithSetting(inserted.imgUrl!),
|
||||||
fullResult: inserted,
|
fullResult: inserted,
|
||||||
|
|||||||
@@ -118,15 +118,15 @@ class Uploader {
|
|||||||
return filePath
|
return filePath
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadWithBuildInClipboardReturnCtx(img?: IUploadOption): Promise<(ImgInfo[] | false)[]> {
|
async uploadWithBuildInClipboardReturnCtx(img?: IUploadOption): Promise<IuploadReturnCtxResult> {
|
||||||
let imgPath: string | false = false
|
let imgPath: string | false = false
|
||||||
try {
|
try {
|
||||||
imgPath = await this.getClipboardImagePath()
|
imgPath = await this.getClipboardImagePath()
|
||||||
if (!imgPath) return [false, false]
|
if (!imgPath) return { ctx: undefined, backupCtx: undefined }
|
||||||
return await this.uploadReturnCtx(img ?? [imgPath])
|
return await this.uploadReturnCtx(img ?? [imgPath])
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
return [false, false]
|
return { ctx: undefined, backupCtx: undefined }
|
||||||
} finally {
|
} finally {
|
||||||
if (imgPath && imgPath.startsWith(path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER))) {
|
if (imgPath && imgPath.startsWith(path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER))) {
|
||||||
fs.remove(imgPath)
|
fs.remove(imgPath)
|
||||||
@@ -134,24 +134,24 @@ class Uploader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadReturnCtx(img?: IUploadOption): Promise<(ImgInfo[] | false)[]> {
|
async uploadReturnCtx(img?: IUploadOption): Promise<IuploadReturnCtxResult> {
|
||||||
try {
|
try {
|
||||||
const result = [false, false] as (ImgInfo[] | false)[]
|
const result = { ctx: undefined, backupCtx: undefined } as IuploadReturnCtxResult
|
||||||
const res = await picgo.uploadReturnCtx(img)
|
const res = await picgo.uploadReturnCtx(img)
|
||||||
const allConfig = picgo.getConfig<any>() || {}
|
const allConfig = picgo.getConfig<any>() || {}
|
||||||
|
|
||||||
if (Array.isArray(res.output) && res.output.some((item: ImgInfo) => item.imgUrl)) {
|
if (Array.isArray(res.ctx?.output) && res.ctx?.output.some((item: ImgInfo) => item.imgUrl)) {
|
||||||
res.output.forEach((item: ImgInfo) => {
|
res.ctx.output.forEach((item: ImgInfo) => {
|
||||||
item.config = JSON.parse(JSON.stringify(allConfig.picBed?.[item.type!]))
|
item.config = JSON.parse(JSON.stringify(allConfig.picBed?.[item.type!]))
|
||||||
})
|
})
|
||||||
result[0] = res.output
|
result.ctx = res.ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(res.backupOutput) && res.backupOutput.some((item: ImgInfo) => item.imgUrl)) {
|
if (Array.isArray(res.backupCtx?.output) && res.backupCtx?.output.some((item: ImgInfo) => item.imgUrl)) {
|
||||||
res.backupOutput.forEach((item: ImgInfo) => {
|
res.backupCtx.output.forEach((item: ImgInfo) => {
|
||||||
item.config = JSON.parse(JSON.stringify(allConfig.picBed?.[item.type!]))
|
item.config = JSON.parse(JSON.stringify(allConfig.picBed?.[item.type!]))
|
||||||
})
|
})
|
||||||
result[1] = res.backupOutput
|
result.backupCtx = res.backupCtx
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -163,7 +163,7 @@ class Uploader {
|
|||||||
clickToCopy: true,
|
clickToCopy: true,
|
||||||
})
|
})
|
||||||
}, 500)
|
}, 500)
|
||||||
return [false, false]
|
return { ctx: undefined, backupCtx: undefined } as IuploadReturnCtxResult
|
||||||
} finally {
|
} finally {
|
||||||
ipcMain.removeAllListeners(GET_RENAME_FILE_NAME)
|
ipcMain.removeAllListeners(GET_RENAME_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ export function defaultDir() {
|
|||||||
return userDataDir()
|
return userDataDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function scriptsDir() {
|
||||||
|
return path.join(dataDir(), 'scripts')
|
||||||
|
}
|
||||||
|
|
||||||
export function defaultConfigPath() {
|
export function defaultConfigPath() {
|
||||||
if (isPortable()) {
|
if (isPortable()) {
|
||||||
return path.join(exeDir(), 'data', 'data.json')
|
return path.join(exeDir(), 'data', 'data.json')
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { T as $t } from '~/i18n'
|
|||||||
import { handleCopyUrl } from '~/utils/common'
|
import { handleCopyUrl } from '~/utils/common'
|
||||||
import { IPasteStyle } from '~/utils/enum'
|
import { IPasteStyle } from '~/utils/enum'
|
||||||
import pasteTemplate from '~/utils/pasteTemplate'
|
import pasteTemplate from '~/utils/pasteTemplate'
|
||||||
|
import { runScriptInStage } from '~/utils/runScript'
|
||||||
|
|
||||||
// Cross-process support may be required in the future
|
// Cross-process support may be required in the future
|
||||||
class GuiApi implements IGuiApi {
|
class GuiApi implements IGuiApi {
|
||||||
@@ -74,8 +75,8 @@ class GuiApi implements IGuiApi {
|
|||||||
const webContents = this.getWebcontentsByWindowId(this.windowId)
|
const webContents = this.getWebcontentsByWindowId(this.windowId)
|
||||||
const rawInput = cloneDeep(input)
|
const rawInput = cloneDeep(input)
|
||||||
const res = await uploader.setWebContents(webContents!).uploadReturnCtx(input)
|
const res = await uploader.setWebContents(webContents!).uploadReturnCtx(input)
|
||||||
const imgs = res[0] ? res[0] : false
|
const imgs = res.ctx?.output ? res.ctx.output : false
|
||||||
const backImgs = res[1] ? res[1] : false
|
const backImgs = res.backupCtx?.output ? res.backupCtx.output : false
|
||||||
let result: ImgInfo[] = []
|
let result: ImgInfo[] = []
|
||||||
const allConfig = picgo.getConfig<any>() || {}
|
const allConfig = picgo.getConfig<any>() || {}
|
||||||
if (imgs !== false) {
|
if (imgs !== false) {
|
||||||
@@ -103,7 +104,8 @@ class GuiApi implements IGuiApi {
|
|||||||
notification.show()
|
notification.show()
|
||||||
}, i * 100)
|
}, i * 100)
|
||||||
}
|
}
|
||||||
await GalleryDB.getInstance().insert(imgs[i])
|
const inserted = await GalleryDB.getInstance().insert(imgs[i])
|
||||||
|
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
|
||||||
}
|
}
|
||||||
handleCopyUrl(pasteText.join('\n'))
|
handleCopyUrl(pasteText.join('\n'))
|
||||||
webContents?.send('uploadFiles')
|
webContents?.send('uploadFiles')
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { clipboard } from 'electron'
|
|||||||
import { RPCRouter } from '~/events/rpc/router'
|
import { RPCRouter } from '~/events/rpc/router'
|
||||||
import { ICOREBuildInEvent, IPasteStyle, IRPCActionType, IRPCType } from '~/utils/enum'
|
import { ICOREBuildInEvent, IPasteStyle, IRPCActionType, IRPCType } from '~/utils/enum'
|
||||||
import pasteTemplate from '~/utils/pasteTemplate'
|
import pasteTemplate from '~/utils/pasteTemplate'
|
||||||
|
import { runScriptInStage } from '~/utils/runScript'
|
||||||
interface IFilter {
|
interface IFilter {
|
||||||
orderBy?: 'asc' | 'desc'
|
orderBy?: 'asc' | 'desc'
|
||||||
limit?: number
|
limit?: number
|
||||||
@@ -38,6 +39,7 @@ const galleryRoutes = [
|
|||||||
action: IRPCActionType.GALLERY_REMOVE_FILES,
|
action: IRPCActionType.GALLERY_REMOVE_FILES,
|
||||||
handler: async (_: IIPCEvent, args: [files: ImgInfo[]]) => {
|
handler: async (_: IIPCEvent, args: [files: ImgInfo[]]) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
runScriptInStage('onGalleryRemove', picgo, { galleryItem: args[0] })
|
||||||
picgo.emit(ICOREBuildInEvent.REMOVE, args[0], GuiApi.getInstance())
|
picgo.emit(ICOREBuildInEvent.REMOVE, args[0], GuiApi.getInstance())
|
||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
import { dataDir } from '@core/datastore/dirs'
|
import { dataDir, scriptsDir } from '@core/datastore/dirs'
|
||||||
import picgo from '@core/picgo'
|
import picgo from '@core/picgo'
|
||||||
import { IpcMainEvent, shell } from 'electron'
|
import { IpcMainEvent, shell } from 'electron'
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
|
|
||||||
|
import logger from '~/apis/core/picgo/logger'
|
||||||
import { isAutoStartEnabled, setAutoStart } from '~/utils/autoStart'
|
import { isAutoStartEnabled, setAutoStart } from '~/utils/autoStart'
|
||||||
|
import { getDirectoryTree } from '~/utils/common'
|
||||||
import { IRPCActionType, IRPCType } from '~/utils/enum'
|
import { IRPCActionType, IRPCType } from '~/utils/enum'
|
||||||
|
import { runScript } from '~/utils/runScript'
|
||||||
|
|
||||||
const STORE_PATH = dataDir()
|
const STORE_PATH = dataDir()
|
||||||
|
|
||||||
@@ -57,9 +60,110 @@ export default [
|
|||||||
action: IRPCActionType.WRITE_FILE_CONTENT,
|
action: IRPCActionType.WRITE_FILE_CONTENT,
|
||||||
handler: async (_: IIPCEvent, args: [fileName: string, content: string]) => {
|
handler: async (_: IIPCEvent, args: [fileName: string, content: string]) => {
|
||||||
const abFilePath = path.join(STORE_PATH, args[0])
|
const abFilePath = path.join(STORE_PATH, args[0])
|
||||||
|
fs.ensureDirSync(path.dirname(abFilePath))
|
||||||
fs.writeFileSync(abFilePath, args[1], 'utf-8')
|
fs.writeFileSync(abFilePath, args[1], 'utf-8')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: IRPCActionType.RUN_SCRIPT_FILE,
|
||||||
|
handler: async (_: IIPCEvent, args: [fileName: string[]]) => {
|
||||||
|
const abFilePath = path.join(scriptsDir(), ...args[0])
|
||||||
|
if (!fs.existsSync(abFilePath)) {
|
||||||
|
throw new Error('Script file does not exist')
|
||||||
|
}
|
||||||
|
const scriptContent = fs.readFileSync(abFilePath, 'utf-8')
|
||||||
|
try {
|
||||||
|
await runScript(picgo, scriptContent, {})
|
||||||
|
logger.info(`Script ${args[0].join('/')} executed successfully`)
|
||||||
|
return 'Script executed successfully'
|
||||||
|
} catch (e) {
|
||||||
|
return Error(`Script execution failed: ${e}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: IRPCType.INVOKE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: IRPCActionType.CREATE_SCRIPTS_FILE,
|
||||||
|
handler: async (_: IIPCEvent, args: [fileName: string[], content: string]) => {
|
||||||
|
const abFilePath = path.join(scriptsDir(), ...args[0])
|
||||||
|
fs.ensureDirSync(path.dirname(abFilePath))
|
||||||
|
fs.writeFileSync(abFilePath, args[1], 'utf-8')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: IRPCActionType.READ_SCRIPTS_FILE,
|
||||||
|
handler: async (_: IIPCEvent, args: [fileName: string[]]) => {
|
||||||
|
const abFilePath = path.join(scriptsDir(), ...args[0])
|
||||||
|
if (!fs.existsSync(abFilePath)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return fs.readFileSync(abFilePath, 'utf-8')
|
||||||
|
},
|
||||||
|
type: IRPCType.INVOKE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: IRPCActionType.LIST_SCRIPTS_FILES,
|
||||||
|
handler: async (_: IIPCEvent, args: [dirPath: string[]]) => {
|
||||||
|
const dir = scriptsDir()
|
||||||
|
const targetDir = path.join(dir, ...(args[0] || []))
|
||||||
|
|
||||||
|
if (!(await fs.pathExists(targetDir))) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await getDirectoryTree(targetDir)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to list scripts:', error)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: IRPCType.INVOKE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: IRPCActionType.WRITE_SCRIPT_FILE,
|
||||||
|
handler: async (_: IIPCEvent, args: [fileName: string[], content: string]) => {
|
||||||
|
const abFilePath = path.join(scriptsDir(), ...args[0])
|
||||||
|
fs.ensureDirSync(path.dirname(abFilePath))
|
||||||
|
fs.writeFileSync(abFilePath, args[1], 'utf-8')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: IRPCActionType.DELETE_SCRIPTS_FILE,
|
||||||
|
handler: async (_: IIPCEvent, args: [fileName: string[]]) => {
|
||||||
|
const abFilePath = path.join(scriptsDir(), ...args[0])
|
||||||
|
if (fs.existsSync(abFilePath)) {
|
||||||
|
fs.unlinkSync(abFilePath)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: IRPCActionType.GET_FILES_STAT,
|
||||||
|
handler: async (_: IIPCEvent, [filePaths, type]: [string[][], 'scripts' | 'config']) => {
|
||||||
|
const basePath = type === 'scripts' ? scriptsDir() : STORE_PATH
|
||||||
|
const result: IObj[] = []
|
||||||
|
const statPromises = filePaths.map(async filePath => {
|
||||||
|
const absolutePath = path.join(basePath, ...filePath)
|
||||||
|
try {
|
||||||
|
return await fs.promises.stat(absolutePath)
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const statsResults = await Promise.all(statPromises)
|
||||||
|
|
||||||
|
for (const [index, item] of filePaths.entries()) {
|
||||||
|
result.push({
|
||||||
|
fileName: item[item.length - 1],
|
||||||
|
stats: statsResults[index],
|
||||||
|
category: item.length > 1 ? item.slice(0, -1).join('.') : '',
|
||||||
|
filePath: item,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
type: IRPCType.INVOKE,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
action: IRPCActionType.PICLIST_OPEN_DIRECTORY,
|
action: IRPCActionType.PICLIST_OPEN_DIRECTORY,
|
||||||
handler: async (_: IIPCEvent, args: [dirPath?: string, inStorePath?: boolean]) => {
|
handler: async (_: IIPCEvent, args: [dirPath?: string, inStorePath?: boolean]) => {
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ const trayRoutes = [
|
|||||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||||
// macOS use builtin clipboard is OK
|
// macOS use builtin clipboard is OK
|
||||||
const res = await uploader.setWebContents(trayWindow?.webContents).uploadWithBuildInClipboardReturnCtx()
|
const res = await uploader.setWebContents(trayWindow?.webContents).uploadWithBuildInClipboardReturnCtx()
|
||||||
const img = res[0] ? res[0] : false
|
const img = res.ctx?.output ? res.ctx.output : false
|
||||||
const backupImgs = res[1] ? res[1] : false
|
const backupImgs = res.backupCtx?.output ? res.backupCtx.output : false
|
||||||
const allConfig = picgo.getConfig<any>() || {}
|
const allConfig = picgo.getConfig<any>() || {}
|
||||||
if (img !== false) {
|
if (img !== false) {
|
||||||
const pasteStyle = allConfig.settings?.pasteStyle || IPasteStyle.MARKDOWN
|
const pasteStyle = allConfig.settings?.pasteStyle || IPasteStyle.MARKDOWN
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import { IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '~/utils/enum'
|
|||||||
import { getUploadFiles } from '~/utils/handleArgv'
|
import { getUploadFiles } from '~/utils/handleArgv'
|
||||||
import { initI18n } from '~/utils/handleI18n'
|
import { initI18n } from '~/utils/handleI18n'
|
||||||
import { notificationList } from '~/utils/notification'
|
import { notificationList } from '~/utils/notification'
|
||||||
import { MemoryMonitor } from '~/utils/performanceOptimizer'
|
import { runScriptInStage } from '~/utils/runScript'
|
||||||
import { CLIPBOARD_IMAGE_FOLDER } from '~/utils/static'
|
import { CLIPBOARD_IMAGE_FOLDER } from '~/utils/static'
|
||||||
import updateChecker from '~/utils/updateChecker'
|
import updateChecker from '~/utils/updateChecker'
|
||||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||||
@@ -146,9 +146,6 @@ class LifeCycle {
|
|||||||
notice.show()
|
notice.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isDevelopment) {
|
|
||||||
MemoryMonitor.start()
|
|
||||||
}
|
|
||||||
await remoteNoticeHandler.init()
|
await remoteNoticeHandler.init()
|
||||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
|
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
|
||||||
if (startMode === ISartMode.MINI && process.platform !== 'darwin') {
|
if (startMode === ISartMode.MINI && process.platform !== 'darwin') {
|
||||||
@@ -196,6 +193,7 @@ class LifeCycle {
|
|||||||
}
|
}
|
||||||
const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER)
|
const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER)
|
||||||
fs.emptyDir(clipboardDir)
|
fs.emptyDir(clipboardDir)
|
||||||
|
runScriptInStage('onSoftwareOpen', picgo, {})
|
||||||
}
|
}
|
||||||
app.whenReady().then(readyFunction)
|
app.whenReady().then(readyFunction)
|
||||||
}
|
}
|
||||||
@@ -251,7 +249,7 @@ class LifeCycle {
|
|||||||
server.shutdown()
|
server.shutdown()
|
||||||
webServer.stop()
|
webServer.stop()
|
||||||
stopFileServer()
|
stopFileServer()
|
||||||
MemoryMonitor.stop()
|
runScriptInStage('onSoftwareClose', picgo, {})
|
||||||
})
|
})
|
||||||
// Exit cleanly on request from parent process in development mode.
|
// Exit cleanly on request from parent process in development mode.
|
||||||
if (isDevelopment) {
|
if (isDevelopment) {
|
||||||
|
|||||||
@@ -375,3 +375,23 @@ export function getUploaderType(ctx: IPicGo): {
|
|||||||
const id = picBedConfig._id || ''
|
const id = picBedConfig._id || ''
|
||||||
return { picBed, id }
|
return { picBed, id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getDirectoryTree(currentPath: string): Promise<Record<string, any>> {
|
||||||
|
const result: Record<string, any> = {}
|
||||||
|
|
||||||
|
const entries = await fs.readdir(currentPath, { withFileTypes: true })
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
entries.map(async entry => {
|
||||||
|
const fullPath = path.join(currentPath, entry.name)
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
result[entry.name] = await getDirectoryTree(fullPath)
|
||||||
|
} else if (entry.isFile()) {
|
||||||
|
result[entry.name] = null
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export const IRPCActionType = {
|
|||||||
SET_CURRENT_LANGUAGE: 'SET_CURRENT_LANGUAGE',
|
SET_CURRENT_LANGUAGE: 'SET_CURRENT_LANGUAGE',
|
||||||
OPEN_WINDOW: 'OPEN_WINDOW',
|
OPEN_WINDOW: 'OPEN_WINDOW',
|
||||||
OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW',
|
OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW',
|
||||||
|
RELOAD_WINDOW: 'RELOAD_WINDOW',
|
||||||
CLOSE_WINDOW: 'CLOSE_WINDOW',
|
CLOSE_WINDOW: 'CLOSE_WINDOW',
|
||||||
MINIMIZE_WINDOW: 'MINIMIZE_WINDOW',
|
MINIMIZE_WINDOW: 'MINIMIZE_WINDOW',
|
||||||
SHOW_MINI_PAGE_MENU: 'SHOW_MINI_PAGE_MENU',
|
SHOW_MINI_PAGE_MENU: 'SHOW_MINI_PAGE_MENU',
|
||||||
@@ -123,9 +124,17 @@ export const IRPCActionType = {
|
|||||||
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
|
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
|
||||||
PICLIST_AUTO_START: 'PICLIST_AUTO_START',
|
PICLIST_AUTO_START: 'PICLIST_AUTO_START',
|
||||||
PICLIST_AUTO_START_STATUS: 'PICLIST_AUTO_START_STATUS',
|
PICLIST_AUTO_START_STATUS: 'PICLIST_AUTO_START_STATUS',
|
||||||
|
|
||||||
|
// file operation rpc
|
||||||
READ_FILE_CONTENT: 'READ_FILE_CONTENT',
|
READ_FILE_CONTENT: 'READ_FILE_CONTENT',
|
||||||
WRITE_FILE_CONTENT: 'WRITE_FILE_CONTENT',
|
WRITE_FILE_CONTENT: 'WRITE_FILE_CONTENT',
|
||||||
RELOAD_WINDOW: 'RELOAD_WINDOW',
|
CREATE_SCRIPTS_FILE: 'CREATE_SCRIPTS_FILE',
|
||||||
|
READ_SCRIPTS_FILE: 'READ_SCRIPTS_FILE',
|
||||||
|
LIST_SCRIPTS_FILES: 'LIST_SCRIPTS_FILES',
|
||||||
|
WRITE_SCRIPT_FILE: 'WRITE_SCRIPT_FILE',
|
||||||
|
GET_FILES_STAT: 'GET_FILES_STAT',
|
||||||
|
DELETE_SCRIPTS_FILE: 'DELETE_SCRIPTS_FILE',
|
||||||
|
RUN_SCRIPT_FILE: 'RUN_SCRIPT_FILE',
|
||||||
|
|
||||||
// shortkey setting rpc
|
// shortkey setting rpc
|
||||||
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',
|
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',
|
||||||
|
|||||||
123
src/main/utils/runScript.ts
Normal file
123
src/main/utils/runScript.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import crypto from 'node:crypto'
|
||||||
|
import os from 'node:os'
|
||||||
|
import path from 'node:path'
|
||||||
|
import vm from 'node:vm'
|
||||||
|
|
||||||
|
import { scriptsDir } from '@core/datastore/dirs'
|
||||||
|
import picgo from '@core/picgo'
|
||||||
|
import logger from '@core/picgo/logger'
|
||||||
|
import axios from 'axios'
|
||||||
|
import fs from 'fs-extra'
|
||||||
|
import { IPicGo } from 'piclist'
|
||||||
|
|
||||||
|
export const scriptLifecycleStages = [
|
||||||
|
'onSoftwareOpen',
|
||||||
|
'onSoftwareClose',
|
||||||
|
'preProcess',
|
||||||
|
'beforeTransform',
|
||||||
|
'transform',
|
||||||
|
'beforeUpload',
|
||||||
|
'upload',
|
||||||
|
'afterUpload',
|
||||||
|
'onUploadSuccess',
|
||||||
|
'onGalleryRemove',
|
||||||
|
'manualTrigger',
|
||||||
|
'uploader.advancedplist',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
function format(data: unknown): string {
|
||||||
|
if (data instanceof Error) {
|
||||||
|
return `${data.name}: ${data.message}\n${data.stack}`
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.stringify(data)
|
||||||
|
} catch {
|
||||||
|
return String(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runScript(ctx: IPicGo, script: string, extra: Record<string, any>): Promise<any> {
|
||||||
|
try {
|
||||||
|
const base64Decode = (str: string): string => Buffer.from(str, 'base64').toString('utf-8')
|
||||||
|
const base64Encode = (data: Buffer | string): string =>
|
||||||
|
(Buffer.isBuffer(data) ? data : Buffer.from(String(data))).toString('base64')
|
||||||
|
const exposedAPI = {
|
||||||
|
ctx,
|
||||||
|
extra,
|
||||||
|
console: Object.freeze({
|
||||||
|
log: (...args: unknown[]) => ctx.log.info(args.map(format).join(' ')),
|
||||||
|
info: (...args: unknown[]) => ctx.log.info(args.map(format).join(' ')),
|
||||||
|
error: (...args: unknown[]) => ctx.log.error(args.map(format).join(' ')),
|
||||||
|
debug: (...args: unknown[]) => ctx.log.debug(args.map(format).join(' ')),
|
||||||
|
}),
|
||||||
|
axios,
|
||||||
|
crypto,
|
||||||
|
setTimeout,
|
||||||
|
setInterval,
|
||||||
|
clearTimeout,
|
||||||
|
clearInterval,
|
||||||
|
fs,
|
||||||
|
path,
|
||||||
|
base64Decode,
|
||||||
|
base64Encode,
|
||||||
|
os,
|
||||||
|
Buffer,
|
||||||
|
}
|
||||||
|
vm.createContext(exposedAPI)
|
||||||
|
ctx.log.info('start to run script')
|
||||||
|
vm.runInContext(script, exposedAPI)
|
||||||
|
const promise = vm.runInContext(
|
||||||
|
`(async () => {
|
||||||
|
const result = main(ctx, extra)
|
||||||
|
if (result instanceof Promise) return await result
|
||||||
|
return result
|
||||||
|
})()`,
|
||||||
|
exposedAPI,
|
||||||
|
)
|
||||||
|
const result = await promise
|
||||||
|
ctx.log.info('script executed successfully')
|
||||||
|
return result
|
||||||
|
} catch (e) {
|
||||||
|
ctx.log.error(`script execution failed: ${e}`)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runScriptInStage(stage: string, ctx: IPicGo, extra: Record<string, any>): Promise<void> {
|
||||||
|
const baseDir = scriptsDir()
|
||||||
|
const enabledPaths: string[] = []
|
||||||
|
let scriptDir: string
|
||||||
|
const allConfig = picgo.getConfig<any>() || {}
|
||||||
|
const disabledList: string[] = allConfig.scripts?.disabledList || []
|
||||||
|
if (stage === 'uploader.advancedplist') {
|
||||||
|
scriptDir = path.join(baseDir, 'uploader', 'advancedplist')
|
||||||
|
stage = 'uploader/advancedplist'
|
||||||
|
} else {
|
||||||
|
scriptDir = path.join(baseDir, stage)
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await fs.readdir(scriptDir).catch(() => [])
|
||||||
|
if (files.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.endsWith('.js')) {
|
||||||
|
if (!disabledList.includes(`${stage}/${file}`)) {
|
||||||
|
enabledPaths.push(path.join(scriptDir, file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (enabledPaths.length === 0) {
|
||||||
|
logger.info(`no enabled scripts found in stage ${stage}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const scriptPath of enabledPaths) {
|
||||||
|
const scriptContent = fs.readFileSync(scriptPath, 'utf-8')
|
||||||
|
try {
|
||||||
|
await runScript(ctx, scriptContent, extra)
|
||||||
|
logger.info(`script ${scriptPath} in stage ${stage} executed successfully`)
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`script ${scriptPath} in stage ${stage} execution failed: ${e}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -238,8 +238,8 @@ class UploadTaskQueueManager {
|
|||||||
const rawInput = cloneDeep(input)
|
const rawInput = cloneDeep(input)
|
||||||
|
|
||||||
const res = await uploader.setWebContents(webContents).uploadReturnCtx(input)
|
const res = await uploader.setWebContents(webContents).uploadReturnCtx(input)
|
||||||
const imgs = res[0] ? res[0] : false
|
const imgs = res.ctx?.output ? res.ctx.output : false
|
||||||
const backupImgs = res[1] ? res[1] : false
|
const backupImgs = res.backupCtx?.output ? res.backupCtx.output : false
|
||||||
const allConfig = picgo.getConfig<any>() || {}
|
const allConfig = picgo.getConfig<any>() || {}
|
||||||
|
|
||||||
if (imgs !== false && imgs.length > 0) {
|
if (imgs !== false && imgs.length > 0) {
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ const editorRef = ref(null)
|
|||||||
const view = shallowRef(null)
|
const view = shallowRef(null)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
const languageExtension = props.language === 'json' ? json() : javascript()
|
||||||
const startState = EditorState.create({
|
const startState = EditorState.create({
|
||||||
doc: props.modelValue,
|
doc: props.modelValue,
|
||||||
extensions: [
|
extensions: [
|
||||||
lineNumbers(),
|
lineNumbers(),
|
||||||
history(),
|
history(),
|
||||||
keymap.of([...defaultKeymap, ...historyKeymap]),
|
keymap.of([...defaultKeymap, ...historyKeymap]),
|
||||||
json(),
|
languageExtension,
|
||||||
javascript(),
|
|
||||||
oneDark,
|
oneDark,
|
||||||
search({ top: true }),
|
search({ top: true }),
|
||||||
keymap.of([...searchKeymap]),
|
keymap.of([...searchKeymap]),
|
||||||
|
|||||||
@@ -95,6 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</SettingCard>
|
</SettingCard>
|
||||||
|
<slot name="extra-config" />
|
||||||
<slot />
|
<slot />
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="tight ? 'mb-0' : 'mb-3'" class="flex items-center gap-2 text-sm font-medium text-main">
|
<div v-if="title" :class="tight ? 'mb-0' : 'mb-3'" class="flex items-center gap-2 text-sm font-medium text-main">
|
||||||
<slot name="icon">
|
<slot name="icon">
|
||||||
<component :is="icon" v-if="icon" :size="iconSize" class="text-accent" />
|
<component :is="icon" v-if="icon" :size="iconSize" class="text-accent" />
|
||||||
</slot>
|
</slot>
|
||||||
@@ -68,14 +68,14 @@ onClickOutside(dropdownRef, () => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
tight = true,
|
tight = true,
|
||||||
title,
|
title = '',
|
||||||
icon = null,
|
icon = null,
|
||||||
iconSize = 18,
|
iconSize = 18,
|
||||||
zeroPlaceholder,
|
zeroPlaceholder,
|
||||||
allList,
|
allList,
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
tight?: boolean
|
tight?: boolean
|
||||||
title: string
|
title?: string
|
||||||
icon?: any
|
icon?: any
|
||||||
iconSize?: number
|
iconSize?: number
|
||||||
zeroPlaceholder: string
|
zeroPlaceholder: string
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"clear": "Clear",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
"picbed": "PicBed",
|
"picbed": "PicBed",
|
||||||
"picBedQrCode": "PicBed QR Code",
|
"picBedQrCode": "PicBed QR Code",
|
||||||
"plugins": "Plugins",
|
"plugins": "Plugins",
|
||||||
|
"scripts": "Scripts",
|
||||||
"selected": "Selected",
|
"selected": "Selected",
|
||||||
"selectPicBeds": "Select PicBeds",
|
"selectPicBeds": "Select PicBeds",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
@@ -714,15 +716,65 @@
|
|||||||
"rename": {
|
"rename": {
|
||||||
"placeholder": "Please enter new file name"
|
"placeholder": "Please enter new file name"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"addNew": "Add New Script",
|
||||||
|
"chooseScriptType": "Please choose script type",
|
||||||
|
"confirmDelete": "Confirm delete script?",
|
||||||
|
"createScript": "Create Script",
|
||||||
|
"createScriptsToGo": "Please create scripts to get started",
|
||||||
|
"deleteFailed": "Delete script failed",
|
||||||
|
"deleteScript": "Delete Script",
|
||||||
|
"deleteScriptConfirm": "Confirm delete script {name}?",
|
||||||
|
"deleteScriptTitle": "Delete Script",
|
||||||
|
"deleteSuccess": "Delete script succeeded",
|
||||||
|
"description": "Extend PicList functionality with lightweight scripts",
|
||||||
|
"disabled": "Disabled",
|
||||||
|
"disableScript": "Disable Script",
|
||||||
|
"duplicateScriptNameError": "Script name already exists, please use a different name",
|
||||||
|
"editScript": "Edit Script",
|
||||||
|
"emptyScriptList": "Script list is empty",
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"enableScript": "Enable Script",
|
||||||
|
"newScript": "New Script",
|
||||||
|
"NoScripts": "No Scripts",
|
||||||
|
"noScriptsFound": "No Scripts Found",
|
||||||
|
"openScriptFolder": "Open Script Folder",
|
||||||
|
"pleaseEnterScriptName": "Please enter script name",
|
||||||
|
"runScript": "Run Script",
|
||||||
|
"runScriptFailed": "Run script failed {errorMessage}",
|
||||||
|
"runScriptSuccess": "Script ran successfully",
|
||||||
|
"scriptContentPlaceholder": "Please enter script content",
|
||||||
|
"scriptNamePlaceholder": "Please enter script name",
|
||||||
|
"scriptSaved": "Script saved",
|
||||||
|
"scriptsTypes": {
|
||||||
|
"_name": "Script Types",
|
||||||
|
"afterUpload": "After Upload",
|
||||||
|
"beforeTransform": "Before Transform",
|
||||||
|
"beforeUpload": "Before Upload",
|
||||||
|
"manualTrigger": "Manual Trigger",
|
||||||
|
"onGalleryRemove": "On Gallery Remove",
|
||||||
|
"onSoftwareClose": "On Software Close",
|
||||||
|
"onSoftwareOpen": "On Software Open",
|
||||||
|
"onUploadSuccess": "On Upload Success",
|
||||||
|
"preProcess": "Pre Process",
|
||||||
|
"transform": "Transform",
|
||||||
|
"upload": "On Upload",
|
||||||
|
"uploader": {
|
||||||
|
"advancedplist": "Advanced Custom Uploader"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"selectScriptType": "Select Script Type",
|
||||||
|
"title": "Script Management"
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"chooseLogLevel": "Please select log level",
|
"chooseLogLevel": "Please select log level",
|
||||||
"enableServer": "Enable Upload API Service",
|
"enableServer": "Enable Upload API Service",
|
||||||
"enableWebServer": "Enable Web Server",
|
"enableWebServer": "Enable Web Server",
|
||||||
"guiLogFile": "GUI Log File",
|
"guiLogFile": "Open GUI Log File",
|
||||||
"invalidJson": "Invalid JSON format",
|
"invalidJson": "Invalid JSON format",
|
||||||
"logDialogDesc": "View log files and configure log settings",
|
"logDialogDesc": "View log files and configure log settings",
|
||||||
"logFile": "General Log File",
|
"logFile": "Open General Log File",
|
||||||
"logFilePath": "Log File Path",
|
"logFilePath": "Log File Path",
|
||||||
"logFilePathDesc": "View log file directory",
|
"logFilePathDesc": "View log file directory",
|
||||||
"logFileSize": "Log File Size",
|
"logFileSize": "Log File Size",
|
||||||
@@ -737,7 +789,7 @@
|
|||||||
"success": "Success",
|
"success": "Success",
|
||||||
"warn": "Warn"
|
"warn": "Warn"
|
||||||
},
|
},
|
||||||
"manageLogFile": "Manage Log File",
|
"manageLogFile": "Open Cloud Log File",
|
||||||
"networkAndProxy": "Network and Proxy",
|
"networkAndProxy": "Network and Proxy",
|
||||||
"pluginInstallMirror": "Plugin Install Mirror",
|
"pluginInstallMirror": "Plugin Install Mirror",
|
||||||
"pluginInstallProxy": "Plugin Install Proxy",
|
"pluginInstallProxy": "Plugin Install Proxy",
|
||||||
@@ -782,7 +834,8 @@
|
|||||||
"commonConfig": "Common Configuration",
|
"commonConfig": "Common Configuration",
|
||||||
"configureSync": "Platform Config",
|
"configureSync": "Platform Config",
|
||||||
"downloadSettings": "Download Settings",
|
"downloadSettings": "Download Settings",
|
||||||
"editConfigFile": "Edit Configuration File",
|
"editCloudConfigFile": "Edit Cloud Configuration File",
|
||||||
|
"editConfigFile": "Edit General Configuration File",
|
||||||
"fileManagement": "File Management",
|
"fileManagement": "File Management",
|
||||||
"galleryDB": "Gallery Database Sync",
|
"galleryDB": "Gallery Database Sync",
|
||||||
"gitea": {
|
"gitea": {
|
||||||
@@ -804,7 +857,7 @@
|
|||||||
"token": "GitHub Access Token",
|
"token": "GitHub Access Token",
|
||||||
"username": "GitHub Username"
|
"username": "GitHub Username"
|
||||||
},
|
},
|
||||||
"manageConfig": "Manage Configuration",
|
"manageConfig": "Cloud Configuration",
|
||||||
"migrateDesc": "Import configuration and gallery data from PicGo",
|
"migrateDesc": "Import configuration and gallery data from PicGo",
|
||||||
"migrateDescPicList": "Import configuration and gallery data from PicList Installation",
|
"migrateDescPicList": "Import configuration and gallery data from PicList Installation",
|
||||||
"migrateFromPicGo": "Migrate from PicGo",
|
"migrateFromPicGo": "Migrate from PicGo",
|
||||||
@@ -815,8 +868,8 @@
|
|||||||
"mirgrateSuccess": "Import Successful, Please Restart PicList to Take Effect",
|
"mirgrateSuccess": "Import Successful, Please Restart PicList to Take Effect",
|
||||||
"mirgrateTitle": "Notification",
|
"mirgrateTitle": "Notification",
|
||||||
"notConfigured": "Not configured",
|
"notConfigured": "Not configured",
|
||||||
"openConfigFile": "Open Configuration File",
|
"openConfigFile": "Open General Configuration File",
|
||||||
"openConfigFileDir": "Open Configuration File Directory",
|
"openConfigFileDir": "Open Application File Directory",
|
||||||
"selectType": "Please select sync platform",
|
"selectType": "Please select sync platform",
|
||||||
"syncActions": "Sync Actions",
|
"syncActions": "Sync Actions",
|
||||||
"syncConfigProxy": "Proxy",
|
"syncConfigProxy": "Proxy",
|
||||||
@@ -919,8 +972,8 @@
|
|||||||
"advancedRnameDialogDesc": "Configure advanced renaming rules for file uploads",
|
"advancedRnameDialogDesc": "Configure advanced renaming rules for file uploads",
|
||||||
"advancedRnameFormat": "Advanced Rename Format",
|
"advancedRnameFormat": "Advanced Rename Format",
|
||||||
"autoCopyUrlAfterUpload": "Auto Copy URL After Upload",
|
"autoCopyUrlAfterUpload": "Auto Copy URL After Upload",
|
||||||
"autoImportInManage": "Auto Import Configuration in Management Page",
|
"autoImportInManage": "Auto Import Configuration in Cloud Page",
|
||||||
"autoImportInManageHint": "After enabling, the management page will automatically import the corresponding image bed configuration",
|
"autoImportInManageHint": "After enabling, the cloud page will automatically import the corresponding image bed configuration",
|
||||||
"autoImportPicBed": "Select the image bed to enable auto import",
|
"autoImportPicBed": "Select the image bed to enable auto import",
|
||||||
"availablePlaceholders": "Available Placeholders",
|
"availablePlaceholders": "Available Placeholders",
|
||||||
"availablePlaceholdersTitle": "Use these placeholders to customize link format",
|
"availablePlaceholdersTitle": "Use these placeholders to customize link format",
|
||||||
@@ -1154,6 +1207,15 @@
|
|||||||
"title": "Configurations"
|
"title": "Configurations"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"createScript": "Create Script",
|
||||||
|
"deleteScript": "Delete Script",
|
||||||
|
"duplicateScriptNameError": "Script name already exists",
|
||||||
|
"editScripts": "Edit Script",
|
||||||
|
"newScriptTitle": "New Script",
|
||||||
|
"noScriptsFound": "No Scripts Found",
|
||||||
|
"pleaseEnterScriptName": "Please enter script name"
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"theme": {
|
"theme": {
|
||||||
"auto": "Auto",
|
"auto": "Auto",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
|
"clear": "清空",
|
||||||
"close": "关闭",
|
"close": "关闭",
|
||||||
"confirm": "确认",
|
"confirm": "确认",
|
||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
"picbed": "图床",
|
"picbed": "图床",
|
||||||
"picBedQrCode": "图床配置二维码",
|
"picBedQrCode": "图床配置二维码",
|
||||||
"plugins": "插件",
|
"plugins": "插件",
|
||||||
|
"scripts": "脚本",
|
||||||
"selected": "已选中",
|
"selected": "已选中",
|
||||||
"selectPicBeds": "请选择图床",
|
"selectPicBeds": "请选择图床",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
@@ -547,7 +549,7 @@
|
|||||||
"savedConfigs": "已配置云端",
|
"savedConfigs": "已配置云端",
|
||||||
"selectPlaceholder": "请选择",
|
"selectPlaceholder": "请选择",
|
||||||
"tips": "提示",
|
"tips": "提示",
|
||||||
"title": "云端管理",
|
"title": "云端存储管理",
|
||||||
"viewDetails": "查看详情"
|
"viewDetails": "查看详情"
|
||||||
},
|
},
|
||||||
"main": {
|
"main": {
|
||||||
@@ -714,15 +716,65 @@
|
|||||||
"rename": {
|
"rename": {
|
||||||
"placeholder": "请输入新的文件名"
|
"placeholder": "请输入新的文件名"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"addNew": "添加新脚本",
|
||||||
|
"chooseScriptType": "请选择脚本类型",
|
||||||
|
"confirmDelete": "确认删除脚本吗?",
|
||||||
|
"createScript": "创建脚本",
|
||||||
|
"createScriptsToGo": "请先创建脚本以开始使用",
|
||||||
|
"deleteFailed": "删除脚本失败",
|
||||||
|
"deleteScript": "删除脚本",
|
||||||
|
"deleteScriptConfirm": "确认删除脚本 {name} 吗?",
|
||||||
|
"deleteScriptTitle": "删除脚本",
|
||||||
|
"deleteSuccess": "删除脚本成功",
|
||||||
|
"description": "使用轻量级脚本扩展 PicList 的功能",
|
||||||
|
"disabled": "已禁用",
|
||||||
|
"disableScript": "禁用脚本",
|
||||||
|
"duplicateScriptNameError": "脚本名称已存在,请使用不同的名称",
|
||||||
|
"editScript": "编辑脚本",
|
||||||
|
"emptyScriptList": "脚本列表为空",
|
||||||
|
"enabled": "已启用",
|
||||||
|
"enableScript": "启用脚本",
|
||||||
|
"newScript": "新脚本",
|
||||||
|
"NoScripts": "暂无脚本",
|
||||||
|
"noScriptsFound": "未找到脚本",
|
||||||
|
"openScriptFolder": "打开脚本文件夹",
|
||||||
|
"pleaseEnterScriptName": "请输入脚本名称",
|
||||||
|
"runScript": "运行脚本",
|
||||||
|
"runScriptFailed": "运行脚本失败 {errorMessage}",
|
||||||
|
"runScriptSuccess": "脚本运行成功",
|
||||||
|
"scriptContentPlaceholder": "请输入脚本内容",
|
||||||
|
"scriptNamePlaceholder": "请输入脚本名称",
|
||||||
|
"scriptSaved": "脚本已保存",
|
||||||
|
"scriptsTypes": {
|
||||||
|
"_name": "脚本类型",
|
||||||
|
"afterUpload": "上传后",
|
||||||
|
"beforeTransform": "图片变换前",
|
||||||
|
"beforeUpload": "上传前",
|
||||||
|
"manualTrigger": "手动触发",
|
||||||
|
"onGalleryRemove": "相册删除时",
|
||||||
|
"onSoftwareClose": "软件关闭时",
|
||||||
|
"onSoftwareOpen": "软件启动时",
|
||||||
|
"onUploadSuccess": "上传成功时",
|
||||||
|
"preProcess": "上传处理前",
|
||||||
|
"transform": "图片变换时",
|
||||||
|
"upload": "上传时",
|
||||||
|
"uploader": {
|
||||||
|
"advancedplist": "高级自定义图床"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"selectScriptType": "选择脚本类型",
|
||||||
|
"title": "脚本管理"
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"chooseLogLevel": "请选择日志记录等级",
|
"chooseLogLevel": "请选择日志记录等级",
|
||||||
"enableServer": "是否开启上传API服务",
|
"enableServer": "是否开启上传API服务",
|
||||||
"enableWebServer": "是否开启 Web 服务",
|
"enableWebServer": "是否开启 Web 服务",
|
||||||
"guiLogFile": "GUI 日志文件",
|
"guiLogFile": "打开 GUI 日志文件",
|
||||||
"invalidJson": "无效的JSON格式",
|
"invalidJson": "无效的JSON格式",
|
||||||
"logDialogDesc": "查看日志文件和配置日志设置",
|
"logDialogDesc": "查看日志文件和配置日志设置",
|
||||||
"logFile": "常规日志文件",
|
"logFile": "打开常规日志文件",
|
||||||
"logFilePath": "日志文件路径",
|
"logFilePath": "日志文件路径",
|
||||||
"logFilePathDesc": "查看日志文件所在目录",
|
"logFilePathDesc": "查看日志文件所在目录",
|
||||||
"logFileSize": "日志文件大小",
|
"logFileSize": "日志文件大小",
|
||||||
@@ -737,7 +789,7 @@
|
|||||||
"success": "成功",
|
"success": "成功",
|
||||||
"warn": "警告"
|
"warn": "警告"
|
||||||
},
|
},
|
||||||
"manageLogFile": "管理日志文件",
|
"manageLogFile": "打开云端日志文件",
|
||||||
"networkAndProxy": "网络与代理",
|
"networkAndProxy": "网络与代理",
|
||||||
"pluginInstallMirror": "插件安装镜像",
|
"pluginInstallMirror": "插件安装镜像",
|
||||||
"pluginInstallProxy": "插件安装代理",
|
"pluginInstallProxy": "插件安装代理",
|
||||||
@@ -782,7 +834,8 @@
|
|||||||
"commonConfig": "通用配置",
|
"commonConfig": "通用配置",
|
||||||
"configureSync": "平台设置",
|
"configureSync": "平台设置",
|
||||||
"downloadSettings": "下载配置",
|
"downloadSettings": "下载配置",
|
||||||
"editConfigFile": "编辑配置文件",
|
"editCloudConfigFile": "编辑云端配置文件",
|
||||||
|
"editConfigFile": "编辑通用配置文件",
|
||||||
"fileManagement": "文件管理",
|
"fileManagement": "文件管理",
|
||||||
"galleryDB": "相册数据库同步",
|
"galleryDB": "相册数据库同步",
|
||||||
"gitea": {
|
"gitea": {
|
||||||
@@ -804,7 +857,7 @@
|
|||||||
"token": "GitHub 访问令牌",
|
"token": "GitHub 访问令牌",
|
||||||
"username": "GitHub 用户名"
|
"username": "GitHub 用户名"
|
||||||
},
|
},
|
||||||
"manageConfig": "管理配置",
|
"manageConfig": "云端管理配置",
|
||||||
"migrateDesc": "从 PicGo 导入配置和相册数据",
|
"migrateDesc": "从 PicGo 导入配置和相册数据",
|
||||||
"migrateDescPicList": "从安装版PicList导入配置和相册数据",
|
"migrateDescPicList": "从安装版PicList导入配置和相册数据",
|
||||||
"migrateFromPicGo": "从PicGo迁移",
|
"migrateFromPicGo": "从PicGo迁移",
|
||||||
@@ -815,8 +868,8 @@
|
|||||||
"mirgrateSuccess": "导入成功, 请重启PicList生效",
|
"mirgrateSuccess": "导入成功, 请重启PicList生效",
|
||||||
"mirgrateTitle": "通知",
|
"mirgrateTitle": "通知",
|
||||||
"notConfigured": "未配置",
|
"notConfigured": "未配置",
|
||||||
"openConfigFile": "打开配置文件",
|
"openConfigFile": "打开通用配置文件",
|
||||||
"openConfigFileDir": "打开配置文件目录",
|
"openConfigFileDir": "打开应用文件目录",
|
||||||
"selectType": "请选择平台",
|
"selectType": "请选择平台",
|
||||||
"syncActions": "同步操作",
|
"syncActions": "同步操作",
|
||||||
"syncConfigProxy": "代理",
|
"syncConfigProxy": "代理",
|
||||||
@@ -919,8 +972,8 @@
|
|||||||
"advancedRnameDialogDesc": "配置文件上传时的高级重命名规则",
|
"advancedRnameDialogDesc": "配置文件上传时的高级重命名规则",
|
||||||
"advancedRnameFormat": "高级重命名格式",
|
"advancedRnameFormat": "高级重命名格式",
|
||||||
"autoCopyUrlAfterUpload": "上传后自动复制URL",
|
"autoCopyUrlAfterUpload": "上传后自动复制URL",
|
||||||
"autoImportInManage": "管理页面自动导入配置",
|
"autoImportInManage": "云端页面自动导入配置",
|
||||||
"autoImportInManageHint": "启用后,管理页面将自动导入对应图床配置",
|
"autoImportInManageHint": "启用后,云端页面将自动导入对应图床配置",
|
||||||
"autoImportPicBed": "选择需要开启自动导入的图床",
|
"autoImportPicBed": "选择需要开启自动导入的图床",
|
||||||
"availablePlaceholders": "可用占位符",
|
"availablePlaceholders": "可用占位符",
|
||||||
"availablePlaceholdersTitle": "使用以下占位符自定义链接格式",
|
"availablePlaceholdersTitle": "使用以下占位符自定义链接格式",
|
||||||
@@ -1146,6 +1199,7 @@
|
|||||||
"duplicateSuccess": "拷贝成功",
|
"duplicateSuccess": "拷贝成功",
|
||||||
"duplicateTitle": "拷贝配置",
|
"duplicateTitle": "拷贝配置",
|
||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
|
|
||||||
"removeFromFavorites": "从快速切换中移除",
|
"removeFromFavorites": "从快速切换中移除",
|
||||||
"selected": "已启用",
|
"selected": "已启用",
|
||||||
"setAsDefault": "设为默认图床",
|
"setAsDefault": "设为默认图床",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
|
"clear": "清空",
|
||||||
"close": "關閉",
|
"close": "關閉",
|
||||||
"confirm": "確認",
|
"confirm": "確認",
|
||||||
"edit": "編輯",
|
"edit": "編輯",
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
"picbed": "圖床",
|
"picbed": "圖床",
|
||||||
"picBedQrCode": "圖床配置 QRCODE",
|
"picBedQrCode": "圖床配置 QRCODE",
|
||||||
"plugins": "插件",
|
"plugins": "插件",
|
||||||
|
"scripts": "腳本",
|
||||||
"selected": "已選中",
|
"selected": "已選中",
|
||||||
"selectPicBeds": "請選擇圖床",
|
"selectPicBeds": "請選擇圖床",
|
||||||
"settings": "設定",
|
"settings": "設定",
|
||||||
@@ -714,15 +716,65 @@
|
|||||||
"rename": {
|
"rename": {
|
||||||
"placeholder": "請輸入新的檔案名稱"
|
"placeholder": "請輸入新的檔案名稱"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"addNew": "添加新腳本",
|
||||||
|
"chooseScriptType": "请选择腳本類型",
|
||||||
|
"confirmDelete": "确认删除腳本吗?",
|
||||||
|
"createScript": "创建腳本",
|
||||||
|
"createScriptsToGo": "请先创建腳本以开始使用",
|
||||||
|
"deleteFailed": "删除腳本失败",
|
||||||
|
"deleteScript": "删除腳本",
|
||||||
|
"deleteScriptConfirm": "确认删除腳本 {name} 吗?",
|
||||||
|
"deleteScriptTitle": "删除腳本",
|
||||||
|
"deleteSuccess": "删除腳本成功",
|
||||||
|
"description": "使用轻量级脚本扩展 PicList 的功能",
|
||||||
|
"disabled": "已禁用",
|
||||||
|
"disableScript": "禁用腳本",
|
||||||
|
"duplicateScriptNameError": "腳本名稱已存在,請使用不同的名稱",
|
||||||
|
"editScript": "編輯腳本",
|
||||||
|
"emptyScriptList": "腳本列表為空",
|
||||||
|
"enabled": "已啟用",
|
||||||
|
"enableScript": "啟用腳本",
|
||||||
|
"newScript": "新腳本",
|
||||||
|
"NoScripts": "暫無腳本",
|
||||||
|
"noScriptsFound": "未找到腳本",
|
||||||
|
"openScriptFolder": "打開腳本文件夾",
|
||||||
|
"pleaseEnterScriptName": "請輸入腳本名稱",
|
||||||
|
"runScript": "運行腳本",
|
||||||
|
"runScriptFailed": "運行腳本失敗 {errorMessage}",
|
||||||
|
"runScriptSuccess": "腳本運行成功",
|
||||||
|
"scriptContentPlaceholder": "請輸入腳本內容",
|
||||||
|
"scriptNamePlaceholder": "請輸入腳本名稱",
|
||||||
|
"scriptSaved": "腳本已保存",
|
||||||
|
"scriptsTypes": {
|
||||||
|
"_name": "腳本類型",
|
||||||
|
"afterUpload": "上傳後",
|
||||||
|
"beforeTransform": "圖片變換前",
|
||||||
|
"beforeUpload": "上傳前",
|
||||||
|
"manualTrigger": "手動觸發",
|
||||||
|
"onGalleryRemove": "相冊刪除時",
|
||||||
|
"onSoftwareClose": "軟件關閉時",
|
||||||
|
"onSoftwareOpen": "軟件啟動時",
|
||||||
|
"onUploadSuccess": "上傳成功時",
|
||||||
|
"preProcess": "上傳處理前",
|
||||||
|
"transform": "圖片變換時",
|
||||||
|
"upload": "上傳時",
|
||||||
|
"uploader": {
|
||||||
|
"advancedplist": "高級自定義圖床"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"selectScriptType": "選擇腳本類型",
|
||||||
|
"title": "腳本管理"
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"chooseLogLevel": "請選擇日誌記錄等級",
|
"chooseLogLevel": "請選擇日誌記錄等級",
|
||||||
"enableServer": "是否開啟上傳API服務",
|
"enableServer": "是否開啟上傳API服務",
|
||||||
"enableWebServer": "是否開啟 Web 服務",
|
"enableWebServer": "是否開啟 Web 服務",
|
||||||
"guiLogFile": "GUI 日誌文件",
|
"guiLogFile": "打開 GUI 日誌文件",
|
||||||
"invalidJson": "無效的 JSON 格式",
|
"invalidJson": "無效的 JSON 格式",
|
||||||
"logDialogDesc": "查看日誌文件和配置日誌設置",
|
"logDialogDesc": "查看日誌文件和配置日誌設置",
|
||||||
"logFile": "常規日誌文件",
|
"logFile": "打開常規日誌文件",
|
||||||
"logFilePath": "日誌文件路徑",
|
"logFilePath": "日誌文件路徑",
|
||||||
"logFilePathDesc": "查看日誌文件所在目錄",
|
"logFilePathDesc": "查看日誌文件所在目錄",
|
||||||
"logFileSize": "日誌文件大小",
|
"logFileSize": "日誌文件大小",
|
||||||
@@ -737,7 +789,7 @@
|
|||||||
"success": "成功",
|
"success": "成功",
|
||||||
"warn": "警告"
|
"warn": "警告"
|
||||||
},
|
},
|
||||||
"manageLogFile": "管理日誌文件",
|
"manageLogFile": "打開雲端日誌文件",
|
||||||
"networkAndProxy": "網絡與代理",
|
"networkAndProxy": "網絡與代理",
|
||||||
"pluginInstallMirror": "插件安裝鏡像",
|
"pluginInstallMirror": "插件安裝鏡像",
|
||||||
"pluginInstallProxy": "插件安裝代理",
|
"pluginInstallProxy": "插件安裝代理",
|
||||||
@@ -782,7 +834,8 @@
|
|||||||
"commonConfig": "通用配置",
|
"commonConfig": "通用配置",
|
||||||
"configureSync": "平台配置",
|
"configureSync": "平台配置",
|
||||||
"downloadSettings": "下載配置",
|
"downloadSettings": "下載配置",
|
||||||
"editConfigFile": "編輯配置文件",
|
"editCloudConfigFile": "編輯雲端配置文件",
|
||||||
|
"editConfigFile": "編輯通用配置文件",
|
||||||
"fileManagement": "文件管理",
|
"fileManagement": "文件管理",
|
||||||
"galleryDB": "相冊數據庫同步",
|
"galleryDB": "相冊數據庫同步",
|
||||||
"gitea": {
|
"gitea": {
|
||||||
@@ -804,7 +857,7 @@
|
|||||||
"token": "GitHub 訪問令牌",
|
"token": "GitHub 訪問令牌",
|
||||||
"username": "GitHub 用戶名"
|
"username": "GitHub 用戶名"
|
||||||
},
|
},
|
||||||
"manageConfig": "管理配置",
|
"manageConfig": "雲端配置",
|
||||||
"migrateDesc": "從 PicGo 導入配置和相冊數據",
|
"migrateDesc": "從 PicGo 導入配置和相冊數據",
|
||||||
"migrateDescPicList": "從安裝版 PicList 導入配置和相冊數據",
|
"migrateDescPicList": "從安裝版 PicList 導入配置和相冊數據",
|
||||||
"migrateFromPicGo": "從PicGo遷移",
|
"migrateFromPicGo": "從PicGo遷移",
|
||||||
@@ -815,8 +868,8 @@
|
|||||||
"mirgrateSuccess": "導入成功, 請重啟PicList生效",
|
"mirgrateSuccess": "導入成功, 請重啟PicList生效",
|
||||||
"mirgrateTitle": "通知",
|
"mirgrateTitle": "通知",
|
||||||
"notConfigured": "未配置",
|
"notConfigured": "未配置",
|
||||||
"openConfigFile": "打開配置文件",
|
"openConfigFile": "打開通用配置文件",
|
||||||
"openConfigFileDir": "打開配置文件目錄",
|
"openConfigFileDir": "打開應用文件目錄",
|
||||||
"selectType": "請選擇同步平台類型",
|
"selectType": "請選擇同步平台類型",
|
||||||
"syncActions": "同步操作",
|
"syncActions": "同步操作",
|
||||||
"syncConfigProxy": "代理",
|
"syncConfigProxy": "代理",
|
||||||
@@ -919,8 +972,8 @@
|
|||||||
"advancedRnameDialogDesc": "配置文件上傳時的高級重命名規則",
|
"advancedRnameDialogDesc": "配置文件上傳時的高級重命名規則",
|
||||||
"advancedRnameFormat": "高級重命名格式",
|
"advancedRnameFormat": "高級重命名格式",
|
||||||
"autoCopyUrlAfterUpload": "上傳後自動複製URL",
|
"autoCopyUrlAfterUpload": "上傳後自動複製URL",
|
||||||
"autoImportInManage": "管理頁面自動導入配置",
|
"autoImportInManage": "雲端頁面自動導入配置",
|
||||||
"autoImportInManageHint": "啟用後,管理頁面將自動導入對應圖床配置",
|
"autoImportInManageHint": "啟用後,雲端頁面將自動導入對應圖床配置",
|
||||||
"autoImportPicBed": "選擇需要開啟自動導入的圖床",
|
"autoImportPicBed": "選擇需要開啟自動導入的圖床",
|
||||||
"availablePlaceholders": "可用占位符",
|
"availablePlaceholders": "可用占位符",
|
||||||
"availablePlaceholdersTitle": "使用以下占位符自定義鏈接格式",
|
"availablePlaceholdersTitle": "使用以下占位符自定義鏈接格式",
|
||||||
@@ -1154,6 +1207,15 @@
|
|||||||
"title": "配置"
|
"title": "配置"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"createScript": "創建腳本",
|
||||||
|
"deleteScript": "刪除腳本",
|
||||||
|
"duplicateScriptNameError": "腳本名稱已存在",
|
||||||
|
"editScripts": "編輯腳本",
|
||||||
|
"newScriptTitle": "新建腳本",
|
||||||
|
"noScriptsFound": "未找到腳本",
|
||||||
|
"pleaseEnterScriptName": "請輸入腳本名稱"
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"theme": {
|
"theme": {
|
||||||
"auto": "自動",
|
"auto": "自動",
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ import {
|
|||||||
Cloud,
|
Cloud,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
DatabaseIcon,
|
DatabaseIcon,
|
||||||
|
FileCode,
|
||||||
ImagesIcon,
|
ImagesIcon,
|
||||||
Info,
|
Info,
|
||||||
PlugIcon,
|
PlugIcon,
|
||||||
@@ -271,6 +272,11 @@ const navigationItems = computed(() => [
|
|||||||
path: '/main-page/plugins',
|
path: '/main-page/plugins',
|
||||||
icon: PlugIcon,
|
icon: PlugIcon,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: t('navigation.scripts'),
|
||||||
|
path: '/main-page/scripts',
|
||||||
|
icon: FileCode,
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<config-form :id="type" ref="$configForm" :config="config" type="uploader">
|
<config-form :id="type" ref="$configForm" :config="config" type="uploader">
|
||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
<div class="mb-4 flex flex-wrap gap-3 rounded-xl border border-border bg-accent/10 p-4">
|
<div class="mb-4 flex flex-wrap gap-3 rounded-xl border border-border bg-accent/10 p-4">
|
||||||
<CustomButton type="secondary" :icon="RotateCcw" :text="t('common.reset')" @click="handleReset" />
|
<CustomButton type="secondary" :icon="RotateCcw" :text="t('common.clear')" @click="handleReset" />
|
||||||
<CustomButton type="primary" :icon="Check" :text="t('common.confirm')" @click="handleConfirm" />
|
<CustomButton type="primary" :icon="Check" :text="t('common.confirm')" @click="handleConfirm" />
|
||||||
|
|
||||||
<div v-if="picBedConfigList.length > 0" class="relative">
|
<div v-if="picBedConfigList.length > 0" class="relative">
|
||||||
@@ -179,6 +179,7 @@ const handleConfirm = async () => {
|
|||||||
async function getPicBeds() {
|
async function getPicBeds() {
|
||||||
try {
|
try {
|
||||||
const result = await window.electron.triggerRPC<any>(IRPCActionType.PICBED_GET_PICBED_CONFIG, $route.params.type)
|
const result = await window.electron.triggerRPC<any>(IRPCActionType.PICBED_GET_PICBED_CONFIG, $route.params.type)
|
||||||
|
console.log('PicBed config result:', result)
|
||||||
config.value = result.config
|
config.value = result.config
|
||||||
picBedName.value = result.name
|
picBedName.value = result.name
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -220,7 +221,30 @@ async function handleConfigImport(configItem: IUploaderConfigListItem) {
|
|||||||
|
|
||||||
const handleReset = async () => {
|
const handleReset = async () => {
|
||||||
try {
|
try {
|
||||||
await window.electron.triggerRPC<void>(IRPCActionType.UPLOADER_RESET_CONFIG, type.value, $route.params.configId)
|
config.value.forEach(item => {
|
||||||
|
let defaultValue
|
||||||
|
switch (item.type) {
|
||||||
|
case 'text':
|
||||||
|
case 'password':
|
||||||
|
defaultValue = ''
|
||||||
|
break
|
||||||
|
case 'number':
|
||||||
|
defaultValue = 0
|
||||||
|
break
|
||||||
|
case 'checkbox':
|
||||||
|
defaultValue = []
|
||||||
|
break
|
||||||
|
case 'select':
|
||||||
|
defaultValue = item.choices && item.choices.length > 0 ? item.choices[0].value : null
|
||||||
|
break
|
||||||
|
case 'switch':
|
||||||
|
defaultValue = false
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
defaultValue = null
|
||||||
|
}
|
||||||
|
$configForm.value?.updateRuleForm(item.name, defaultValue)
|
||||||
|
})
|
||||||
message.success(t('pages.picBedConfigs.resetSuccess'))
|
message.success(t('pages.picBedConfigs.resetSuccess'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to reset configuration:', error)
|
console.error('Failed to reset configuration:', error)
|
||||||
|
|||||||
@@ -279,6 +279,11 @@
|
|||||||
:icon="Edit"
|
:icon="Edit"
|
||||||
@click="editFile('data.json')"
|
@click="editFile('data.json')"
|
||||||
/>
|
/>
|
||||||
|
<CustomNavCard
|
||||||
|
:title="t('pages.settings.sync.editCloudConfigFile')"
|
||||||
|
:icon="Edit"
|
||||||
|
@click="editFile('manage.json')"
|
||||||
|
/>
|
||||||
<CustomNavCard
|
<CustomNavCard
|
||||||
:title="t('pages.settings.sync.openConfigFileDir')"
|
:title="t('pages.settings.sync.openConfigFileDir')"
|
||||||
:icon="FolderOpen"
|
:icon="FolderOpen"
|
||||||
@@ -562,10 +567,22 @@
|
|||||||
>
|
>
|
||||||
<SettingSection :icon="FileText" :title="t('pages.settings.advanced.logging')">
|
<SettingSection :icon="FileText" :title="t('pages.settings.advanced.logging')">
|
||||||
<CustomNavCard
|
<CustomNavCard
|
||||||
:title="t('pages.settings.advanced.logFilePath')"
|
:title="t('pages.settings.advanced.logFile')"
|
||||||
:description="t('pages.settings.advanced.logFilePathDesc')"
|
description="piclist.log"
|
||||||
:icon="FolderOpen"
|
:icon="FileText"
|
||||||
@click="openDirectory"
|
@click="openFile('piclist.log')"
|
||||||
|
/>
|
||||||
|
<CustomNavCard
|
||||||
|
:title="t('pages.settings.advanced.guiLogFile')"
|
||||||
|
description="piclist-gui-local.log"
|
||||||
|
:icon="FileText"
|
||||||
|
@click="openFile('piclist-gui-local.log')"
|
||||||
|
/>
|
||||||
|
<CustomNavCard
|
||||||
|
:title="t('pages.settings.advanced.manageLogFile')"
|
||||||
|
description="manage.log"
|
||||||
|
:icon="FileText"
|
||||||
|
@click="openFile('manage.log')"
|
||||||
/>
|
/>
|
||||||
<CustomNavCard
|
<CustomNavCard
|
||||||
:title="t('pages.settings.advanced.setLog')"
|
:title="t('pages.settings.advanced.setLog')"
|
||||||
@@ -902,24 +919,6 @@
|
|||||||
>
|
>
|
||||||
<div class="flex h-full w-full flex-col p-4">
|
<div class="flex h-full w-full flex-col p-4">
|
||||||
<SettingSection>
|
<SettingSection>
|
||||||
<CustomNavCard
|
|
||||||
:title="t('pages.settings.advanced.logFile')"
|
|
||||||
description="piclist.log"
|
|
||||||
:icon="FileText"
|
|
||||||
@click="openFile('piclist.log')"
|
|
||||||
/>
|
|
||||||
<CustomNavCard
|
|
||||||
:title="t('pages.settings.advanced.guiLogFile')"
|
|
||||||
description="piclist-gui-local.log"
|
|
||||||
:icon="FileText"
|
|
||||||
@click="openFile('piclist-gui-local.log')"
|
|
||||||
/>
|
|
||||||
<CustomNavCard
|
|
||||||
:title="t('pages.settings.advanced.manageLogFile')"
|
|
||||||
description="manage.log"
|
|
||||||
:icon="FileText"
|
|
||||||
@click="openFile('manage.log')"
|
|
||||||
/>
|
|
||||||
<CustomInput
|
<CustomInput
|
||||||
v-model="formOfSetting.logFileSizeLimit"
|
v-model="formOfSetting.logFileSizeLimit"
|
||||||
:title="t('pages.settings.advanced.logFileSize')"
|
:title="t('pages.settings.advanced.logFileSize')"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
class="flex w-full items-center justify-between gap-4 overflow-visible rounded-2xl border border-border-secondary px-6 py-2 shadow-md max-md:items-stretch max-md:p-5"
|
class="flex w-full items-center justify-between gap-4 overflow-visible rounded-2xl border border-border-secondary px-6 py-2 shadow-md max-md:items-stretch max-md:p-5"
|
||||||
>
|
>
|
||||||
<div class="flex flex-1 flex-wrap items-center gap-4 p-1">
|
<div class="flex flex-1 flex-wrap items-center gap-4 p-1">
|
||||||
<DatabaseIcon :size="24" class="text-accent" />
|
<PlugIcon :size="24" class="text-accent" />
|
||||||
<div>
|
<div>
|
||||||
<h1 class="m-0 text-2xl font-semibold tracking-tight text-main">{{ t('pages.plugin.title') }}</h1>
|
<h1 class="m-0 text-2xl font-semibold tracking-tight text-main">{{ t('pages.plugin.title') }}</h1>
|
||||||
<p class="m-0 text-sm text-secondary">{{ t('pages.plugin.description') }}</p>
|
<p class="m-0 text-sm text-secondary">{{ t('pages.plugin.description') }}</p>
|
||||||
@@ -407,10 +407,10 @@ import { debounce, DebouncedFunc } from 'lodash-es'
|
|||||||
import {
|
import {
|
||||||
AlertCircleIcon,
|
AlertCircleIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
DatabaseIcon,
|
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
ExternalLinkIcon,
|
ExternalLinkIcon,
|
||||||
PackageIcon,
|
PackageIcon,
|
||||||
|
PlugIcon,
|
||||||
RefreshCwIcon,
|
RefreshCwIcon,
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
@@ -835,9 +835,6 @@ onBeforeUnmount(() => {
|
|||||||
window.electron.ipcRendererRemoveAllListeners(PICGO_CONFIG_PLUGIN)
|
window.electron.ipcRendererRemoveAllListeners(PICGO_CONFIG_PLUGIN)
|
||||||
window.electron.ipcRendererRemoveAllListeners(PICGO_HANDLE_PLUGIN_ING)
|
window.electron.ipcRendererRemoveAllListeners(PICGO_HANDLE_PLUGIN_ING)
|
||||||
window.electron.ipcRendererRemoveAllListeners(PICGO_TOGGLE_PLUGIN)
|
window.electron.ipcRendererRemoveAllListeners(PICGO_TOGGLE_PLUGIN)
|
||||||
|
|
||||||
// Reset body overflow
|
|
||||||
document.body.style.overflow = 'auto'
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
430
src/renderer/pages/ScriptPage.vue
Normal file
430
src/renderer/pages/ScriptPage.vue
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
<template>
|
||||||
|
<div class="relative flex h-full w-full items-center justify-center">
|
||||||
|
<div class="relative z-1 flex h-full w-full flex-col items-center justify-start gap-4 rounded-xl border-none p-4">
|
||||||
|
<div
|
||||||
|
class="flex w-full items-center justify-between gap-4 overflow-visible rounded-2xl border border-border-secondary px-6 py-2 shadow-md max-md:items-stretch max-md:p-5"
|
||||||
|
>
|
||||||
|
<div class="flex flex-1 flex-wrap items-center gap-4 p-1">
|
||||||
|
<FileCode :size="24" class="text-accent" />
|
||||||
|
<div>
|
||||||
|
<h1 class="m-0 text-2xl font-semibold tracking-tight text-main">{{ t('pages.scripts.title') }}</h1>
|
||||||
|
<p class="m-0 text-sm text-secondary">{{ t('pages.scripts.description') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-3 overflow-visible">
|
||||||
|
<div class="flex max-w-[220px] min-w-[180px] flex-1 flex-col gap-1">
|
||||||
|
<MultiSelect
|
||||||
|
v-model:choosed="choosedCat"
|
||||||
|
:zero-placeholder="t('pages.scripts.chooseScriptType')"
|
||||||
|
:all-list="supportedScriptCategories"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CustomButton
|
||||||
|
type="primary"
|
||||||
|
:icon="FolderOpen"
|
||||||
|
:text="t('pages.scripts.openScriptFolder')"
|
||||||
|
@click="handleOpenScriptFolder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Plugin Grid -->
|
||||||
|
<div
|
||||||
|
class="relative flex h-full w-full flex-1 items-center justify-center overflow-hidden rounded-2xl border border-border-secondary p-4 shadow-md"
|
||||||
|
>
|
||||||
|
<div class="no-scrollbar h-full w-full overflow-auto rounded-sm">
|
||||||
|
<div class="grid w-full grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-5 border-none p-1 max-md:gap-4">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in scriptsList"
|
||||||
|
:key="item.fileName + index"
|
||||||
|
class="group/config-card relative flex min-h-[160px] cursor-pointer flex-col gap-6 overflow-hidden rounded-xl border border-border-secondary p-5 shadow-sm transition-all duration-fast ease-apple hover:border-2 hover:border-accent hover:shadow-md [.disabled]:opacity-80"
|
||||||
|
:class="{
|
||||||
|
disabled:
|
||||||
|
!item.enabled && item.category !== 'manualTrigger' && item.category !== 'uploader.advancedplist',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute right-1 bottom-0 flex h-[15px] w-auto items-center rounded-md bg-accent/70 px-2 py-1 text-xs font-semibold text-white"
|
||||||
|
>
|
||||||
|
{{ supportedScriptCategories.find(cat => cat.type === item.category)?.name || item.category }}
|
||||||
|
</div>
|
||||||
|
<div class="relative z-1 flex flex-1 items-start justify-between">
|
||||||
|
<div
|
||||||
|
class="peer flex h-[40px] w-[40px] items-center justify-center rounded-lg border border-border-secondary text-accent transition-all duration-fast ease-apple group-hover/config-card:scale-105 [.is-active]:border-none [.is-active]:bg-accent [.is-active]:text-white"
|
||||||
|
:class="{ 'is-active': item.enabled }"
|
||||||
|
>
|
||||||
|
<FileCode :size="20" />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-1.5 transition-all duration-fast ease-apple">
|
||||||
|
<button
|
||||||
|
class="action-btn"
|
||||||
|
:title="t('pages.scripts.editScript')"
|
||||||
|
@click.stop="openEditPage(item.filePath)"
|
||||||
|
>
|
||||||
|
<Pencil :size="14" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="action-btn danger"
|
||||||
|
:title="t('pages.scripts.deleteScript')"
|
||||||
|
@click.stop="() => deleteConfig(item.filePath)"
|
||||||
|
>
|
||||||
|
<Trash2 :size="14" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="item.category === 'manualTrigger'"
|
||||||
|
class="action-btn bg-accent/50 text-white!"
|
||||||
|
:title="t('pages.scripts.runScript')"
|
||||||
|
@click.stop="runScript(item.filePath)"
|
||||||
|
>
|
||||||
|
<Play :size="14" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="item.category !== 'manualTrigger' && item.category !== 'uploader.advancedplist'"
|
||||||
|
class="action-btn"
|
||||||
|
:title="item.enabled ? t('pages.scripts.disableScript') : t('pages.scripts.enableScript')"
|
||||||
|
@click.stop="toggleScript(item.filePath)"
|
||||||
|
>
|
||||||
|
<template v-if="!item.enabled">
|
||||||
|
<CheckCircle2 :size="14" />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<XIcon :size="14" />
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relative z-1 flex-1">
|
||||||
|
<div class="mx-0 mt-0 mb-2 flex items-center text-base font-semibold tracking-tight text-main">
|
||||||
|
{{ item.fileName }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 flex items-center gap-1.5 text-xs text-tertiary">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Clock :size="12" />
|
||||||
|
<span>{{ formatDate(item.mtimeMs) }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="item.enabled"
|
||||||
|
class="inline-flex items-center gap-1.5 rounded-2xl bg-accent/40 px-3 py-1.5 text-xs font-medium text-white transition-all duration-fast ease-standard"
|
||||||
|
>
|
||||||
|
<CheckCircle2 :size="15" />
|
||||||
|
<span>{{ t('pages.scripts.enabled') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="inline-flex items-center gap-1.5 rounded-2xl px-3 py-1.5 text-xs font-medium text-tertiary transition-all duration-fast ease-standard group-hover/config-card:bg-accent/10"
|
||||||
|
>
|
||||||
|
<span>{{ t('pages.scripts.disabled') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
key="add-new"
|
||||||
|
class="group/new relative flex min-h-[180px] cursor-pointer flex-col items-center justify-center gap-6 overflow-hidden rounded-xl border-2 border-dashed border-border p-5 shadow-sm transition-all duration-fast ease-apple hover:border-solid hover:border-accent hover:bg-surface hover:shadow-md"
|
||||||
|
@click="openNewScriptsNameDialog"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col items-center gap-3 transition-all duration-fast ease-apple">
|
||||||
|
<div
|
||||||
|
class="flex h-[56px] w-[56px] items-center justify-center rounded-xl border-2 border-dashed border-border text-tertiary transition-all duration-fast ease-apple group-hover/new:scale-105 group-hover/new:border-solid group-hover/new:border-accent group-hover/new:bg-accent/5 group-hover/new:text-accent"
|
||||||
|
>
|
||||||
|
<Plus :size="24" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center gap-1">
|
||||||
|
<span class="text-base font-semibold text-secondary">{{ t('pages.scripts.addNew') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CustomModal v-if="editorVisible" v-model:visible="editorVisible" :title="t('common.edit')">
|
||||||
|
<Editor v-model="editorContent" language="javascript" />
|
||||||
|
<template #footer>
|
||||||
|
<CustomButton type="secondary" :text="t('common.cancel')" @click="editorVisible = false" />
|
||||||
|
<CustomButton type="primary" :text="t('common.save')" @click="saveEditorContent" />
|
||||||
|
</template>
|
||||||
|
</CustomModal>
|
||||||
|
|
||||||
|
<CustomModal
|
||||||
|
v-if="newScriptNameVisible"
|
||||||
|
v-model:visible="newScriptNameVisible"
|
||||||
|
:title="t('pages.scripts.addNew')"
|
||||||
|
height="auto"
|
||||||
|
width="600px"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col items-center justify-center gap-4 bg-bg-secondary p-6">
|
||||||
|
<SettingCard class="w-full">
|
||||||
|
<SingleSelect
|
||||||
|
v-model="newScriptCategory"
|
||||||
|
:title="t('pages.scripts.selectScriptType')"
|
||||||
|
:key-list="supportedScriptCategories.map(cat => cat.type)"
|
||||||
|
:fronticon="false"
|
||||||
|
>
|
||||||
|
<template #item="{ item }">
|
||||||
|
{{
|
||||||
|
supportedScriptCategories.find(cat => cat.type === item)
|
||||||
|
? supportedScriptCategories.find(cat => cat.type === item)?.name
|
||||||
|
: item
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
</SingleSelect>
|
||||||
|
</SettingCard>
|
||||||
|
<SettingCard class="w-full">
|
||||||
|
<CustomInput
|
||||||
|
v-model="newScriptName"
|
||||||
|
:title="t('pages.scripts.pleaseEnterScriptName')"
|
||||||
|
placeholder="test.js"
|
||||||
|
/>
|
||||||
|
</SettingCard>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<CustomButton type="secondary" :text="t('common.cancel')" @click="newScriptNameVisible = false" />
|
||||||
|
<CustomButton type="primary" :text="t('common.confirm')" @click="handleNewScriptNameConfirm" />
|
||||||
|
</template>
|
||||||
|
</CustomModal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { CheckCircle2, Clock, FileCode, FolderOpen, Pencil, Play, Plus, Trash2, XIcon } from 'lucide-vue-next'
|
||||||
|
import { computed, onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import CustomButton from '@/components/common/CustomButton.vue'
|
||||||
|
import CustomInput from '@/components/common/CustomInput.vue'
|
||||||
|
import CustomModal from '@/components/common/CustomModal.vue'
|
||||||
|
import MultiSelect from '@/components/common/MultiSelect.vue'
|
||||||
|
import SettingCard from '@/components/common/SettingCard.vue'
|
||||||
|
import SingleSelect from '@/components/common/SingleSelect.vue'
|
||||||
|
import Editor from '@/components/Editor.vue'
|
||||||
|
import useConfirm from '@/hooks/useConfirm'
|
||||||
|
import useMessage from '@/hooks/useMessage'
|
||||||
|
import { getRawData } from '@/utils/common'
|
||||||
|
import { configPaths } from '@/utils/configPaths'
|
||||||
|
import { getConfig, saveConfig } from '@/utils/dataSender'
|
||||||
|
import { II18nLanguage, IRPCActionType } from '@/utils/enum'
|
||||||
|
import { defaultScriptTemplate, defaultScriptTemplateEn } from '@/utils/static'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const message = useMessage()
|
||||||
|
const { confirm } = useConfirm()
|
||||||
|
const scriptsMap = ref<Record<string, any>>({})
|
||||||
|
const choosedCat = ref<string[]>([])
|
||||||
|
const scriptsList = ref<IStringKeyMap[]>([])
|
||||||
|
const editorVisible = ref(false)
|
||||||
|
const editorContent = ref('')
|
||||||
|
const editingScriptName = ref<string[]>([])
|
||||||
|
const newScriptNameVisible = ref(false)
|
||||||
|
const newScriptName = ref('')
|
||||||
|
const newScriptCategory = ref('manualTrigger')
|
||||||
|
|
||||||
|
const supportedScriptCategories = [
|
||||||
|
{ type: 'onSoftwareOpen', name: t('pages.scripts.scriptsTypes.onSoftwareOpen') },
|
||||||
|
{ type: 'onSoftwareClose', name: t('pages.scripts.scriptsTypes.onSoftwareClose') },
|
||||||
|
{ type: 'preProcess', name: t('pages.scripts.scriptsTypes.preProcess') },
|
||||||
|
{ type: 'beforeTransform', name: t('pages.scripts.scriptsTypes.beforeTransform') },
|
||||||
|
{ type: 'transform', name: t('pages.scripts.scriptsTypes.transform') },
|
||||||
|
{ type: 'beforeUpload', name: t('pages.scripts.scriptsTypes.beforeUpload') },
|
||||||
|
{ type: 'upload', name: t('pages.scripts.scriptsTypes.upload') },
|
||||||
|
{ type: 'afterUpload', name: t('pages.scripts.scriptsTypes.afterUpload') },
|
||||||
|
{ type: 'onUploadSuccess', name: t('pages.scripts.scriptsTypes.onUploadSuccess') },
|
||||||
|
{ type: 'onGalleryRemove', name: t('pages.scripts.scriptsTypes.onGalleryRemove') },
|
||||||
|
{ type: 'manualTrigger', name: t('pages.scripts.scriptsTypes.manualTrigger') },
|
||||||
|
{ type: 'uploader.advancedplist', name: t('pages.scripts.scriptsTypes.uploader.advancedplist') },
|
||||||
|
]
|
||||||
|
|
||||||
|
const existingPathsSet = computed(() => {
|
||||||
|
return new Set(scriptsList.value.map(item => item.filePath.join('/')))
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(scriptsMap, async () => {
|
||||||
|
await refreshList()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(choosedCat, async () => {
|
||||||
|
await refreshList()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function refreshList() {
|
||||||
|
const result: string[][] = []
|
||||||
|
const keysToCheck = choosedCat.value.length > 0 ? choosedCat.value : supportedScriptCategories.map(cat => cat.type)
|
||||||
|
for (const key of keysToCheck) {
|
||||||
|
if (key.includes('.')) {
|
||||||
|
const parts = key.split('.')
|
||||||
|
const value = scriptsMap.value[parts[0]] ? scriptsMap.value[parts[0]][parts[1]] : undefined
|
||||||
|
if (value) {
|
||||||
|
Object.entries(value).forEach(([valueKey, item]: [string, any]) => {
|
||||||
|
if (item === null) {
|
||||||
|
result.push([parts[0], parts[1], valueKey])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const value = scriptsMap.value[key]
|
||||||
|
if (value) {
|
||||||
|
Object.entries(value).forEach(([valueKey, item]: [string, any]) => {
|
||||||
|
if (item === null) {
|
||||||
|
result.push([key, valueKey])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fileStats =
|
||||||
|
(await window.electron.triggerRPC<IObj[]>(IRPCActionType.GET_FILES_STAT, getRawData(result), 'scripts')) || []
|
||||||
|
const disabledList = ((await getConfig(configPaths.scripts.disabledList)) as string[] | undefined) || []
|
||||||
|
console.log('disabledList', disabledList)
|
||||||
|
fileStats.forEach(file => {
|
||||||
|
const fullPath = file.filePath.join('/')
|
||||||
|
file.enabled = !disabledList.includes(fullPath)
|
||||||
|
})
|
||||||
|
scriptsList.value = fileStats
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getScriptsMap() {
|
||||||
|
scriptsMap.value =
|
||||||
|
(await window.electron.triggerRPC<Record<string, any>>(IRPCActionType.LIST_SCRIPTS_FILES, [])) || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(timestamp: number) {
|
||||||
|
const date = dayjs(timestamp)
|
||||||
|
return date.format('YYYY/MM/DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTemplate() {
|
||||||
|
const lang = (await getConfig(configPaths.settings.language)) || II18nLanguage.ZH_CN
|
||||||
|
if (lang === II18nLanguage.ZH_CN || lang === II18nLanguage.ZH_TW) {
|
||||||
|
return defaultScriptTemplate
|
||||||
|
} else {
|
||||||
|
return defaultScriptTemplateEn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openEditPage(filePath: string[], mode: 'edit' | 'new' = 'edit') {
|
||||||
|
editingScriptName.value = filePath
|
||||||
|
if (mode === 'edit') {
|
||||||
|
const content =
|
||||||
|
(await window.electron.triggerRPC<string>(IRPCActionType.READ_SCRIPTS_FILE, getRawData(filePath))) || ''
|
||||||
|
editorContent.value = content
|
||||||
|
} else {
|
||||||
|
editorContent.value = await getTemplate()
|
||||||
|
}
|
||||||
|
editorVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveEditorContent() {
|
||||||
|
const content = editorContent.value.trim()
|
||||||
|
try {
|
||||||
|
window.electron.sendRPC(IRPCActionType.WRITE_SCRIPT_FILE, getRawData(editingScriptName.value), content)
|
||||||
|
message.success(t('pages.settings.advanced.saveFileSuccess'))
|
||||||
|
await getScriptsMap()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save file:', error)
|
||||||
|
message.error(t('pages.settings.advanced.saveFileFailed'))
|
||||||
|
}
|
||||||
|
editorVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteConfig(scriptPath: string[]) {
|
||||||
|
const result = await confirm({
|
||||||
|
title: t('pages.scripts.deleteScriptTitle'),
|
||||||
|
message: t('pages.scripts.deleteScriptConfirm', { name: scriptPath[scriptPath.length - 1] }),
|
||||||
|
type: 'warning',
|
||||||
|
confirmButtonText: t('common.confirm'),
|
||||||
|
cancelButtonText: t('common.cancel'),
|
||||||
|
center: true,
|
||||||
|
})
|
||||||
|
if (!result) return
|
||||||
|
try {
|
||||||
|
window.electron.sendRPC(IRPCActionType.DELETE_SCRIPTS_FILE, getRawData(scriptPath))
|
||||||
|
message.success(t('pages.scripts.deleteSuccess'))
|
||||||
|
await getScriptsMap()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete script file:', error)
|
||||||
|
message.error(t('pages.scripts.deleteFailed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpenScriptFolder() {
|
||||||
|
window.electron.sendRPC(IRPCActionType.PICLIST_OPEN_DIRECTORY, 'scripts', true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openNewScriptsNameDialog() {
|
||||||
|
newScriptName.value = ''
|
||||||
|
newScriptNameVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runScript(scriptPath: string[]) {
|
||||||
|
const result = await window.electron.triggerRPC(IRPCActionType.RUN_SCRIPT_FILE, getRawData(scriptPath))
|
||||||
|
if (result instanceof Error) {
|
||||||
|
const errorMessage = result.message || 'Unknown error'
|
||||||
|
message.error(`${t('pages.scripts.runScriptFailed', { errorMessage })}`)
|
||||||
|
} else {
|
||||||
|
message.success(t('pages.scripts.runScriptSuccess'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkDup(fullPath: string[]) {
|
||||||
|
return existingPathsSet.value.has(fullPath.join('/'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNewScriptNameConfirm() {
|
||||||
|
let trimmedName = newScriptName.value.trim()
|
||||||
|
trimmedName = trimmedName.endsWith('.js') ? trimmedName : `${trimmedName}.js`
|
||||||
|
if (!trimmedName) {
|
||||||
|
message.error(t('pages.scripts.pleaseEnterScriptName'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const scriptPath = newScriptCategory.value.includes('.')
|
||||||
|
? [...newScriptCategory.value.split('.'), trimmedName]
|
||||||
|
: [newScriptCategory.value, trimmedName]
|
||||||
|
if (checkDup(scriptPath)) {
|
||||||
|
message.error(t('pages.scripts.duplicateScriptNameError'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newScriptNameVisible.value = false
|
||||||
|
openEditPage(scriptPath, 'new')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleScript(scriptPath: string[]) {
|
||||||
|
const disabledList = ((await getConfig(configPaths.scripts.disabledList)) as string[] | undefined) || []
|
||||||
|
const fullPath = scriptPath.join('/')
|
||||||
|
if (disabledList.includes(fullPath)) {
|
||||||
|
const index = disabledList.indexOf(fullPath)
|
||||||
|
if (index > -1) {
|
||||||
|
disabledList.splice(index, 1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
disabledList.push(fullPath)
|
||||||
|
}
|
||||||
|
saveConfig(configPaths.scripts.disabledList, disabledList)
|
||||||
|
await getScriptsMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
getScriptsMap()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'ScriptPage',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@import 'tailwindcss' reference;
|
||||||
|
@import '../assets/css/theme.css' reference;
|
||||||
|
@import '../assets/css/utilities.css' reference;
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
@apply flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded-md border border-accent/20 text-secondary transition-all duration-fast ease-standard hover:scale-105 hover:bg-accent/30 hover:text-white disabled:cursor-not-allowed disabled:opacity-50 hover:not-disabled:[.danger]:border-danger hover:not-disabled:[.danger]:bg-danger;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -17,6 +17,58 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center gap-3">
|
<div class="flex items-center justify-center gap-3">
|
||||||
|
<div class="relative">
|
||||||
|
<CustomButton
|
||||||
|
v-if="type === 'advancedplist'"
|
||||||
|
type="secondary"
|
||||||
|
:text="t('pages.scripts.editScript')"
|
||||||
|
@click="openScriptsList"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="scriptsListVisible"
|
||||||
|
class="absolute top-full left-1/2 z-10 mt-2 w-max -translate-x-1/2 gap-2 rounded-md border-2 border-border bg-bg-tertiary px-3 py-1.5 text-sm font-medium text-main shadow-md transition-all duration-fast ease-apple"
|
||||||
|
>
|
||||||
|
<div class="no-scrollbar flex max-h-[200px] min-w-[150px] flex-col overflow-auto">
|
||||||
|
<div
|
||||||
|
v-for="script in scriptsList"
|
||||||
|
:key="script"
|
||||||
|
class="cursor-pointer rounded-md border-b border-border px-2 py-1 text-center whitespace-nowrap last:border-b-0 hover:bg-accent/20"
|
||||||
|
@click="handleScriptClick(script)"
|
||||||
|
>
|
||||||
|
{{ script }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CustomButton
|
||||||
|
v-if="type === 'advancedplist'"
|
||||||
|
type="primary"
|
||||||
|
:text="t('pages.scripts.createScript')"
|
||||||
|
@click="openNewScriptsNameDialog"
|
||||||
|
/>
|
||||||
|
<div class="relative">
|
||||||
|
<CustomButton
|
||||||
|
v-if="type === 'advancedplist'"
|
||||||
|
type="secondary"
|
||||||
|
:text="t('pages.scripts.deleteScript')"
|
||||||
|
@click="openDeleteScriptsList"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="deleteScriptListVisible"
|
||||||
|
class="absolute top-full left-1/2 z-10 mt-2 w-max -translate-x-1/2 gap-2 rounded-md border-2 border-border bg-bg-tertiary px-3 py-1.5 text-sm font-medium text-main shadow-md transition-all duration-fast ease-apple"
|
||||||
|
>
|
||||||
|
<div class="no-scrollbar flex max-h-[200px] min-w-[150px] flex-col overflow-auto">
|
||||||
|
<div
|
||||||
|
v-for="script in scriptsList"
|
||||||
|
:key="script"
|
||||||
|
class="cursor-pointer rounded-md border-b border-border px-2 py-1 text-center whitespace-nowrap last:border-b-0 hover:bg-accent/20"
|
||||||
|
@click="deleteScript(script)"
|
||||||
|
>
|
||||||
|
{{ script }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
class="relative inline-flex cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-lg border-none bg-accent px-6 py-3 font-[inherit] text-sm font-semibold text-white shadow-sm transition-all duration-fast ease-apple disabled:cursor-not-allowed disabled:bg-surface disabled:text-secondary disabled:opacity-60"
|
class="relative inline-flex cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-lg border-none bg-accent px-6 py-3 font-[inherit] text-sm font-semibold text-white shadow-sm transition-all duration-fast ease-apple disabled:cursor-not-allowed disabled:bg-surface disabled:text-secondary disabled:opacity-60"
|
||||||
:disabled="defaultPicBedG === type"
|
:disabled="defaultPicBedG === type"
|
||||||
@@ -39,7 +91,7 @@
|
|||||||
<div
|
<div
|
||||||
v-for="(item, index) in curConfigList"
|
v-for="(item, index) in curConfigList"
|
||||||
:key="item._id"
|
:key="item._id"
|
||||||
class="group/config-card relative flex min-h-[180px] cursor-pointer flex-col gap-6 overflow-hidden rounded-xl border border-border-secondary p-5 shadow-sm transition-all duration-fast ease-apple hover:border-accent hover:shadow-md [.is-active]:border-2 [.is-active]:border-accent [.is-active]:shadow-md"
|
class="group/config-card relative flex min-h-[180px] cursor-pointer flex-col gap-6 overflow-hidden rounded-xl border border-border-secondary p-5 shadow-sm transition-all duration-fast ease-apple hover:border-2 hover:border-accent hover:shadow-md [.is-active]:border-2 [.is-active]:border-accent [.is-active]:shadow-md"
|
||||||
:class="{ 'is-active': defaultConfigId === item._id }"
|
:class="{ 'is-active': defaultConfigId === item._id }"
|
||||||
:style="{ '--delay': `${index * 50}ms` }"
|
:style="{ '--delay': `${index * 50}ms` }"
|
||||||
@click="() => selectItem(item._id)"
|
@click="() => selectItem(item._id)"
|
||||||
@@ -135,6 +187,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<CustomModal v-if="editorVisible" v-model:visible="editorVisible" :title="t('common.edit')">
|
||||||
|
<Editor v-model="editorContent" language="javascript" />
|
||||||
|
<template #footer>
|
||||||
|
<CustomButton type="secondary" :text="t('common.cancel')" @click="editorVisible = false" />
|
||||||
|
<CustomButton type="primary" :text="t('common.save')" @click="saveEditorContent" />
|
||||||
|
</template>
|
||||||
|
</CustomModal>
|
||||||
|
|
||||||
|
<CustomModal
|
||||||
|
v-if="newScriptNameVisible"
|
||||||
|
v-model:visible="newScriptNameVisible"
|
||||||
|
:title="t('pages.scripts.addNew')"
|
||||||
|
height="auto"
|
||||||
|
width="400px"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-center bg-bg-secondary p-6">
|
||||||
|
<SettingCard class="w-full">
|
||||||
|
<CustomInput
|
||||||
|
v-model="newScriptName"
|
||||||
|
:title="t('pages.scripts.pleaseEnterScriptName')"
|
||||||
|
placeholder="test.js"
|
||||||
|
/>
|
||||||
|
</SettingCard>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<CustomButton type="secondary" :text="t('common.cancel')" @click="newScriptNameVisible = false" />
|
||||||
|
<CustomButton type="primary" :text="t('common.confirm')" @click="handleNewScriptNameConfirm" />
|
||||||
|
</template>
|
||||||
|
</CustomModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -146,6 +228,11 @@ import { computed, onBeforeMount, ref } from 'vue'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
|
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import CustomButton from '@/components/common/CustomButton.vue'
|
||||||
|
import CustomInput from '@/components/common/CustomInput.vue'
|
||||||
|
import CustomModal from '@/components/common/CustomModal.vue'
|
||||||
|
import SettingCard from '@/components/common/SettingCard.vue'
|
||||||
|
import Editor from '@/components/Editor.vue'
|
||||||
import useConfirm from '@/hooks/useConfirm'
|
import useConfirm from '@/hooks/useConfirm'
|
||||||
import { usePicBed } from '@/hooks/useGlobal'
|
import { usePicBed } from '@/hooks/useGlobal'
|
||||||
import useMessage from '@/hooks/useMessage'
|
import useMessage from '@/hooks/useMessage'
|
||||||
@@ -153,8 +240,9 @@ import { PICBEDS_PAGE, UPLOADER_CONFIG_PAGE } from '@/router/config'
|
|||||||
import $bus from '@/utils/bus'
|
import $bus from '@/utils/bus'
|
||||||
import { configPaths } from '@/utils/configPaths'
|
import { configPaths } from '@/utils/configPaths'
|
||||||
import { SHOW_INPUT_BOX, SHOW_INPUT_BOX_RESPONSE } from '@/utils/constant'
|
import { SHOW_INPUT_BOX, SHOW_INPUT_BOX_RESPONSE } from '@/utils/constant'
|
||||||
import { saveConfig } from '@/utils/dataSender'
|
import { getConfig, saveConfig } from '@/utils/dataSender'
|
||||||
import { IRPCActionType } from '@/utils/enum'
|
import { II18nLanguage, IRPCActionType } from '@/utils/enum'
|
||||||
|
import { defaultScriptTemplate, defaultScriptTemplateEn } from '@/utils/static'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@@ -166,6 +254,14 @@ const favoritePicbeds = useStorage<IFavoritePicbedItem[]>('favorite-picbeds', []
|
|||||||
const type = ref('')
|
const type = ref('')
|
||||||
const curConfigList = ref<IStringKeyMap[]>([])
|
const curConfigList = ref<IStringKeyMap[]>([])
|
||||||
const defaultConfigId = ref('')
|
const defaultConfigId = ref('')
|
||||||
|
const scriptsListVisible = ref(false)
|
||||||
|
const scriptsList = ref<string[]>([])
|
||||||
|
const editorVisible = ref(false)
|
||||||
|
const editorContent = ref('')
|
||||||
|
const editingScriptName = ref('')
|
||||||
|
const newScriptNameVisible = ref(false)
|
||||||
|
const newScriptName = ref('')
|
||||||
|
const deleteScriptListVisible = ref(false)
|
||||||
|
|
||||||
const picBedName = computed(() => {
|
const picBedName = computed(() => {
|
||||||
if (!picBedG.value || picBedG.value.length === 0) {
|
if (!picBedG.value || picBedG.value.length === 0) {
|
||||||
@@ -316,6 +412,122 @@ function setDefaultPicBed(type: string) {
|
|||||||
message.success(t('pages.uploaderConfig.setSuccess'))
|
message.success(t('pages.uploaderConfig.setSuccess'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getScriptsList() {
|
||||||
|
const scriptsFiles = await window.electron.triggerRPC<Record<string, any>>(IRPCActionType.LIST_SCRIPTS_FILES, [
|
||||||
|
'uploader',
|
||||||
|
'advancedplist',
|
||||||
|
])
|
||||||
|
scriptsList.value = Object.keys(scriptsFiles || {}).filter(fileName => fileName.endsWith('.js'))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openScriptsList() {
|
||||||
|
if (scriptsListVisible.value) {
|
||||||
|
scriptsListVisible.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await getScriptsList()
|
||||||
|
if (scriptsList.value.length === 0) {
|
||||||
|
message.info(t('pages.scripts.noScriptsFound'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scriptsListVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function openNewScriptsNameDialog() {
|
||||||
|
newScriptName.value = ''
|
||||||
|
newScriptNameVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTemplate() {
|
||||||
|
const lang = (await getConfig(configPaths.settings.language)) || II18nLanguage.ZH_CN
|
||||||
|
if (lang === II18nLanguage.ZH_CN || lang === II18nLanguage.ZH_TW) {
|
||||||
|
return defaultScriptTemplate
|
||||||
|
} else {
|
||||||
|
return defaultScriptTemplateEn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openEditScripts(scriptName: string, mode: 'edit' | 'new' = 'edit') {
|
||||||
|
editingScriptName.value = scriptName
|
||||||
|
if (mode === 'edit') {
|
||||||
|
const filePath = ['uploader', 'advancedplist', editingScriptName.value]
|
||||||
|
const content = (await window.electron.triggerRPC<string>(IRPCActionType.READ_SCRIPTS_FILE, filePath)) || ''
|
||||||
|
editorContent.value = content
|
||||||
|
} else {
|
||||||
|
editorContent.value = await getTemplate()
|
||||||
|
}
|
||||||
|
editorVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveEditorContent() {
|
||||||
|
const file = ['uploader', 'advancedplist', editingScriptName.value]
|
||||||
|
const content = editorContent.value.trim()
|
||||||
|
try {
|
||||||
|
window.electron.sendRPC(IRPCActionType.WRITE_SCRIPT_FILE, file, content)
|
||||||
|
message.success(t('pages.settings.advanced.saveFileSuccess'))
|
||||||
|
await getScriptsList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save file:', error)
|
||||||
|
message.error(t('pages.settings.advanced.saveFileFailed'))
|
||||||
|
}
|
||||||
|
editorVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNewScriptNameConfirm() {
|
||||||
|
let trimmedName = newScriptName.value.trim()
|
||||||
|
trimmedName = trimmedName.endsWith('.js') ? trimmedName : `${trimmedName}.js`
|
||||||
|
if (!trimmedName) {
|
||||||
|
message.error(t('pages.scripts.pleaseEnterScriptName'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (scriptsList.value.includes(trimmedName)) {
|
||||||
|
message.error(t('pages.scripts.duplicateScriptNameError'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newScriptNameVisible.value = false
|
||||||
|
openEditScripts(trimmedName, 'new')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleScriptClick(scriptName: string) {
|
||||||
|
scriptsListVisible.value = false
|
||||||
|
openEditScripts(scriptName)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDeleteScriptsList() {
|
||||||
|
if (deleteScriptListVisible.value) {
|
||||||
|
deleteScriptListVisible.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
getScriptsList().then(() => {
|
||||||
|
if (scriptsList.value.length === 0) {
|
||||||
|
message.info(t('pages.scripts.noScriptsFound'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
deleteScriptListVisible.value = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteScript(scriptName: string) {
|
||||||
|
const result = await confirm({
|
||||||
|
title: t('pages.scripts.deleteScriptTitle'),
|
||||||
|
message: t('pages.scripts.deleteScriptConfirm', { name: scriptName }),
|
||||||
|
type: 'warning',
|
||||||
|
confirmButtonText: t('common.confirm'),
|
||||||
|
cancelButtonText: t('common.cancel'),
|
||||||
|
center: true,
|
||||||
|
})
|
||||||
|
if (!result) return
|
||||||
|
try {
|
||||||
|
const filePath = ['uploader', 'advancedplist', scriptName]
|
||||||
|
window.electron.sendRPC(IRPCActionType.DELETE_SCRIPTS_FILE, filePath)
|
||||||
|
message.success(t('pages.scripts.deleteSuccess'))
|
||||||
|
await getScriptsList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete script file:', error)
|
||||||
|
message.error(t('pages.scripts.deleteFailed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeRouteUpdate((to, _, next) => {
|
onBeforeRouteUpdate((to, _, next) => {
|
||||||
if (to.params.type && to.name === UPLOADER_CONFIG_PAGE) {
|
if (to.params.type && to.name === UPLOADER_CONFIG_PAGE) {
|
||||||
type.value = to.params.type as string
|
type.value = to.params.type as string
|
||||||
@@ -327,6 +539,7 @@ onBeforeRouteUpdate((to, _, next) => {
|
|||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
type.value = route.params.type as string
|
type.value = route.params.type as string
|
||||||
getCurrentConfigList()
|
getCurrentConfigList()
|
||||||
|
getScriptsList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ export const UPDATE_PAGE = 'UpdatePage'
|
|||||||
export const UPLOAD_PAGE = 'UploadPage'
|
export const UPLOAD_PAGE = 'UploadPage'
|
||||||
export const UPLOADER_CONFIG_PAGE = 'UploaderConfigPage'
|
export const UPLOADER_CONFIG_PAGE = 'UploaderConfigPage'
|
||||||
export const MANAGE_EDIT_PAGE = 'ManageEditPage'
|
export const MANAGE_EDIT_PAGE = 'ManageEditPage'
|
||||||
|
export const SCRIPT_PAGE = 'ScriptPage'
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import PicBedsPage from '@/pages/PicBed.vue'
|
|||||||
import SettingPage from '@/pages/PicGoSetting.vue'
|
import SettingPage from '@/pages/PicGoSetting.vue'
|
||||||
import PluginPage from '@/pages/Plugin.vue'
|
import PluginPage from '@/pages/Plugin.vue'
|
||||||
import RenamePage from '@/pages/RenamePage.vue'
|
import RenamePage from '@/pages/RenamePage.vue'
|
||||||
|
import ScriptPage from '@/pages/ScriptPage.vue'
|
||||||
import ShortKeyPage from '@/pages/ShortKey.vue'
|
import ShortKeyPage from '@/pages/ShortKey.vue'
|
||||||
import Toolbox from '@/pages/Toolbox.vue'
|
import Toolbox from '@/pages/Toolbox.vue'
|
||||||
import TrayPage from '@/pages/TrayPage.vue'
|
import TrayPage from '@/pages/TrayPage.vue'
|
||||||
@@ -104,6 +105,11 @@ export default createRouter({
|
|||||||
component: PluginPage,
|
component: PluginPage,
|
||||||
name: config.PLUGIN_PAGE,
|
name: config.PLUGIN_PAGE,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'scripts',
|
||||||
|
component: ScriptPage,
|
||||||
|
name: config.SCRIPT_PAGE,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'shortKey',
|
path: 'shortKey',
|
||||||
component: ShortKeyPage,
|
component: ShortKeyPage,
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ export interface IConfigStruct {
|
|||||||
needReload: boolean
|
needReload: boolean
|
||||||
picgoPlugins: IPicGoPlugins
|
picgoPlugins: IPicGoPlugins
|
||||||
uploader: IUploaderConfig
|
uploader: IUploaderConfig
|
||||||
|
scripts: {
|
||||||
|
disabledList: string[]
|
||||||
|
}
|
||||||
buildIn: {
|
buildIn: {
|
||||||
compress: IBuildInCompressOptions
|
compress: IBuildInCompressOptions
|
||||||
watermark: IBuildInWaterMarkOptions
|
watermark: IBuildInWaterMarkOptions
|
||||||
@@ -189,6 +192,9 @@ export const configPaths = {
|
|||||||
needReload: 'needReload',
|
needReload: 'needReload',
|
||||||
picgoPlugins: 'picgoPlugins',
|
picgoPlugins: 'picgoPlugins',
|
||||||
uploader: 'uploader',
|
uploader: 'uploader',
|
||||||
|
scripts: {
|
||||||
|
disabledList: 'scripts.disabledList',
|
||||||
|
},
|
||||||
buildIn: {
|
buildIn: {
|
||||||
_name: 'buildIn',
|
_name: 'buildIn',
|
||||||
compress: 'buildIn.compress',
|
compress: 'buildIn.compress',
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const IRPCActionType = {
|
|||||||
OPEN_WINDOW: 'OPEN_WINDOW',
|
OPEN_WINDOW: 'OPEN_WINDOW',
|
||||||
OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW',
|
OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW',
|
||||||
CLOSE_WINDOW: 'CLOSE_WINDOW',
|
CLOSE_WINDOW: 'CLOSE_WINDOW',
|
||||||
|
RELOAD_WINDOW: 'RELOAD_WINDOW',
|
||||||
MINIMIZE_WINDOW: 'MINIMIZE_WINDOW',
|
MINIMIZE_WINDOW: 'MINIMIZE_WINDOW',
|
||||||
SHOW_MINI_PAGE_MENU: 'SHOW_MINI_PAGE_MENU',
|
SHOW_MINI_PAGE_MENU: 'SHOW_MINI_PAGE_MENU',
|
||||||
SHOW_MAIN_PAGE_MENU: 'SHOW_MAIN_PAGE_MENU',
|
SHOW_MAIN_PAGE_MENU: 'SHOW_MAIN_PAGE_MENU',
|
||||||
@@ -41,6 +42,7 @@ export const IRPCActionType = {
|
|||||||
MAIN_WINDOW_ON_TOP: 'MAIN_WINDOW_ON_TOP',
|
MAIN_WINDOW_ON_TOP: 'MAIN_WINDOW_ON_TOP',
|
||||||
UPDATE_MINI_WINDOW_ICON: 'UPDATE_MINI_WINDOW_ICON',
|
UPDATE_MINI_WINDOW_ICON: 'UPDATE_MINI_WINDOW_ICON',
|
||||||
REFRESH_SETTING_WINDOW: 'REFRESH_SETTING_WINDOW',
|
REFRESH_SETTING_WINDOW: 'REFRESH_SETTING_WINDOW',
|
||||||
|
|
||||||
// picbed RPC
|
// picbed RPC
|
||||||
PICBED_GET_PICBED_CONFIG: 'PICBED_GET_PICBED_CONFIG',
|
PICBED_GET_PICBED_CONFIG: 'PICBED_GET_PICBED_CONFIG',
|
||||||
PICBED_GET_CONFIG_LIST: 'PICBED_GET_CONFIG_LIST',
|
PICBED_GET_CONFIG_LIST: 'PICBED_GET_CONFIG_LIST',
|
||||||
@@ -51,6 +53,8 @@ export const IRPCActionType = {
|
|||||||
UPLOADER_UPDATE_CONFIG: 'UPLOADER_UPDATE_CONFIG',
|
UPLOADER_UPDATE_CONFIG: 'UPLOADER_UPDATE_CONFIG',
|
||||||
UPLOADER_RESET_CONFIG: 'UPLOADER_RESET_CONFIG',
|
UPLOADER_RESET_CONFIG: 'UPLOADER_RESET_CONFIG',
|
||||||
DELETE_ALL_API: 'DELETE_ALL_API',
|
DELETE_ALL_API: 'DELETE_ALL_API',
|
||||||
|
GET_FILES_STAT: 'GET_FILES_STAT',
|
||||||
|
RUN_SCRIPT_FILE: 'RUN_SCRIPT_FILE',
|
||||||
|
|
||||||
// toolbox rpc
|
// toolbox rpc
|
||||||
TOOLBOX_CHECK: 'TOOLBOX_CHECK',
|
TOOLBOX_CHECK: 'TOOLBOX_CHECK',
|
||||||
@@ -65,9 +69,15 @@ export const IRPCActionType = {
|
|||||||
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
|
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
|
||||||
PICLIST_AUTO_START: 'PICLIST_AUTO_START',
|
PICLIST_AUTO_START: 'PICLIST_AUTO_START',
|
||||||
PICLIST_AUTO_START_STATUS: 'PICLIST_AUTO_START_STATUS',
|
PICLIST_AUTO_START_STATUS: 'PICLIST_AUTO_START_STATUS',
|
||||||
|
|
||||||
|
// file operation rpc
|
||||||
READ_FILE_CONTENT: 'READ_FILE_CONTENT',
|
READ_FILE_CONTENT: 'READ_FILE_CONTENT',
|
||||||
WRITE_FILE_CONTENT: 'WRITE_FILE_CONTENT',
|
WRITE_FILE_CONTENT: 'WRITE_FILE_CONTENT',
|
||||||
RELOAD_WINDOW: 'RELOAD_WINDOW',
|
CREATE_SCRIPTS_FILE: 'CREATE_SCRIPTS_FILE',
|
||||||
|
READ_SCRIPTS_FILE: 'READ_SCRIPTS_FILE',
|
||||||
|
LIST_SCRIPTS_FILES: 'LIST_SCRIPTS_FILES',
|
||||||
|
WRITE_SCRIPT_FILE: 'WRITE_SCRIPT_FILE',
|
||||||
|
DELETE_SCRIPTS_FILE: 'DELETE_SCRIPTS_FILE',
|
||||||
|
|
||||||
// shortkey setting rpc
|
// shortkey setting rpc
|
||||||
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',
|
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',
|
||||||
|
|||||||
@@ -67,3 +67,25 @@ export const picBedManualUrlList: IStringKeyMap = {
|
|||||||
webdavplist: 'https://piclist.cn/en/configure.html#webdav',
|
webdavplist: 'https://piclist.cn/en/configure.html#webdav',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const defaultScriptTemplate = `
|
||||||
|
// ctx 为 核心PicList实例, extra为额外参数, 其中extra.galleryItem为当前删除的相册对象
|
||||||
|
// 可用额外API: axios, crypto, fs, path, os, setTimeout, setInterval, clearTimeout, clearInterval, base64Decode, base64Encode
|
||||||
|
// 图床上传脚本必须返回 ctx 对象, 其它脚本可根据需求返回任意数据
|
||||||
|
|
||||||
|
async function main(ctx, extra) {
|
||||||
|
// 在这里编写你的脚本代码
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const defaultScriptTemplateEn = `
|
||||||
|
// ctx is the core PicList instance, extra is additional parameters, among which extra.galleryItem is the currently deleted album object
|
||||||
|
// Available additional APIs: axios, crypto, fs, path, os, setTimeout, setInterval, clearTimeout, clearInterval, base64Decode, base64Encode
|
||||||
|
// The image bed upload script must return the ctx object, other scripts can return any data as needed
|
||||||
|
|
||||||
|
async function main(ctx, extra) {
|
||||||
|
// Write your script code here
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
19
src/universal/types/types.d.ts
vendored
19
src/universal/types/types.d.ts
vendored
@@ -528,3 +528,22 @@ interface IFavoritePicbedItem {
|
|||||||
type: string
|
type: string
|
||||||
configName: string
|
configName: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IuploadReturnCtxResult {
|
||||||
|
ctx: import('piclist').IPicGo | undefined
|
||||||
|
backupCtx: import('piclist').IPicGo | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
type IScriptLifecycle =
|
||||||
|
| 'onSoftwareOpen'
|
||||||
|
| 'onSoftwareClose'
|
||||||
|
| 'preProcess'
|
||||||
|
| 'beforeTransform'
|
||||||
|
| 'transform'
|
||||||
|
| 'beforeUpload'
|
||||||
|
| 'upload'
|
||||||
|
| 'afterUpload'
|
||||||
|
| 'onUploadSuccess'
|
||||||
|
| 'onGalleryRemove'
|
||||||
|
| 'manualTrigger'
|
||||||
|
| 'uploader.advancedplist'
|
||||||
|
|||||||
Reference in New Issue
Block a user