mirror of
https://github.com/Kuingsmile/PicList.git
synced 2025-01-22 22:28:14 -05:00
Merge branch 'dev' into release
This commit is contained in:
commit
fa4e1bf0b2
23
CHANGELOG.md
23
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)
|
||||
|
||||
|
||||
|
13
FAQ.md
13
FAQ.md
@ -1,6 +1,6 @@
|
||||
# FAQ
|
||||
|
||||
该FAQ修改自PicGo的FAQ,感谢PicGo的作者Molunerfinn。
|
||||
本 FAQ 修改自 PicGo 的 FAQ,感谢 PicGo 的作者 Molunerfinn。
|
||||
|
||||
## 常见问题
|
||||
|
||||
@ -8,13 +8,13 @@
|
||||
|
||||
## 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. 水印没有正常添加
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
### ✨ Features
|
||||
|
||||
- 优化了第二图床的上传逻辑,现在会使用相同文件名和压缩方式
|
||||
- 移除了telegra.ph图床(官方现已关闭匿名上传功能)
|
||||
- 默认上传快捷键修改为`Ctrl+Alt+U`,避免与vscode命令面板冲突
|
||||
- 优化了短链接处理逻辑:
|
||||
- 现在相册界面中复制链接时会得到相同的短链接,而不是每次重新获取
|
||||
- 现在开启短链接时上传结果通知会正确显示
|
||||
- 现在开启短链接时上传接口会返回短链接
|
||||
- 管理界面新增使用预签名链接显示预览图片选项
|
||||
- 现在图片压缩质量只允许设置1-100之间的正整数
|
||||
- 新增对短链项目[Sink](https://github.com/ccbikai/Sink)的支持
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- 修复了webdav图床链接拼接错误的问题
|
||||
- 修复了开启云删除时部分第三方图床图片无法批量删除的问题
|
||||
- 修复了管理页面设置项界面中,部分没有tooltip的选项依旧会显示图标的问题
|
||||
- 修复了在管理页面无法修改下载文件夹的问题
|
||||
- 修复了对HEIC图片无法转换格式的问题
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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: 卡片
|
||||
|
@ -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: 卡片
|
||||
|
@ -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(() => {
|
||||
|
@ -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(() => {
|
||||
|
@ -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 {
|
||||
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 {
|
||||
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,7 +167,8 @@ 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 (!Array.isArray(ctx.output) || !ctx.output.some((item: ImgInfo) => item.imgUrl)) return false
|
||||
|
||||
if (this.webContents) {
|
||||
handleTalkingData(this.webContents, {
|
||||
fromClipboard: !img,
|
||||
@ -190,13 +177,12 @@ class Uploader {
|
||||
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
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
setTimeout(() => {
|
||||
@ -216,7 +202,8 @@ class Uploader {
|
||||
try {
|
||||
const startTime = Date.now()
|
||||
const output = await picgo.upload(img)
|
||||
if (Array.isArray(output) && output.some((item: ImgInfo) => item.imgUrl)) {
|
||||
if (!Array.isArray(output) || !output.some((item: ImgInfo) => item.imgUrl)) return false
|
||||
|
||||
if (this.webContents) {
|
||||
handleTalkingData(this.webContents, {
|
||||
fromClipboard: !img,
|
||||
@ -229,9 +216,6 @@ class Uploader {
|
||||
item.config = JSON.parse(JSON.stringify(db.get(`picBed.${item.type}`)))
|
||||
})
|
||||
return output.filter(item => item.imgUrl)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
setTimeout(() => {
|
||||
|
@ -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)
|
||||
}
|
||||
if (checkLogFileIsLarge(logPath).isLarge) {
|
||||
recreateLogFile(logPath)
|
||||
}
|
||||
|
@ -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(() => {
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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(' ; '))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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!
|
||||
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)) {
|
||||
return {
|
||||
path: item
|
||||
if (isUrl(item) || path.isAbsolute(item)) return { path: item }
|
||||
|
||||
const resolvedPath = path.join(cwd, item)
|
||||
if (fs.existsSync(resolvedPath)) {
|
||||
return { path: resolvedPath }
|
||||
}
|
||||
}
|
||||
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`)
|
||||
logger.warn(`cli -> can't get file: ${resolvedPath}, invalid path`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter(item => item !== null) as Result
|
||||
return result
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export { getUploadFiles }
|
||||
|
@ -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 : '']
|
||||
}
|
||||
|
51
src/renderer/components/ImagePreSign.vue
Normal file
51
src/renderer/components/ImagePreSign.vue
Normal 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>
|
65
src/renderer/components/ImagePreSignTsx.tsx
Normal file
65
src/renderer/components/ImagePreSignTsx.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
})
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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,6 +3223,15 @@ const columns: Column<any>[] = [
|
||||
reference: () =>
|
||||
!item.isDir ? (
|
||||
currentPicBedName.value !== 'webdavplist' ? (
|
||||
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
|
||||
@ -3206,6 +3258,7 @@ const columns: Column<any>[] = [
|
||||
)
|
||||
}}
|
||||
</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 ?? '')}`)}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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 {
|
||||
|
3
src/universal/types/i18n.d.ts
vendored
3
src/universal/types/i18n.d.ts
vendored
@ -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
|
||||
|
2
src/universal/types/view.d.ts
vendored
2
src/universal/types/view.d.ts
vendored
@ -26,6 +26,8 @@ interface ISettingForm {
|
||||
yourlsDomain: string
|
||||
yourlsSignature: string
|
||||
cfWorkerHost: string
|
||||
sinkDomain: string
|
||||
sinkToken: string
|
||||
deleteLocalFile: boolean
|
||||
serverKey: string
|
||||
aesPassword: string
|
||||
|
@ -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',
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user