diff --git a/CHANGELOG.md b/CHANGELOG.md index fe3a975..8e75f4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +## :tada: 2.9.5 (2024-11-16) + + +### :sparkles: Features + +* **custom:** add support for sink ([b843278](https://github.com/Kuingsmile/piclist/commit/b843278)), closes [#254](https://github.com/Kuingsmile/piclist/issues/254) +* **custom:** optimize short url ([fd5316a](https://github.com/Kuingsmile/piclist/commit/fd5316a)), closes [#252](https://github.com/Kuingsmile/piclist/issues/252) +* **custom:** support use presigned url for image preview ([4209838](https://github.com/Kuingsmile/piclist/commit/4209838)), closes [#265](https://github.com/Kuingsmile/piclist/issues/265) + + +### :bug: Bug Fixes + +* **custom:** await RPC call for download directory selection ([2079faa](https://github.com/Kuingsmile/piclist/commit/2079faa)) +* **custom:** quality must be an int between 1-100 ([cd48b24](https://github.com/Kuingsmile/piclist/commit/cd48b24)) + + +### :pencil: Documentation + +* **custom:** prepare for 2.9.5 ([c5aaa37](https://github.com/Kuingsmile/piclist/commit/c5aaa37)) +* **custom:** update FAQ ([a9eed2d](https://github.com/Kuingsmile/piclist/commit/a9eed2d)) + + + ## :tada: 2.9.4 (2024-10-22) diff --git a/FAQ.md b/FAQ.md index 7334224..ec86dda 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,20 +1,20 @@ # FAQ -该FAQ修改自PicGo的FAQ,感谢PicGo的作者Molunerfinn。 +本 FAQ 修改自 PicGo 的 FAQ,感谢 PicGo 的作者 Molunerfinn。 ## 常见问题 > 常规配置问题请参考 [官方文档](https://piclist.cn) -## 1. PicList和PicGo有什么关系? +## 1. PicList 和 PicGo 有什么关系? -PicList项目fork自PicGo项目,基于PicGo进行了二次开发,同时核心功能内核PicGo-Core也进行了二次开发,重命名为[PicList-Core](https://github.com/Kuingsmile/PicList-Core)。 +PicList 项目是从 PicGo 项目 fork 而来,基于 PicGo 进行了二次开发。同时,核心功能内核 PicGo-Core 也进行了二次开发,并重命名为 [PicList-Core](https://github.com/Kuingsmile/PicList-Core)。 -PicList所有新功能的添加没有影响到PicGo的原有功能,所以你可以在PicList中使用PicGo的大部分插件。同时仍然可以配合typora、obsidian等软件进行使用。 +PicList 添加的所有新功能未影响 PicGo 的原有功能,因此你可以在 PicList 中使用 PicGo 的大部分插件。同时,仍然可以配合 Typora、Obsidian 等软件使用。 ## 2. 使用图床管理功能时,出现无法获取目录等错误 -请查看日志文件 `manage.log`,此外,各平台的API调用基本都有每小时次数限制,如果出现错误,请稍后再试。 +请查看日志文件 `manage.log`。此外,各平台的 API 调用基本都有每小时次数限制,如果出现错误,请稍后再试。 ## 3. 支持哪些图床远端同步删除 @@ -40,7 +40,7 @@ PicList所有新功能的添加没有影响到PicGo的原有功能,所以你 可以,通过新添加的图床管理功能,你可以上传任意格式的文件,包括视频文件。同时,在管理界面内上传时,使用分片上传/流式上传等方式,相对于PicGo内置的转换为base64的方式,上传更快,更稳定。 -## 5. 能否支持某某某图床 +## 5. 能否支持xxx图床 PicList本体支持了如下图床: @@ -57,6 +57,7 @@ PicList本体支持了如下图床: - `SFTP` - `兰空图床` - `PicList(套娃)` +- `高级自定义图床` PicList计划整合和优化现有插件,内置更多的常用图床。 @@ -91,7 +92,7 @@ GitHub 服务器和国内 GFW 的问题会导致有时上传成功,有时上 ## 11. macOS系统安装完PicList显示「文件已损坏」或者安装完打开没有反应 -请升级PicList 1.4.1或以上版本,自1.4.1开始,PicList已经经过Apple的签名,不会再出现这种情况。 +请升级至 PicList 1.4.1 或以上版本。 ## 12. 水印没有正常添加 diff --git a/FAQ_EN.md b/FAQ_EN.md index 06d60cb..abc13e1 100644 --- a/FAQ_EN.md +++ b/FAQ_EN.md @@ -57,6 +57,7 @@ PicList itself supports the following image hosting platforms: - SFTP - Lsky Pro - PicList (nested) +- Advanced custom image hosting PicList plans to integrate and optimize existing plugins and embed more commonly used image hosting platforms. @@ -92,7 +93,7 @@ Or right-click on the icon of PicList in the Docker bar to find the menu of "Ope ## 11. After installing PicList on macOS, it shows "The file is damaged" or there is no response after installing and opening -Please upgrade PicList to version 1.4.1 or above. Starting from 1.4.1, PicList has been signed by Apple and will not have this problem again. +Please upgrade PicList to version 1.4.1 or above. ## 12. Watermark is not added normally diff --git a/currentVersion.md b/currentVersion.md index d3fa846..75bf770 100644 --- a/currentVersion.md +++ b/currentVersion.md @@ -1,10 +1,15 @@ ### ✨ Features -- 优化了第二图床的上传逻辑,现在会使用相同文件名和压缩方式 -- 移除了telegra.ph图床(官方现已关闭匿名上传功能) -- 默认上传快捷键修改为`Ctrl+Alt+U`,避免与vscode命令面板冲突 +- 优化了短链接处理逻辑: + - 现在相册界面中复制链接时会得到相同的短链接,而不是每次重新获取 + - 现在开启短链接时上传结果通知会正确显示 + - 现在开启短链接时上传接口会返回短链接 +- 管理界面新增使用预签名链接显示预览图片选项 +- 现在图片压缩质量只允许设置1-100之间的正整数 +- 新增对短链项目[Sink](https://github.com/ccbikai/Sink)的支持 ### 🐛 Bug Fixes -- 修复了webdav图床链接拼接错误的问题 -- 修复了开启云删除时部分第三方图床图片无法批量删除的问题 +- 修复了管理页面设置项界面中,部分没有tooltip的选项依旧会显示图标的问题 +- 修复了在管理页面无法修改下载文件夹的问题 +- 修复了对HEIC图片无法转换格式的问题 diff --git a/currentVersion_en.md b/currentVersion_en.md index 499e314..48864ac 100644 --- a/currentVersion_en.md +++ b/currentVersion_en.md @@ -1,10 +1,15 @@ ### ✨ Features - -- Optimize the upload logic of the second image bed, now it will use the same file name and compression method -- Removed telegra.ph image bed (officially closed anonymous upload function) -- The default upload shortcut key is changed to `Ctrl+Alt+U` to avoid conflicts with the vscode command panel + +- Optimized the short link processing logic: + - Now when copying links in the album interface, the same short link will be obtained, instead of reacquiring it each time + - Now the upload result notification will be displayed correctly when the short link is enabled + - Now the upload interface will return a short link when the short link is enabled +- Added the option to display preview images using presigned links in the management interface +- Now the image compression quality only allows setting positive integers between 1-100 +- Added support for the short link project [Sink](https://github.com/ccbikai/Sink) ### 🐛 Bug Fixes -- Fixed the problem of incorrect splicing of webdav image bed links -- Fixed the problem that some third-party image bed pictures cannot be deleted in batches when cloud deletion is enabled +- Fixed the issue where some options without tooltips in the management page settings interface would still display icons +- Fixed the issue where the download folder could not be modified in the management page +- Fixed the issue where HEIC images could not be converted diff --git a/package.json b/package.json index 81554fe..70cdc0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "piclist", - "version": "2.9.4", + "version": "2.9.5", "author": { "name": "Kuingsmile", "email": "pkukuing@gmail.com" @@ -67,7 +67,7 @@ "multer": "^1.4.5-lts.1", "node-ssh-no-cpu-features": "^2.0.0", "nodejs-file-downloader": "^4.12.1", - "piclist": "^1.9.6", + "piclist": "^1.9.7", "pinia": "^2.1.7", "pinia-plugin-persistedstate": "^3.2.1", "proxy-agent": "^5.0.0", diff --git a/public/i18n/en.yml b/public/i18n/en.yml index 888250a..641ccc4 100644 --- a/public/i18n/en.yml +++ b/public/i18n/en.yml @@ -121,7 +121,7 @@ UPLOAD_PAGE_IMAGE_PROCESS_WMCOLOR: Watermark Color, Please select from the color UPLOAD_PAGE_IMAGE_PROCESS_WMPATH: Watermark Image Path (leave blank to use default image) UPLOAD_PAGE_IMAGE_PROCESS_WMPOSITION: Watermark Position UPLOAD_PAGE_IMAGE_PROCESS_ISREMOVEEXIF: Remove EXIF Info -UPLOAD_PAGE_IMAGE_PROCESS_QUALITY: Compression Quality +UPLOAD_PAGE_IMAGE_PROCESS_QUALITY: Compression Quality(1-100) UPLOAD_PAGE_IMAGE_PROCESS_ISCONVERT: Convert Format UPLOAD_PAGE_IMAGE_PROCESS_CONVERTFORMAT: Destination Format UPLOAD_PAGE_IMAGE_PROCESS_CONVERTFORMAT_SPECIFIC: 'Specific Format, Please enter in json format, e.g. {"png": "jpg"}' @@ -263,6 +263,8 @@ SETTINGS_SHORT_URL_C1N_TOKEN: C1N Token SETTINGS_SHORT_URL_YOURLS_DOMAIN: YOURLS domain SETTINGS_SHORT_URL_YOURLS_SIGNATURE: YOURLS signature SETTINGS_SHORT_URL_CF_WORKER_HOST: Cloudflare Worker Host +SETTINGS_SHORT_SINK_DOMAIN: sink domain +SETTINGS_SHORT_SINK_TOKEN: sink token SETTINGS_DELETE_LOCAL_FILE_AFTER_UPLOAD: Delete local file after upload SETTINGS_SYNC_CONFIG: Settings Sync Configuration SETTINGS_SYNC_CONFIG_TITLE: Sync Settings @@ -390,6 +392,7 @@ MANAGE_SETTING_CLEAR_CACHE_TIPS: After clearing, the file list will be reloaded MANAGE_SETTING_CLEAR_CACHE_PROMPT: Are you sure you want to clear the file list cache database? MANAGE_SETTING_CLEAR_CACHE_BUTTON: Clear MANAGE_SETTING_ISSHOWTHUMBNAIL_TITLE: Display the original image instead of format icon (requires public access permissions) +MANAGE_SETTING_ISUSEPRESIGNEDURL_TITLE: Use presigned URL for image display MANAGE_SETTING_ISSHOWLIST_TITLE: Default display mode for the file list MANAGE_SETTING_ISSHOWLIST_ON: List MANAGE_SETTING_ISSHOWLIST_OFF: Card diff --git a/public/i18n/zh-CN.yml b/public/i18n/zh-CN.yml index e60931a..4703f7a 100644 --- a/public/i18n/zh-CN.yml +++ b/public/i18n/zh-CN.yml @@ -121,7 +121,7 @@ UPLOAD_PAGE_IMAGE_PROCESS_WMCOLOR: 水印颜色,请从取色器中选择 UPLOAD_PAGE_IMAGE_PROCESS_WMPATH: 水印图片路径(留空使用默认图片) UPLOAD_PAGE_IMAGE_PROCESS_WMPOSITION: 水印位置 UPLOAD_PAGE_IMAGE_PROCESS_ISREMOVEEXIF: 是否移除EXIF信息 -UPLOAD_PAGE_IMAGE_PROCESS_QUALITY: 压缩质量 +UPLOAD_PAGE_IMAGE_PROCESS_QUALITY: 压缩质量(1-100) UPLOAD_PAGE_IMAGE_PROCESS_ISCONVERT: 是否转换格式 UPLOAD_PAGE_IMAGE_PROCESS_CONVERTFORMAT: 转换目的格式 UPLOAD_PAGE_IMAGE_PROCESS_CONVERTFORMAT_SPECIFIC: '精细化转换格式, 请输入JSON格式,如: {"png": "jpg"}' @@ -266,6 +266,8 @@ SETTINGS_SHORT_URL_C1N_TOKEN: C1N Token SETTINGS_SHORT_URL_YOURLS_DOMAIN: YOURLS域名 SETTINGS_SHORT_URL_YOURLS_SIGNATURE: YOURLS signature SETTINGS_SHORT_URL_CF_WORKER_HOST: Cloudflare Worker域名 +SETTINGS_SHORT_SINK_DOMAIN: sink域名 +SETTINGS_SHORT_SINK_TOKEN: sink token SETTINGS_DELETE_LOCAL_FILE_AFTER_UPLOAD: 上传后删除本地文件 SETTINGS_SYNC_CONFIG: 设置配置同步 SETTINGS_SYNC_CONFIG_TITLE: 同步设置 @@ -392,6 +394,7 @@ MANAGE_SETTING_CLEAR_CACHE_TIPS: 清空后下次进入新目录时将会重新 MANAGE_SETTING_CLEAR_CACHE_PROMPT: 确定要清空文件列表缓存数据库吗? MANAGE_SETTING_CLEAR_CACHE_BUTTON: 清空 MANAGE_SETTING_ISSHOWTHUMBNAIL_TITLE: 图片显示为原图而非默认文件格式图标(需要存储桶可公开访问) +MANAGE_SETTING_ISUSEPRESIGNEDURL_TITLE: 使用预签名URL预览图片 MANAGE_SETTING_ISSHOWLIST_TITLE: 文件列表默认显示方式 MANAGE_SETTING_ISSHOWLIST_ON: 列表 MANAGE_SETTING_ISSHOWLIST_OFF: 卡片 diff --git a/public/i18n/zh-TW.yml b/public/i18n/zh-TW.yml index 1c88f00..22efdab 100644 --- a/public/i18n/zh-TW.yml +++ b/public/i18n/zh-TW.yml @@ -121,7 +121,7 @@ UPLOAD_PAGE_IMAGE_PROCESS_WMCOLOR: 水印顏色,請從取色器中選擇 UPLOAD_PAGE_IMAGE_PROCESS_WMPATH: 水印圖片路徑(留空使用預設圖片) UPLOAD_PAGE_IMAGE_PROCESS_WMPOSITION: 水印位置 UPLOAD_PAGE_IMAGE_PROCESS_ISREMOVEEXIF: 是否移除EXIF信息 -UPLOAD_PAGE_IMAGE_PROCESS_QUALITY: 壓縮質量 +UPLOAD_PAGE_IMAGE_PROCESS_QUALITY: 壓縮質量(1-100) UPLOAD_PAGE_IMAGE_PROCESS_ISCONVERT: 是否轉換格式 UPLOAD_PAGE_IMAGE_PROCESS_CONVERTFORMAT: 轉換目的格式 UPLOAD_PAGE_IMAGE_PROCESS_CONVERTFORMAT_SPECIFIC: '指定格式, 请输入JSON格式配置,如{"jpg":"png"}' @@ -264,6 +264,8 @@ SETTINGS_SHORT_URL_C1N_TOKEN: C1N Token SETTINGS_SHORT_URL_YOURLS_DOMAIN: YOURLS域名 SETTINGS_SHORT_URL_YOURLS_SIGNATURE: YOURLS signature SETTINGS_SHORT_URL_CF_WORKER_HOST: Cloudflare Worker Host +SETTINGS_SHORT_SINK_DOMAIN: sink域名 +SETTINGS_SHORT_SINK_TOKEN: sink token SETTINGS_DELETE_LOCAL_FILE_AFTER_UPLOAD: 上傳後刪除本地檔案 SETTINGS_SYNC_CONFIG: 設置同步配置 SETTINGS_SYNC_CONFIG_TITLE: 同步設置 @@ -390,6 +392,7 @@ MANAGE_SETTING_CLEAR_CACHE_TIPS: 清空後下次進入新目錄時將會重新 MANAGE_SETTING_CLEAR_CACHE_PROMPT: 確定要清空檔案列表快取資料庫嗎? MANAGE_SETTING_CLEAR_CACHE_BUTTON: 清空 MANAGE_SETTING_ISSHOWTHUMBNAIL_TITLE: 顯示圖片的原始圖像而非預設的檔案格式圖示(需要存儲桶公開訪問權限) +MANAGE_SETTING_ISUSEPRESIGNEDURL_TITLE: 使用預簽名URL预览圖片 MANAGE_SETTING_ISSHOWLIST_TITLE: 檔案列表預設顯示方式 MANAGE_SETTING_ISSHOWLIST_ON: 列表 MANAGE_SETTING_ISSHOWLIST_OFF: 卡片 diff --git a/src/main/apis/app/system/index.ts b/src/main/apis/app/system/index.ts index 380a680..35b2efb 100644 --- a/src/main/apis/app/system/index.ts +++ b/src/main/apis/app/system/index.ts @@ -326,7 +326,13 @@ export function createTray(tooltip: string) { if (deleteLocalFile) { await fs.remove(rawInput[i]) } - pasteText.push(await pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink))) + const [pasteTextItem, shortUrl] = await pasteTemplate( + pasteStyle, + imgs[i], + db.get(configPaths.settings.customLink) + ) + imgs[i].shortUrl = shortUrl + pasteText.push(pasteTextItem) const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true @@ -334,7 +340,7 @@ export function createTray(tooltip: string) { if (isShowResultNotification) { const notification = new Notification({ title: T('UPLOAD_SUCCEED'), - body: imgs[i].imgUrl! + body: shortUrl || imgs[i].imgUrl! // icon: files[i] }) setTimeout(() => { diff --git a/src/main/apis/app/uploader/apis.ts b/src/main/apis/app/uploader/apis.ts index 068ff1f..13c87bf 100644 --- a/src/main/apis/app/uploader/apis.ts +++ b/src/main/apis/app/uploader/apis.ts @@ -54,7 +54,9 @@ export const uploadClipboardFiles = async (): Promise => { if (img.length > 0) { const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW) const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN - handleCopyUrl(await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink))) + const [pastedText, shortUrl] = await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink)) + img[0].shortUrl = shortUrl + handleCopyUrl(pastedText) const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true @@ -62,7 +64,7 @@ export const uploadClipboardFiles = async (): Promise => { if (isShowResultNotification) { const notification = new Notification({ title: T('UPLOAD_SUCCEED'), - body: img[0].imgUrl! + body: shortUrl || img[0].imgUrl! // icon: img[0].imgUrl }) setTimeout(() => { @@ -128,7 +130,13 @@ export const uploadChoosedFiles = async ( picgo.log.error(err) }) } - pasteText.push(await pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink))) + const [pasteTextItem, shortUrl] = await pasteTemplate( + pasteStyle, + imgs[i], + db.get(configPaths.settings.customLink) + ) + imgs[i].shortUrl = shortUrl + pasteText.push(pasteTextItem) const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true @@ -136,7 +144,7 @@ export const uploadChoosedFiles = async ( if (isShowResultNotification) { const notification = new Notification({ title: T('UPLOAD_SUCCEED'), - body: imgs[i].imgUrl! + body: shortUrl || imgs[i].imgUrl! // icon: files[i].path }) setTimeout(() => { diff --git a/src/main/apis/app/uploader/index.ts b/src/main/apis/app/uploader/index.ts index 1c917e0..eeb8ccf 100644 --- a/src/main/apis/app/uploader/index.ts +++ b/src/main/apis/app/uploader/index.ts @@ -1,9 +1,9 @@ import dayjs from 'dayjs' import { BrowserWindow, clipboard, ipcMain, Notification, WebContents } from 'electron' import fs from 'fs-extra' -import util from 'util' import path from 'path' import { IPicGo } from 'piclist' +import util from 'util' import writeFile from 'write-file-atomic' import windowManager from 'apis/app/window/windowManager' @@ -22,7 +22,6 @@ import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static' const waitForRename = (window: BrowserWindow, id: number): Promise => { return new Promise(resolve => { - const windowId = window.id ipcMain.once(`${RENAME_FILE_NAME}${id}`, (_: Event, newName: string) => { resolve(newName) window.close() @@ -30,20 +29,21 @@ const waitForRename = (window: BrowserWindow, id: number): Promise { resolve(null) ipcMain.removeAllListeners(`${RENAME_FILE_NAME}${id}`) - windowManager.deleteById(windowId) + windowManager.deleteById(window.id) }) }) } const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) => { + const { type, fromClipboard, count, duration } = options const data: ITalkingDataOptions = { EventId: 'upload', - Label: options.type, + Label: type, MapKv: { - by: options.fromClipboard ? 'clipboard' : 'files', - count: options.count, - duration: calcDurationRange(options.duration || 0), - type: options.type + by: fromClipboard ? 'clipboard' : 'files', + count, + duration: calcDurationRange(duration || 0), + type } } webContents.send(TALKING_DATA_EVENT, data) @@ -58,8 +58,7 @@ class Uploader { init() { picgo.on(ICOREBuildInEvent.NOTIFICATION, (message: Electron.NotificationConstructorOptions | undefined) => { - const notification = new Notification(message) - notification.show() + new Notification(message).show() }) picgo.on(ICOREBuildInEvent.UPLOAD_PROGRESS, (progress: any) => { @@ -84,15 +83,11 @@ class Uploader { await Promise.all( ctx.output.map(async (item, index) => { let name: undefined | string | null - let fileName: string | undefined - if (autoRename) { - fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS') + item.extname - } else { - fileName = item.fileName - } + const fileName = autoRename + ? `${dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS')}${item.extname}` + : item.fileName if (rename) { const window = windowManager.create(IWindowList.RENAME_WINDOW)! - logger.info('create rename window') ipcMain.on(GET_RENAME_FILE_NAME, evt => { try { if (evt.sender.id === window.webContents.id) { @@ -118,61 +113,52 @@ class Uploader { return this } + private async getClipboardImagePath(): Promise { + const imgPath = getClipboardFilePath() + if (imgPath) return imgPath + + const nativeImage = clipboard.readImage() + if (nativeImage.isEmpty()) return false + + const buffer = nativeImage.toPNG() + const baseDir = picgo.baseDir + const fileName = `${dayjs().format('YYYYMMDDHHmmSSS')}.png` + const filePath = path.join(baseDir, CLIPBOARD_IMAGE_FOLDER, fileName) + await writeFile(filePath, buffer) + return filePath + } + /** * use electron's clipboard image to upload */ async uploadWithBuildInClipboard(): Promise { - let filePath = '' + let imgPath: string | false = false try { - const imgPath = getClipboardFilePath() - if (!imgPath) { - const nativeImage = clipboard.readImage() - if (nativeImage.isEmpty()) { - return false - } - const buffer = nativeImage.toPNG() - const baseDir = picgo.baseDir - const fileName = `${dayjs().format('YYYYMMDDHHmmSSS')}.png` - filePath = path.join(baseDir, CLIPBOARD_IMAGE_FOLDER, fileName) - await writeFile(filePath, buffer) - return await this.upload([filePath]) - } else { - return await this.upload([imgPath]) - } + imgPath = await this.getClipboardImagePath() + if (!imgPath) return false + return await this.upload([imgPath]) } catch (e: any) { logger.error(e) return false } finally { - if (filePath) { - fs.remove(filePath) + if (imgPath) { + fs.remove(imgPath) } } } async uploadWithBuildInClipboardReturnCtx(img?: IUploadOption, skipProcess = false): Promise { - let filePath = '' + let imgPath: string | false = false try { - const imgPath = getClipboardFilePath() - if (!imgPath) { - const nativeImage = clipboard.readImage() - if (nativeImage.isEmpty()) { - return false - } - const buffer = nativeImage.toPNG() - const baseDir = picgo.baseDir - const fileName = `${dayjs().format('YYYYMMDDHHmmSSS')}.png` - filePath = path.join(baseDir, CLIPBOARD_IMAGE_FOLDER, fileName) - await writeFile(filePath, buffer) - return await this.uploadReturnCtx(img ?? [filePath], skipProcess) - } else { - return await this.uploadReturnCtx(img ?? [imgPath], skipProcess) - } + imgPath = await this.getClipboardImagePath() + if (!imgPath) return false + return await this.uploadReturnCtx(img ?? [imgPath], skipProcess) } catch (e: any) { logger.error(e) return false } finally { - if (filePath) { - fs.remove(filePath) + if (imgPath) { + fs.remove(imgPath) } } } @@ -181,22 +167,22 @@ class Uploader { try { const startTime = Date.now() const ctx = await picgo.uploadReturnCtx(img, skipProcess) - if (Array.isArray(ctx.output) && ctx.output.some((item: ImgInfo) => item.imgUrl)) { - if (this.webContents) { - handleTalkingData(this.webContents, { - fromClipboard: !img, - type: db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms', - count: img ? img.length : 1, - duration: Date.now() - startTime - } as IAnalyticsData) - } - ctx.output.forEach((item: ImgInfo) => { - item.config = JSON.parse(JSON.stringify(db.get(`picBed.${item.type}`))) - }) - return ctx - } else { - return false + if (!Array.isArray(ctx.output) || !ctx.output.some((item: ImgInfo) => item.imgUrl)) return false + + if (this.webContents) { + handleTalkingData(this.webContents, { + fromClipboard: !img, + type: db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms', + count: img ? img.length : 1, + duration: Date.now() - startTime + } as IAnalyticsData) } + + ctx.output.forEach((item: ImgInfo) => { + item.config = JSON.parse(JSON.stringify(db.get(`picBed.${item.type}`))) + }) + + return ctx } catch (e: any) { logger.error(e) setTimeout(() => { @@ -216,22 +202,20 @@ class Uploader { try { const startTime = Date.now() const output = await picgo.upload(img) - if (Array.isArray(output) && output.some((item: ImgInfo) => item.imgUrl)) { - if (this.webContents) { - handleTalkingData(this.webContents, { - fromClipboard: !img, - type: db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms', - count: img ? img.length : 1, - duration: Date.now() - startTime - } as IAnalyticsData) - } - output.forEach((item: ImgInfo) => { - item.config = JSON.parse(JSON.stringify(db.get(`picBed.${item.type}`))) - }) - return output.filter(item => item.imgUrl) - } else { - return false + if (!Array.isArray(output) || !output.some((item: ImgInfo) => item.imgUrl)) return false + + if (this.webContents) { + handleTalkingData(this.webContents, { + fromClipboard: !img, + type: db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms', + count: img ? img.length : 1, + duration: Date.now() - startTime + } as IAnalyticsData) } + output.forEach((item: ImgInfo) => { + item.config = JSON.parse(JSON.stringify(db.get(`picBed.${item.type}`))) + }) + return output.filter(item => item.imgUrl) } catch (e: any) { logger.error(e) setTimeout(() => { diff --git a/src/main/apis/core/utils/localLogger.ts b/src/main/apis/core/utils/localLogger.ts index 2a95e08..8d9dcb3 100644 --- a/src/main/apis/core/utils/localLogger.ts +++ b/src/main/apis/core/utils/localLogger.ts @@ -21,14 +21,10 @@ const checkLogFileIsLarge = (logPath: string): CheckLogFileResult => { logFileSizeLimit: DEFAULT_LOG_FILE_SIZE_LIMIT } } - return { - isLarge: false - } + return { isLarge: false } } catch (e) { console.log(e) - return { - isLarge: true - } + return { isLarge: true } } } @@ -49,9 +45,7 @@ const recreateLogFile = (logPath: string): void => { const getLogger = (logPath: string, logType: string) => { let hasUncathcedError = false try { - if (!fs.existsSync(logPath)) { - fs.ensureFileSync(logPath) - } + fs.ensureFileSync(logPath) if (checkLogFileIsLarge(logPath).isLarge) { recreateLogFile(logPath) } diff --git a/src/main/apis/gui/index.ts b/src/main/apis/gui/index.ts index 2d571b1..a2b2537 100644 --- a/src/main/apis/gui/index.ts +++ b/src/main/apis/gui/index.ts @@ -95,7 +95,13 @@ class GuiApi implements IGuiApi { if (deleteLocalFile) { await fs.remove(rawInput[i]) } - pasteText.push(await pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink))) + const [pasteTextItem, shortUrl] = await pasteTemplate( + pasteStyle, + imgs[i], + db.get(configPaths.settings.customLink) + ) + imgs[i].shortUrl = shortUrl + pasteText.push(pasteTextItem) const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true @@ -103,7 +109,7 @@ class GuiApi implements IGuiApi { if (isShowResultNotification) { const notification = new Notification({ title: T('UPLOAD_SUCCEED'), - body: imgs[i].imgUrl as string + body: shortUrl || (imgs[i].imgUrl! as string) // icon: imgs[i].imgUrl }) setTimeout(() => { diff --git a/src/main/events/rpc/routes/gallery/index.ts b/src/main/events/rpc/routes/gallery/index.ts index fc049b8..ec66114 100644 --- a/src/main/events/rpc/routes/gallery/index.ts +++ b/src/main/events/rpc/routes/gallery/index.ts @@ -27,11 +27,11 @@ const galleryRoutes = [ const [item, copy = true] = args const pasteStyle = picgo.getConfig(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN const customLink = picgo.getConfig(configPaths.settings.customLink) - const txt = await pasteTemplate(pasteStyle, item, customLink) + const [txt, shortUrl] = await pasteTemplate(pasteStyle, item, customLink) if (copy) { clipboard.writeText(txt) } - return txt + return [txt, shortUrl] }, type: IRPCType.INVOKE }, diff --git a/src/main/events/rpc/routes/tray/index.ts b/src/main/events/rpc/routes/tray/index.ts index a48cc2e..930d3c9 100644 --- a/src/main/events/rpc/routes/tray/index.ts +++ b/src/main/events/rpc/routes/tray/index.ts @@ -40,7 +40,9 @@ const trayRoutes = [ const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard() if (img !== false) { const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN - handleCopyUrl(await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink))) + const [pasteText, shortUrl] = await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink)) + img[0].shortUrl = shortUrl + handleCopyUrl(pasteText) const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true @@ -48,7 +50,7 @@ const trayRoutes = [ if (isShowResultNotification) { const notification = new Notification({ title: T('UPLOAD_SUCCEED'), - body: img[0].imgUrl! + body: shortUrl || img[0].imgUrl! // icon: file[0] // icon: img[0].imgUrl }) diff --git a/src/main/lifeCycle/index.ts b/src/main/lifeCycle/index.ts index b9cb5ad..9f07975 100644 --- a/src/main/lifeCycle/index.ts +++ b/src/main/lifeCycle/index.ts @@ -41,18 +41,20 @@ const isDevelopment = process.env.NODE_ENV !== 'production' const handleStartUpFiles = (argv: string[], cwd: string) => { const files = getUploadFiles(argv, cwd, logger) - if (files === null || files.length > 0) { - // 如果有文件列表作为参数,说明是命令行启动 - if (files === null) { - logger.info('cli -> uploading file from clipboard') - uploadClipboardFiles() - } else { - logger.info('cli -> uploading files from cli', ...files.map(item => item.path)) - const win = windowManager.getAvailableWindow() - uploadChoosedFiles(win.webContents, files) - } + + if (files === null) { + logger.info('cli -> uploading file from clipboard') + uploadClipboardFiles() return true } + + if (files.length > 0) { + logger.info('cli -> uploading files from cli', ...files.map(file => file.path)) + const win = windowManager.getAvailableWindow() + uploadChoosedFiles(win.webContents, files) + return true + } + return false } diff --git a/src/main/server/routerManager.ts b/src/main/server/routerManager.ts index 71718e9..32d9cae 100644 --- a/src/main/server/routerManager.ts +++ b/src/main/server/routerManager.ts @@ -54,6 +54,7 @@ router.post( const picbed = urlparams?.get('picbed') const passedKey = urlparams?.get('key') const serverKey = picgo.getConfig(configPaths.settings.serverKey) || '' + const useShortUrl = picgo.getConfig(configPaths.settings.useShortUrl) if (serverKey && passedKey !== serverKey) { handleResponse({ response, @@ -92,8 +93,9 @@ router.post( // upload with clipboard logger.info('[PicList Server] upload clipboard file') const result = await uploadClipboardFiles() - const res = result.url + const res = useShortUrl ? result.fullResult.shortUrl || result.url : result.url const fullResult = result.fullResult + fullResult.imgUrl = useShortUrl ? fullResult.shortUrl || fullResult.imgUrl : fullResult.imgUrl logger.info('[PicList Server] upload result:', res) if (res) { const treatedFullResult = { @@ -130,7 +132,7 @@ router.post( const win = windowManager.getAvailableWindow() const result = await uploadChoosedFiles(win.webContents, pathList) const res = result.map(item => { - return item.url + return useShortUrl ? item.fullResult.shortUrl || item.url : item.url }) const fullResult = result.map((item: any) => { const treatedItem = { @@ -139,6 +141,7 @@ router.post( ...item.fullResult } delete treatedItem.config + treatedItem.imgUrl = useShortUrl ? treatedItem.shortUrl || treatedItem.imgUrl : treatedItem.imgUrl return treatedItem }) logger.info('[PicList Server] upload result', res.join(' ; ')) diff --git a/src/main/utils/aesHelper.ts b/src/main/utils/aesHelper.ts index e822076..1b1b808 100644 --- a/src/main/utils/aesHelper.ts +++ b/src/main/utils/aesHelper.ts @@ -6,15 +6,13 @@ import { configPaths } from '#/utils/configPaths' import { DEFAULT_AES_PASSWORD } from '#/utils/static' export class AESHelper { - key: Buffer - - constructor() { - const userPassword = picgo.getConfig(configPaths.settings.aesPassword) || DEFAULT_AES_PASSWORD - const fixedSalt = Buffer.from('a8b3c4d2e4f5098712345678feedc0de', 'hex') - const fixedIterations = 100000 - const keyLength = 32 - this.key = crypto.pbkdf2Sync(userPassword, fixedSalt, fixedIterations, keyLength, 'sha512') - } + private key: Buffer = crypto.pbkdf2Sync( + picgo.getConfig(configPaths.settings.aesPassword) || DEFAULT_AES_PASSWORD, + Buffer.from('a8b3c4d2e4f5098712345678feedc0de', 'hex'), + 100000, + 32, + 'sha512' + ) encrypt(plainText: string) { const iv = crypto.randomBytes(16) @@ -26,11 +24,9 @@ export class AESHelper { decrypt(encryptedData: string) { const [ivHex, encryptedText] = encryptedData.split(':') - if (!ivHex || !encryptedText) { - return '{}' - } - const iv = Buffer.from(ivHex, 'hex') - const decipher = crypto.createDecipheriv('aes-256-cbc', this.key, iv) + if (!ivHex || !encryptedText) return '{}' + + const decipher = crypto.createDecipheriv('aes-256-cbc', this.key, Buffer.from(ivHex, 'hex')) let decrypted = decipher.update(encryptedText, 'hex', 'utf8') decrypted += decipher.final('utf8') return decrypted diff --git a/src/main/utils/common.ts b/src/main/utils/common.ts index bb5b4c3..d680259 100644 --- a/src/main/utils/common.ts +++ b/src/main/utils/common.ts @@ -210,6 +210,34 @@ const generateCFWORKERShortUrl = async (url: string) => { return url } +const generateSinkShortUrl = async (url: string) => { + let sinkDomain = db.get(configPaths.settings.sinkDomain) || '' + const sinkToken = db.get(configPaths.settings.sinkToken) || '' + if (!sinkDomain || !sinkToken) { + logger.warn('Sink domain or token is not set') + return url + } + if (!/^https?:\/\//.test(sinkDomain)) { + sinkDomain = `http://${sinkDomain}` + } + if (sinkDomain.endsWith('/')) { + sinkDomain = sinkDomain.slice(0, -1) + } + try { + const res = await axios.post( + `${sinkDomain}/api/link/create`, + { url }, + { headers: { Authorization: `Bearer ${sinkToken}` } } + ) + if (res.data?.link?.slug) { + return `${sinkDomain}/${res.data.link.slug}` + } + } catch (e: any) { + logger.error(e) + } + return url +} + export const generateShortUrl = async (url: string) => { const server = db.get(configPaths.settings.shortUrlServer) || IShortUrlServer.C1N switch (server) { @@ -219,6 +247,8 @@ export const generateShortUrl = async (url: string) => { return generateYOURLSShortUrl(url) case IShortUrlServer.CFWORKER: return generateCFWORKERShortUrl(url) + case IShortUrlServer.SINK: + return generateSinkShortUrl(url) default: return url } diff --git a/src/main/utils/handleArgv.ts b/src/main/utils/handleArgv.ts index d4c9a56..8b8fcf5 100644 --- a/src/main/utils/handleArgv.ts +++ b/src/main/utils/handleArgv.ts @@ -9,61 +9,25 @@ interface IResultFileObject { } type Result = IResultFileObject[] -interface IHandleArgvResult { - success: boolean - fileList?: string[] -} - -const handleArgv = (argv: string[]): IHandleArgvResult => { - const uploadIndex = argv.indexOf('upload') - if (uploadIndex !== -1) { - const fileList = argv.slice(1 + uploadIndex) - return { - success: true, - fileList - } - } - return { - success: false - } -} - const getUploadFiles = (argv = process.argv, cwd = process.cwd(), logger: Logger) => { - const { success, fileList } = handleArgv(argv) - if (!success) { - return [] - } else { - if (fileList?.length === 0) { - return null // for uploading images in clipboard - } else if ((fileList?.length || 0) > 0) { - const result = fileList! - .map(item => { - if (isUrl(item)) { - return { - path: item - } - } - if (path.isAbsolute(item)) { - return { - path: item - } - } else { - const tempPath = path.join(cwd, item) - if (fs.existsSync(tempPath)) { - return { - path: tempPath - } - } else { - logger.warn(`cli -> can't get file: ${tempPath}, invalid path`) - return null - } - } - }) - .filter(item => item !== null) as Result - return result - } - } - return [] + const uploadIndex = argv.indexOf('upload') + if (uploadIndex === -1) return [] + const fileList = argv.slice(uploadIndex + 1) + + if (fileList.length === 0) return null // for uploading images in clipboard + + return fileList + .map(item => { + if (isUrl(item) || path.isAbsolute(item)) return { path: item } + + const resolvedPath = path.join(cwd, item) + if (fs.existsSync(resolvedPath)) { + return { path: resolvedPath } + } + logger.warn(`cli -> can't get file: ${resolvedPath}, invalid path`) + return null + }) + .filter(item => item !== null) as Result } export { getUploadFiles } diff --git a/src/main/utils/pasteTemplate.ts b/src/main/utils/pasteTemplate.ts index 605706d..ee02ca8 100644 --- a/src/main/utils/pasteTemplate.ts +++ b/src/main/utils/pasteTemplate.ts @@ -32,7 +32,7 @@ export default async (style: IPasteStyle, item: ImgInfo, customLink: string | un url = handleUrlEncodeWithSetting(url) const useShortUrl = db.get(configPaths.settings.useShortUrl) || false if (useShortUrl) { - url = await generateShortUrl(url) + url = item.shortUrl && item.shortUrl !== url ? item.shortUrl : await generateShortUrl(url) } const _customLink = customLink || '![$fileName]($url)' const tpl = { @@ -45,5 +45,5 @@ export default async (style: IPasteStyle, item: ImgInfo, customLink: string | un url }) } - return tpl[style] + return [tpl[style], useShortUrl ? url : ''] } diff --git a/src/renderer/components/ImagePreSign.vue b/src/renderer/components/ImagePreSign.vue new file mode 100644 index 0000000..a514e35 --- /dev/null +++ b/src/renderer/components/ImagePreSign.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/renderer/components/ImagePreSignTsx.tsx b/src/renderer/components/ImagePreSignTsx.tsx new file mode 100644 index 0000000..de0a8b2 --- /dev/null +++ b/src/renderer/components/ImagePreSignTsx.tsx @@ -0,0 +1,65 @@ +import { ElImage, ElIcon } from 'element-plus' +import { defineComponent, ref, onMounted, watch, computed } from 'vue' +import { Loading } from '@element-plus/icons-vue' + +import { getFileIconPath } from '@/manage/utils/common' +import { IRPCActionType } from '#/types/enum' +import { triggerRPC } from '@/utils/common' + +export default defineComponent({ + props: { + isShowThumbnail: { + type: Boolean, + required: true + }, + item: { + type: Object, + required: true + }, + alias: { + type: String, + required: true + }, + url: { + type: String, + required: true + }, + config: { + type: Object, + required: true + } + }, + + setup(props) { + const preSignedUrl = ref('') + + const imageSource = computed(() => { + return props.isShowThumbnail && props.item.isImage + ? preSignedUrl.value + : require(`../manage/pages/assets/icons/${getFileIconPath(props.item.fileName ?? '')}`) + }) + const iconPath = computed(() => + require(`../manage/pages/assets/icons/${getFileIconPath(props.item.fileName ?? '')}`) + ) + + async function getUrl() { + preSignedUrl.value = await triggerRPC(IRPCActionType.MANAGE_GET_PRE_SIGNED_URL, props.alias, props.config) + } + + watch(() => [props.url, props.item], getUrl, { deep: true }) + onMounted(getUrl) + + return () => ( + + {{ + placeholder: () => ( + + + + ), + error: () => + }} + + ) + } +}) diff --git a/src/renderer/components/ImageProcessSetting.vue b/src/renderer/components/ImageProcessSetting.vue index fbd3c8a..ab916f3 100644 --- a/src/renderer/components/ImageProcessSetting.vue +++ b/src/renderer/components/ImageProcessSetting.vue @@ -63,7 +63,7 @@ - + diff --git a/src/renderer/manage/components/DynamicSwitch.vue b/src/renderer/manage/components/DynamicSwitch.vue index 9ac22d5..3ef9a0e 100644 --- a/src/renderer/manage/components/DynamicSwitch.vue +++ b/src/renderer/manage/components/DynamicSwitch.vue @@ -5,7 +5,7 @@ {{ segment.text }} - + diff --git a/src/renderer/manage/pages/BucketPage.vue b/src/renderer/manage/pages/BucketPage.vue index bde0b40..8abdf1c 100644 --- a/src/renderer/manage/pages/BucketPage.vue +++ b/src/renderer/manage/pages/BucketPage.vue @@ -391,12 +391,7 @@ https://www.baidu.com/img/bd_logo1.png" shadow="hover" > + + + + + manageStore.config.settings.isShowThumbnail ?? false) +const isUsePreSignedUrl = computed(() => manageStore.config.settings.isUsePreSignedUrl ?? false) const isAutoRefresh = computed(() => manageStore.config.settings.isAutoRefresh ?? false) const isIgnoreCase = computed(() => manageStore.config.settings.isIgnoreCase ?? false) @@ -2901,6 +2932,18 @@ function singleRename() { }) } +function handleGetS3Config(item: any) { + return { + bucketName: configMap.bucketName, + region: configMap.bucketConfig.Location, + key: item.key, + customUrl: currentCustomDomain.value, + expires: manageStore.config.settings.PreSignedExpire, + githubPrivate: configMap.bucketConfig.private, + rawUrl: item.url + } +} + async function getPreSignedUrl(item: any) { const param = { // tcyun @@ -3180,32 +3223,42 @@ const columns: Column[] = [ reference: () => !item.isDir ? ( currentPicBedName.value !== 'webdavplist' ? ( - + ) : ( + - {{ - placeholder: () => ( - - - - ), - error: () => ( - - ) - }} - + } + fit='contain' + style={{ width: '20px', height: '20px' }} + > + {{ + placeholder: () => ( + + + + ), + error: () => ( + + ) + }} + + ) ) : item.isImage ? ( [] = [ config={handleGetWebdavConfig()} url={item.url} /> + ) : currentPicBedName.value === 's3plist' && item.isImage && isUsePreSignedUrl.value ? ( + ) : ( ({ isAutoRefresh: false, isShowThumbnail: false, isShowList: false, + isUsePreSignedUrl: false, isIgnoreCase: false, isForceCustomUrlHttps: false, isEncodeUrl: false, @@ -246,6 +247,7 @@ const switchFieldsList = [ 'isAutoRefresh', 'isShowThumbnail', 'isShowList', + 'isUsePreSignedUrl', 'isForceCustomUrlHttps', 'isEncodeUrl', 'isUploadKeepDirStructure', @@ -254,7 +256,7 @@ const switchFieldsList = [ 'randomStringRename', 'customRename' ] -const switchFieldsNoTipsList = ['isShowThumbnail', 'isShowList'] +const switchFieldsNoTipsList = ['isShowThumbnail', 'isShowList', 'isUsePreSignedUrl'] const switchFieldsHasActiveTextList = ['isShowList'] const switchFieldsConfigList = switchFieldsList.map(item => ({ @@ -321,7 +323,7 @@ async function initData() { } async function handleDownloadDirClick() { - const result = triggerRPC(IRPCActionType.MANAGE_SELECT_DOWNLOAD_FOLDER) + const result = await triggerRPC(IRPCActionType.MANAGE_SELECT_DOWNLOAD_FOLDER) if (result) { form.value.downloadDir = result } diff --git a/src/renderer/pages/Gallery.vue b/src/renderer/pages/Gallery.vue index 7e6f8c0..0b9a5ee 100644 --- a/src/renderer/pages/Gallery.vue +++ b/src/renderer/pages/Gallery.vue @@ -546,15 +546,21 @@ function handleClose() { async function copy(item: ImgInfo) { item.config = JSON.parse(JSON.stringify(item.config) || '{}') - const copyLink = await triggerRPC(IRPCActionType.GALLERY_PASTE_TEXT, item) + const result = await triggerRPC<[string, string]>(IRPCActionType.GALLERY_PASTE_TEXT, item) + if (result && result[1] && item.id) { + await $$db.updateById(item.id, { + shortUrl: result[1] + }) + } const obj = { title: $T('COPY_LINK_SUCCEED'), - body: copyLink + body: result ? result[0] : '' } const myNotification = new Notification(obj.title, obj) myNotification.onclick = () => { return true } + updateGallery() } function remove(item: ImgInfo) { @@ -743,8 +749,13 @@ async function multiCopy() { if (choosedList[key]) { const item = await $$db.getById(key) if (item) { - const txt = await triggerRPC(IRPCActionType.GALLERY_PASTE_TEXT, item) - copyString.push(txt!) + const result = await triggerRPC(IRPCActionType.GALLERY_PASTE_TEXT, item) + copyString.push(result ? result[0] : '') + if (result && result[1] && item.id) { + await $$db.updateById(item.id, { + shortUrl: result[1] + }) + } choosedList[key] = false } } @@ -758,6 +769,7 @@ async function multiCopy() { myNotification.onclick = () => { return true } + updateGallery() } } diff --git a/src/renderer/pages/PicGoSetting.vue b/src/renderer/pages/PicGoSetting.vue index eece303..8ee1da5 100644 --- a/src/renderer/pages/PicGoSetting.vue +++ b/src/renderer/pages/PicGoSetting.vue @@ -360,6 +360,28 @@ :placeholder="$T('SETTINGS_SHORT_URL_CF_WORKER_HOST')" /> + + + + + + ({ yourlsDomain: '', yourlsSignature: '', cfWorkerHost: '', + sinkDomain: '', + sinkToken: '', deleteLocalFile: false, serverKey: '', aesPassword: 'PicList-aesPassword', @@ -1089,6 +1117,8 @@ const autoWatchKeys = [ 'yourlsDomain', 'yourlsSignature', 'cfWorkerHost', + 'sinkDomain', + 'sinkToken', 'registry', 'proxy', 'autoCopy', diff --git a/src/universal/types/enum.ts b/src/universal/types/enum.ts index d72c20a..f6203ad 100644 --- a/src/universal/types/enum.ts +++ b/src/universal/types/enum.ts @@ -241,7 +241,8 @@ export enum II18nLanguage { export enum IShortUrlServer { C1N = 'c1n', YOURLS = 'yourls', - CFWORKER = 'cf_worker' + CFWORKER = 'cf_worker', + SINK = 'sink' } export enum commonTaskStatus { diff --git a/src/universal/types/i18n.d.ts b/src/universal/types/i18n.d.ts index a47561d..ea6c360 100644 --- a/src/universal/types/i18n.d.ts +++ b/src/universal/types/i18n.d.ts @@ -259,6 +259,8 @@ interface ILocales { SETTINGS_SHORT_URL_YOURLS_DOMAIN: string SETTINGS_SHORT_URL_YOURLS_SIGNATURE: string SETTINGS_SHORT_URL_CF_WORKER_HOST: string + SETTINGS_SHORT_SINK_DOMAIN: string + SETTINGS_SHORT_SINK_TOKEN: string SETTINGS_DELETE_LOCAL_FILE_AFTER_UPLOAD: string SETTINGS_SYNC_CONFIG: string SETTINGS_SYNC_CONFIG_TITLE: string @@ -366,6 +368,7 @@ interface ILocales { MANAGE_SETTING_CLEAR_CACHE_PROMPT: string MANAGE_SETTING_CLEAR_CACHE_BUTTON: string MANAGE_SETTING_ISSHOWTHUMBNAIL_TITLE: string + MANAGE_SETTING_ISUSEPRESIGNEDURL_TITLE: string MANAGE_SETTING_ISSHOWLIST_TITLE: string MANAGE_SETTING_ISSHOWLIST_ON: string MANAGE_SETTING_ISSHOWLIST_OFF: string diff --git a/src/universal/types/view.d.ts b/src/universal/types/view.d.ts index 5ef4d13..6d518d3 100644 --- a/src/universal/types/view.d.ts +++ b/src/universal/types/view.d.ts @@ -26,6 +26,8 @@ interface ISettingForm { yourlsDomain: string yourlsSignature: string cfWorkerHost: string + sinkDomain: string + sinkToken: string deleteLocalFile: boolean serverKey: string aesPassword: string diff --git a/src/universal/utils/configPaths.ts b/src/universal/utils/configPaths.ts index 6f693ec..efe18ca 100644 --- a/src/universal/utils/configPaths.ts +++ b/src/universal/utils/configPaths.ts @@ -71,6 +71,8 @@ export interface IConfigStruct { cfWorkerHost: string yourlsDomain: string yourlsSignature: string + sinkDomain: string + sinkToken: string isSilentNotice: boolean proxy: string registry: string @@ -153,6 +155,8 @@ export const configPaths = { cfWorkerHost: 'settings.cfWorkerHost', yourlsDomain: 'settings.yourlsDomain', yourlsSignature: 'settings.yourlsSignature', + sinkDomain: 'settings.sinkDomain', + sinkToken: 'settings.sinkToken', isSilentNotice: 'settings.isSilentNotice', proxy: 'settings.proxy', registry: 'settings.registry', diff --git a/yarn.lock b/yarn.lock index facd982..872b10e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11956,10 +11956,10 @@ performance-now@^2.1.0: resolved "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -piclist@^1.9.6: - version "1.9.6" - resolved "https://registry.yarnpkg.com/piclist/-/piclist-1.9.6.tgz#010753f7b7cedc076251bd1639f7859e48508386" - integrity sha512-CAUbU43/eibk/Jq+SXPL96TXog1vNjpE1pwbsof+D8A8SEpXRg+K5cLAsRjGXubi/SmLonU+imtbldUNuCoHjA== +piclist@^1.9.7: + version "1.9.7" + resolved "https://registry.yarnpkg.com/piclist/-/piclist-1.9.7.tgz#f34714248c8b72b009626fd7feaa25ac663a0bb4" + integrity sha512-52cRCmGZx3jK9unzK4CAmgzIhOOYrfI71wvdigXjNnWGwfVjhxgWj8UkTLSZ9zPRJXzrOLmJvck30UhWUElBsg== dependencies: "@aws-sdk/client-s3" "3.421.0" "@aws-sdk/lib-storage" "3.421.0"