Merge branch 'dev' into release

This commit is contained in:
Kuingsmile 2024-11-16 11:32:42 +08:00
commit fa4e1bf0b2
35 changed files with 525 additions and 255 deletions

View File

@ -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)

15
FAQ.md
View File

@ -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. 水印没有正常添加

View File

@ -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

View File

@ -1,10 +1,15 @@
### ✨ Features
- 优化了第二图床的上传逻辑,现在会使用相同文件名和压缩方式
- 移除了telegra.ph图床官方现已关闭匿名上传功能
- 默认上传快捷键修改为`Ctrl+Alt+U`避免与vscode命令面板冲突
- 优化了短链接处理逻辑:
- 现在相册界面中复制链接时会得到相同的短链接,而不是每次重新获取
- 现在开启短链接时上传结果通知会正确显示
- 现在开启短链接时上传接口会返回短链接
- 管理界面新增使用预签名链接显示预览图片选项
- 现在图片压缩质量只允许设置1-100之间的正整数
- 新增对短链项目[Sink](https://github.com/ccbikai/Sink)的支持
### 🐛 Bug Fixes
- 修复了webdav图床链接拼接错误的问题
- 修复了开启云删除时部分第三方图床图片无法批量删除的问题
- 修复了管理页面设置项界面中部分没有tooltip的选项依旧会显示图标的问题
- 修复了在管理页面无法修改下载文件夹的问题
- 修复了对HEIC图片无法转换格式的问题

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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: 卡片

View File

@ -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: 卡片

View File

@ -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(() => {

View File

@ -54,7 +54,9 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
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<IStringKeyMap> => {
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(() => {

View File

@ -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<string | null> => {
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<string | null
window.on('close', () => {
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<string | false> {
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<ImgInfo[] | false> {
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<IPicGo | false> {
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(() => {

View File

@ -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)
}

View File

@ -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(() => {

View File

@ -27,11 +27,11 @@ const galleryRoutes = [
const [item, copy = true] = args
const pasteStyle = picgo.getConfig<IPasteStyle>(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const customLink = picgo.getConfig<string>(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
},

View File

@ -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
})

View File

@ -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
}

View File

@ -54,6 +54,7 @@ router.post(
const picbed = urlparams?.get('picbed')
const passedKey = urlparams?.get('key')
const serverKey = picgo.getConfig<string>(configPaths.settings.serverKey) || ''
const useShortUrl = picgo.getConfig<boolean>(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(' ; '))

View File

@ -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<string>(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<string>(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

View File

@ -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
}

View File

@ -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 }

View File

@ -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 : '']
}

View File

@ -0,0 +1,51 @@
<template>
<el-image :src="imageSource" fit="contain" style="height: 100px; width: 100%; margin: 0 auto">
<template #placeholder>
<el-icon>
<Loading />
</el-icon>
</template>
<template #error>
<el-image :src="iconPath" fit="contain" style="height: 100px; width: 100%; margin: 0 auto" />
</template>
</el-image>
</template>
<script lang="ts" setup>
import { 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'
const preSignedUrl = ref('')
const props = defineProps<{
item: {
key: string
isImage: boolean
fileName: string | null | undefined
}
alias: string
url: string
config: any
isShowThumbnail: boolean
}>()
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<any>(IRPCActionType.MANAGE_GET_PRE_SIGNED_URL, props.alias, props.config)
}
watch(() => [props.url, props.item], getUrl, { deep: true })
onMounted(getUrl)
</script>

View File

@ -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<any>(IRPCActionType.MANAGE_GET_PRE_SIGNED_URL, props.alias, props.config)
}
watch(() => [props.url, props.item], getUrl, { deep: true })
onMounted(getUrl)
return () => (
<ElImage src={imageSource.value} fit='contain' style='height: 100px;width: 100%;margin: 0 auto;'>
{{
placeholder: () => (
<ElIcon>
<Loading />
</ElIcon>
),
error: () => <ElImage src={iconPath.value} fit='contain' style='height: 100px;width: 100%;margin: 0 auto;' />
}}
</ElImage>
)
}
})

View File

@ -63,7 +63,7 @@
<el-switch v-model="compressForm.isRemoveExif" :style="switchStyle" />
</el-form-item>
<el-form-item :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_QUALITY')">
<el-input-number v-model="compressForm.quality" :min="0" :max="100" :step="1" />
<el-input-number v-model="compressForm.quality" :min="1" :max="100" :step="1" />
</el-form-item>
<el-form-item :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISCONVERT')">
<el-switch v-model="compressForm.isConvert" :style="switchStyle" />

View File

@ -5,7 +5,7 @@
<span v-for="(segment, index) in segments" :key="index" :style="segment.style">
{{ segment.text }}
</span>
<el-tooltip :content="tooltip" effect="dark" placement="right" :persistent="false" teleported>
<el-tooltip v-if="tooltip" :content="tooltip" effect="dark" placement="right" :persistent="false" teleported>
<el-icon>
<InfoFilled />
</el-icon>

View File

@ -391,12 +391,7 @@ https://www.baidu.com/img/bd_logo1.png"
shadow="hover"
>
<el-image
v-if="
!item.isDir &&
currentPicBedName !== 'webdavplist' &&
currentPicBedName !== 'sftp' &&
currentPicBedName !== 'local'
"
v-if="!item.isDir && !['webdavplist', 'sftp', 'local', 's3plist'].includes(currentPicBedName)"
:src="
isShowThumbnail && item.isImage
? item.url
@ -419,6 +414,39 @@ https://www.baidu.com/img/bd_logo1.png"
/>
</template>
</el-image>
<el-image
v-else-if="!item.isDir && currentPicBedName === 's3plist' && !isUsePreSignedUrl"
:src="
isShowThumbnail && item.isImage
? item.url
: require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)
"
fit="contain"
style="height: 100px; width: 100%; margin: 0 auto"
@click="handleClickFile(item)"
>
<template #placeholder>
<el-icon>
<Loading />
</el-icon>
</template>
<template #error>
<el-image
:src="require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)"
fit="contain"
style="height: 100px; width: 100%; margin: 0 auto"
/>
</template>
</el-image>
<ImagePreSign
v-else-if="!item.isDir && currentPicBedName === 's3plist' && isUsePreSignedUrl"
:is-show-thumbnail="isShowThumbnail"
:item="item"
:alias="configMap.alias"
:url="item.url"
:config="handleGetS3Config(item)"
@click="handleClickFile(item)"
/>
<ImageWebdav
v-else-if="!item.isDir && currentPicBedName === 'webdavplist' && item.isImage"
:is-show-thumbnail="isShowThumbnail"
@ -1222,8 +1250,10 @@ import { textFileExt } from '@/manage/utils/textfile'
import { videoExt } from '@/manage/utils/videofile'
import ImageWebdav from '@/components/ImageWebdav.vue'
import ImagePreSign from '@/components/ImagePreSign.vue'
import ImageLocal from '@/components/ImageLocal.vue'
import ImageWebdavTsx from '@/components/ImageWebdavTsx'
import ImagePreSignTsx from '@/components/ImagePreSignTsx'
import { T as $T } from '@/i18n'
@ -1231,7 +1261,7 @@ import { getExtension, trimPath } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import { IUploadTask, IDownloadTask } from '#/types/manage'
import { sendRPC, triggerRPC } from '@/utils/common'
import { IRPCActionType } from 'root/src/universal/types/enum'
import { IRPCActionType } from '#/types/enum'
/*
configMap:{
@ -1378,6 +1408,7 @@ const calculateAllFileSize = computed(
'0'
)
const isShowThumbnail = computed(() => 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<any>[] = [
reference: () =>
!item.isDir ? (
currentPicBedName.value !== 'webdavplist' ? (
<ElImage
src={
isShowThumbnail.value
? item.isImage
? item.url
currentPicBedName.value === 's3plist' && item.isImage && isUsePreSignedUrl.value ? (
<ImagePreSignTsx
isShowThumbnail={isShowThumbnail.value}
item={item}
config={handleGetS3Config(item)}
url={item.url}
alias={configMap.alias}
/>
) : (
<ElImage
src={
isShowThumbnail.value
? item.isImage
? item.url
: require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)
: require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)
: require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)
}
fit='contain'
style={{ width: '20px', height: '20px' }}
>
{{
placeholder: () => (
<ElIcon>
<Loading />
</ElIcon>
),
error: () => (
<ElImage
src={require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)}
fit='contain'
style={{ width: '20px', height: '20px' }}
/>
)
}}
</ElImage>
}
fit='contain'
style={{ width: '20px', height: '20px' }}
>
{{
placeholder: () => (
<ElIcon>
<Loading />
</ElIcon>
),
error: () => (
<ElImage
src={require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)}
fit='contain'
style={{ width: '20px', height: '20px' }}
/>
)
}}
</ElImage>
)
) : item.isImage ? (
<ImageWebdavTsx
isShowThumbnail={isShowThumbnail.value}
@ -3235,6 +3288,14 @@ const columns: Column<any>[] = [
config={handleGetWebdavConfig()}
url={item.url}
/>
) : currentPicBedName.value === 's3plist' && item.isImage && isUsePreSignedUrl.value ? (
<ImagePreSignTsx
isShowThumbnail={isShowThumbnail.value}
item={item}
config={handleGetS3Config(item)}
url={item.url}
alias={configMap.alias}
/>
) : (
<ElImage
src={item.isImage ? item.url : require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)}

View File

@ -214,6 +214,7 @@ const form = ref<IStringKeyMap>({
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<any>(IRPCActionType.MANAGE_SELECT_DOWNLOAD_FOLDER)
const result = await triggerRPC<any>(IRPCActionType.MANAGE_SELECT_DOWNLOAD_FOLDER)
if (result) {
form.value.downloadDir = result
}

View File

@ -546,15 +546,21 @@ function handleClose() {
async function copy(item: ImgInfo) {
item.config = JSON.parse(JSON.stringify(item.config) || '{}')
const copyLink = await triggerRPC<string>(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<ImgInfo>(key)
if (item) {
const txt = await triggerRPC<string>(IRPCActionType.GALLERY_PASTE_TEXT, item)
copyString.push(txt!)
const result = await triggerRPC<string>(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()
}
}

View File

@ -360,6 +360,28 @@
:placeholder="$T('SETTINGS_SHORT_URL_CF_WORKER_HOST')"
/>
</el-form-item>
<el-form-item
v-if="formOfSetting.useShortUrl && formOfSetting.shortUrlServer === 'sink'"
:label="$T('SETTINGS_SHORT_SINK_DOMAIN')"
>
<el-input
v-model="formOfSetting.sinkDomain"
size="small"
style="width: 50%"
:placeholder="$T('SETTINGS_SHORT_SINK_DOMAIN')"
/>
</el-form-item>
<el-form-item
v-if="formOfSetting.useShortUrl && formOfSetting.shortUrlServer === 'sink'"
:label="$T('SETTINGS_SHORT_SINK_TOKEN')"
>
<el-input
v-model="formOfSetting.sinkToken"
size="small"
style="width: 50%"
:placeholder="$T('SETTINGS_SHORT_SINK_TOKEN')"
/>
</el-form-item>
<el-form-item :label="$T('SETTINGS_ENCODE_OUTPUT_URL')">
<el-switch
v-model="formOfSetting.encodeOutputURL"
@ -976,6 +998,10 @@ const shortUrlServerList = [
{
label: 'xyTom/Url-Shorten-Worker',
value: 'cf_worker'
},
{
label: 'ccbikai/Sink',
value: 'sink'
}
]
@ -1050,6 +1076,8 @@ const formOfSetting = ref<ISettingForm>({
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',

View File

@ -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 {

View File

@ -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

View File

@ -26,6 +26,8 @@ interface ISettingForm {
yourlsDomain: string
yourlsSignature: string
cfWorkerHost: string
sinkDomain: string
sinkToken: string
deleteLocalFile: boolean
serverKey: string
aesPassword: string

View File

@ -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',

View File

@ -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"