mirror of
https://github.com/Molunerfinn/PicGo.git
synced 2026-09-20 11:17:32 +00:00
Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cccd2954db | ||
|
|
cfb6146de5 | ||
|
|
45b3227456 | ||
|
|
2450a524ff | ||
|
|
4a29bf2b50 | ||
|
|
24e4a829d8 | ||
|
|
e0d45fa7a2 | ||
|
|
de441a892e | ||
|
|
f1817eb1e1 | ||
|
|
2bd5d0653b | ||
|
|
9841418779 | ||
|
|
7bc70628f9 | ||
|
|
a928b4c8ab | ||
|
|
7df37a2526 | ||
|
|
dacb926f17 | ||
|
|
1ea074e75f | ||
|
|
04140def7c | ||
|
|
c9fe4023f0 | ||
|
|
c8ba547edb | ||
|
|
316928edc3 | ||
|
|
ff35335126 | ||
|
|
ff7336b99e | ||
|
|
917ec73027 | ||
|
|
8e91582adc | ||
|
|
aaec99f466 | ||
|
|
50e0a64519 | ||
|
|
8cede85732 | ||
|
|
050a3dd602 | ||
|
|
cd07b33e6b | ||
|
|
6d4e1c27d5 | ||
|
|
8fc65f4dcc | ||
|
|
0f7b07dd7b | ||
|
|
dca6667a81 | ||
|
|
1b92f20a7d | ||
|
|
a2320c3503 | ||
|
|
2db0fea6ee | ||
|
|
894d0a243e | ||
|
|
536b70832e | ||
|
|
bc2e92836c | ||
|
|
a5ed1d107e | ||
|
|
e3566b5a8e | ||
|
|
64eb832e06 | ||
|
|
10e0584c96 | ||
|
|
8d9a4000f3 |
+2
-1
@@ -20,7 +20,8 @@ module.exports = {
|
||||
'no-async-promise-executor': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'@typescript-eslint/indent': ['error', 2]
|
||||
'@typescript-eslint/indent': ['error', 2],
|
||||
'vue/no-v-html': 'off'
|
||||
},
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
custom: ["https://paypal.me/Molunerfinn"]
|
||||
github: ["Molunerfinn"]
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-11]
|
||||
os: [ubuntu-latest, macos-14]
|
||||
|
||||
# create steps
|
||||
steps:
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-11]
|
||||
os: [ubuntu-latest, macos-14]
|
||||
|
||||
# create steps
|
||||
steps:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
18
|
||||
Vendored
+8
-2
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"eslint.enable": true,
|
||||
"eslint.alwaysShowStatus": true,
|
||||
"eslint.format.enable": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
@@ -20,6 +21,11 @@
|
||||
"stylusSupremacy.sortProperties": "grouped",
|
||||
"stylusSupremacy.quoteChar": "\"",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"prettier.enable": false,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
PicGo is an Electron + Vue 3 desktop client. Source lives in `src/`: `src/main` for main-process and IPC logic, `src/renderer` for Vue views, and `src/universal` for shared helpers (`types/`, `events/constants.ts`). `background.ts` wires Electron Builder. Static assets and locale YAML files stay in `public/` (add languages under `public/i18n/`), while `docs/` hosts user-facing guides. Automation scripts live in `scripts/`, and legacy tests sit under `test/unit` (Karma) and `test/e2e` (Spectron).
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- `yarn install` — install dependencies; `npm install` is unsupported because native modules are patched for Yarn.
|
||||
- `yarn dev` / `yarn electron:serve` — start the hot-reload dev environment for both processes.
|
||||
- `yarn build` / `yarn electron:build` — package the app into `dist_electron/`; set `ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/` when downloads are slow.
|
||||
- `yarn lint` / `yarn lint:fix` — run or auto-fix ESLint (Standard, TypeScript, Vue rules).
|
||||
- `yarn lint:dpdm` — fail fast on circular dependencies in `src/`.
|
||||
- `yarn gen-i18n` — regenerate typed locales after touching `public/i18n/*.yml`.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
Follow ESLint Standard defaults: two-space indentation, single quotes, trailing commas where allowed, and no stray semicolons. Author new modules in TypeScript. Keep renderer files browser-safe; route Node APIs through IPC helpers such as `src/main/events/picgoCoreIPC.ts`. Name Vue components in PascalCase (`UploadPanel.vue`) and use camelCase for utilities. Centralize IPC event names inside `src/universal/events/constants.ts`, and store enums/types under `src/universal/types/` so they stay reusable.
|
||||
|
||||
## Testing Guidelines
|
||||
Place renderer unit specs in `test/unit/specs` with the `.spec.js` suffix; Karma picks them up via `require.context`. Run them with `npx karma start test/unit/karma.conf.js --single-run` and ensure new renderer folders are covered. Spectron e2e cases live in `test/e2e/specs`; build first (`yarn build`), then run `npx mocha test/e2e/index.js` so Spectron can launch `dist/electron/main.js`. Document any test data, IPC stubs, or fixtures you add to keep suites reproducible.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
Commits follow the PicGo conventional preset enforced by Husky (`yarn lint:dpdm` + Commitlint). Stage your changes and run `yarn cz` to craft messages that pass CI. Pull requests should explain the change, link related issues, and attach UI screenshots or recordings. Note how you validated the work (dev server, build, Karma, Spectron) and call out migration or configuration steps reviewers must perform.
|
||||
|
||||
## Internationalization Tips
|
||||
Add locales by creating `public/i18n/<locale>.yml`, exposing its `LANG_DISPLAY_LABEL`, and registering it in `src/universal/i18n/index.ts`. Finish with `yarn gen-i18n` so the generated typings stay in sync.
|
||||
+111
@@ -1,3 +1,114 @@
|
||||
# :tada: 2.4.0-beta.10 (2025-06-08)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* finish form uploader for picgo server ([9841418](https://github.com/Molunerfinn/PicGo/commit/9841418))
|
||||
* **server:** add support for form upload in PicGo Server ([#1327](https://github.com/Molunerfinn/PicGo/issues/1327)) ([a928b4c](https://github.com/Molunerfinn/PicGo/commit/a928b4c)), closes [#428](https://github.com/Molunerfinn/PicGo/issues/428)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* auto-copy url can't be turned off ([#1300](https://github.com/Molunerfinn/PicGo/issues/1300)) ([dacb926](https://github.com/Molunerfinn/PicGo/commit/dacb926))
|
||||
* encoded url filename unreadable ([2bd5d06](https://github.com/Molunerfinn/PicGo/commit/2bd5d06))
|
||||
* unable to use clip in wayland ([#1301](https://github.com/Molunerfinn/PicGo/issues/1301)) ([7df37a2](https://github.com/Molunerfinn/PicGo/commit/7df37a2))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.9 (2024-12-02)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* clipboard filename missing second ([c9fe402](https://github.com/Molunerfinn/PicGo/commit/c9fe402)), closes [#1293](https://github.com/Molunerfinn/PicGo/issues/1293)
|
||||
* copy text bug ([c8ba547](https://github.com/Molunerfinn/PicGo/commit/c8ba547)), closes [#1210](https://github.com/Molunerfinn/PicGo/issues/1210) [#1280](https://github.com/Molunerfinn/PicGo/issues/1280)
|
||||
* plugin list search bug ([04140de](https://github.com/Molunerfinn/PicGo/commit/04140de)), closes [#1297](https://github.com/Molunerfinn/PicGo/issues/1297)
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* update ci macos version ([316928e](https://github.com/Molunerfinn/PicGo/commit/316928e))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.8 (2024-07-16)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* tencent cos url encode bug ([ff7336b](https://github.com/Molunerfinn/PicGo/commit/ff7336b)), closes [#1265](https://github.com/Molunerfinn/PicGo/issues/1265)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.7 (2024-04-22)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add startup mode ([aaec99f](https://github.com/Molunerfinn/PicGo/commit/aaec99f)), closes [#915](https://github.com/Molunerfinn/PicGo/issues/915)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* config page scroll bug ([8e91582](https://github.com/Molunerfinn/PicGo/commit/8e91582)), closes [#1237](https://github.com/Molunerfinn/PicGo/issues/1237)
|
||||
* tray menu open bug ([50e0a64](https://github.com/Molunerfinn/PicGo/commit/50e0a64)), closes [#1217](https://github.com/Molunerfinn/PicGo/issues/1217)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.6 (2023-11-19)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* app.asar directroy copy error ([#1180](https://github.com/Molunerfinn/PicGo/issues/1180)) ([cd07b33](https://github.com/Molunerfinn/PicGo/commit/cd07b33)), closes [#1179](https://github.com/Molunerfinn/PicGo/issues/1179)
|
||||
* can't add new config for picbed ([050a3dd](https://github.com/Molunerfinn/PicGo/commit/050a3dd)), closes [#1184](https://github.com/Molunerfinn/PicGo/issues/1184)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.5 (2023-09-10)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add gallery toolbox menu ([#1177](https://github.com/Molunerfinn/PicGo/issues/1177)) ([0f7b07d](https://github.com/Molunerfinn/PicGo/commit/0f7b07d))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.4 (2023-08-26)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add configName for upload page ([894d0a2](https://github.com/Molunerfinn/PicGo/commit/894d0a2))
|
||||
* support "tips" option for uploader ([1b92f20](https://github.com/Molunerfinn/PicGo/commit/1b92f20))
|
||||
* **tcyun:** add slim section ([#1165](https://github.com/Molunerfinn/PicGo/issues/1165)) ([a2320c3](https://github.com/Molunerfinn/PicGo/commit/a2320c3))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* open config file bug ([2db0fea](https://github.com/Molunerfinn/PicGo/commit/2db0fea)), closes [#1163](https://github.com/Molunerfinn/PicGo/issues/1163)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.3 (2023-07-09)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* rename page bug ([bc2e928](https://github.com/Molunerfinn/PicGo/commit/bc2e928)), closes [#1130](https://github.com/Molunerfinn/PicGo/issues/1130)
|
||||
* tailwind css bug ([e3566b5](https://github.com/Molunerfinn/PicGo/commit/e3566b5))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.2 (2023-07-09)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* fileName encode bug ([8d9a400](https://github.com/Molunerfinn/PicGo/commit/8d9a400)), closes [#1121](https://github.com/Molunerfinn/PicGo/issues/1121)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.1 (2023-05-03)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
<div align="center" markdown="1">
|
||||
<sup>Special thanks to:</sup>
|
||||
<br>
|
||||
<a href="https://go.warp.dev/picgo">
|
||||
<img alt="Warp sponsorship" width="400" src="https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Github/Sponsor/Warp-Github-LG-03.png">
|
||||
</a>
|
||||
|
||||
### [Warp, the intelligent terminal for developers](https://go.warp.dev/picgo)
|
||||
[Available for MacOS, Linux, & Windows](https://go.warp.dev/picgo)<br>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/New%20LOGO-150.png" alt="">
|
||||
<h1>PicGo</h1>
|
||||
@@ -54,15 +68,14 @@ PicGo 本体支持如下图床:
|
||||
|
||||
## 下载安装
|
||||
|
||||
| 下载源 | 地址/安装方式 | 平台 | 备注 |
|
||||
|---|---|---|---|
|
||||
| GitHub Release | https://github.com/Molunerfinn/PicGo/releases | All | 国内下载速度可能会慢 |
|
||||
| [腾讯云COS](https://cloud.tencent.com/product/cos) | https://github.com/Molunerfinn/PicGo/releases 附在更新日志结尾 | All | 感谢 [腾讯云COS](https://cloud.tencent.com/product/cos) 提供的赞助支持 |
|
||||
| [山东大学镜像站](https://mirrors.sdu.edu.cn/) | https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo | All | 感谢 [山东大学镜像站](https://mirrors.sdu.edu.cn/) 提供的镜像支持 |
|
||||
| [Scoop](https://scoop.sh/) | `scoop bucket add helbing https://github.com/helbing/scoop-bucket` & `scoop install picgo` | Windows | 感谢 @helbing 的贡献 |
|
||||
| [Chocolatey](https://chocolatey.org/) | `choco install picgo` | Windows | 感谢 @iYato 的贡献 |
|
||||
| [Homebrew](https://brew.sh/) | `brew install picgo --cask` | macOS | 感谢 @womeimingzi11 的贡献 |
|
||||
| [AUR](https://aur.archlinux.org/packages/yay) | `yay -S picgo-appimage` | Arch-Linux | 感谢 @houbaron 的贡献 |
|
||||
| 下载源 | 地址/安装方式 | 平台 | 备注 |
|
||||
| --------------------------------------------- | ----------------------------------------------------------- | ---------- | ----------------------------------------------------------------- |
|
||||
| GitHub Release | https://github.com/Molunerfinn/PicGo/releases | All | 国内下载速度可能会慢 |
|
||||
| [山东大学镜像站](https://mirrors.sdu.edu.cn/) | https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo | All | 感谢 [山东大学镜像站](https://mirrors.sdu.edu.cn/) 提供的镜像支持 |
|
||||
| [Scoop](https://scoop.sh/) | `scoop bucket add extras` & `scoop install picgo` | Windows | 感谢 @huangnauh 和 @Gladtbam 的贡献 |
|
||||
| [Chocolatey](https://chocolatey.org/) | `choco install picgo` | Windows | 感谢 @iYato 的贡献 |
|
||||
| [Homebrew](https://brew.sh/) | `brew install picgo --cask` | macOS | 感谢 @womeimingzi11 的贡献 |
|
||||
| [AUR](https://aur.archlinux.org/packages/yay) | `yay -S picgo-appimage` | Arch-Linux | 感谢 @houbaron 的贡献 |
|
||||
|
||||
## 应用截图
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
# PicGo 2.4.0 Changelog
|
||||
|
||||
## Features
|
||||
- 新增 相册页新增文件名展示,参考 #1050
|
||||
- 新增 相册中和顶部栏窗口中无法展示的图片或者 url 将会展示默认图片,参考#1050
|
||||

|
||||
- 新增 macOS 顶部栏窗口新增文件名展示,参考 #1054
|
||||
- 新增 同种类型图床支持多份配置,上传时可以指定某一份配置进行上传。感谢 @STDSuperman ,参考 #1016
|
||||

|
||||
- 新增 选择图床的菜单可以支持选择该图床类型某一项具体配置
|
||||

|
||||
- 新增 显示 dock 栏图标选项,参考 #1045
|
||||
<img width="787" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/bb9492f1-6522-45ce-ae5d-c614901d8b06" />
|
||||
- 新增 PicGo 修复工具箱,可以自行排查一些问题
|
||||
<img width="324" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/98cf6e64-7313-4ebe-83d0-aa86e4514109" />
|
||||
- 新增 对输出的 URL 进行 encode 转义的选项,参考 #731
|
||||
<img width="775" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/6e0c13dd-3404-4b07-9c99-412321835b27" />
|
||||
- 新增 支持拖拽任意格式文件上传,参考 #1052
|
||||
- 新增 腾讯云 COS 支持 `Endpoint` 配置 和 `极智压缩` 配置。 感谢 @palmcivet @yc910920 的贡献!
|
||||

|
||||
- 新增 `tips` 配置渲染的支持,可以动态渲染 Uploader config 的 tips。支持 markdown 格式。参考 [tcyun uploader config](https://github.com/PicGo/PicGo-Core/blob/dev/src/plugins/uploader/tcyun.ts#L280)
|
||||

|
||||
- 新增 上传界面展示当前图床使用的配置名
|
||||

|
||||
- 新增 相册页工具栏,目前内置 `批量修改图片 URL HOST 的功能` 。参考 #875
|
||||
注意:需要先选中指定的图片,然后会根据已选中的图片进行修改,你可以通过图床筛选功能只筛选出需要修改的图片。
|
||||
例如,你有一批图片都是 `https://www.a.com/...` 打头的 URL,你想把 `www.a.com` 批量修改成 `www.b.com` ,就可以用这个功能
|
||||

|
||||
- 新增 `启动模式`,可以设置启动的时候是否要打开窗口。全平台支持 `静默启动`(默认值) & `打开主窗口`,Windows 和 Linux 额外支持 `打开 Mini 窗口`。参考 #915
|
||||
<img width="577" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/7e63bb5c-44b0-480e-824f-3c2edbed4fbb" />
|
||||
- 新增 PicGo Server 支持表单形式上传图片。参考 #428 ,感谢 @happy-game
|
||||
表单字段为 files。参考截图:
|
||||
<img width="951" alt="image" src="https://github.com/user-attachments/assets/14244f1d-60f5-487f-bde6-6d0e009645fb" />
|
||||
|
||||
## Bug Fixes
|
||||
- 修复 windows 右键菜单配置生成脚本,参考 #1019
|
||||
- 修复 自定义链接 URL encode 问题,参考 #1112
|
||||
- 修复 日志写入可能存在死循环问题,参考 #1101
|
||||
- 修复 拖拽文件到顶部栏图标报错问题,参考 #1107
|
||||
- 修复 文件名 encode 问题。参考 #1121
|
||||
- 修复 GitHub 重名文件上传不再报错。
|
||||
- 修复 beta.2 版本部分样式问题
|
||||
- 修复 重命名窗口某些情况下显示文件名过慢的问题,参考 #1130
|
||||
- 修复 打开配置文件打开的是日志文件的 bug,参考 #1163
|
||||
- 修复 插件配置弹窗打开后无法正确读取和保存配置的问题。
|
||||
- 修复 无法新增图床配置的问题(新增变成了编辑)。参考 #1198,#1196
|
||||
- 修复 macOS 右键菜单消失问题。感谢 @muwoo 。参考 #1179
|
||||
- 修复 macOS 顶部栏窗口右键之后一闪而过的问题。 感谢 @QThans。 参考 #1217
|
||||
- 修复 图床配置页面配置项过多时无法滚动到底部的问题。参考 #1237
|
||||
- 修复 腾讯云 COS URL encode 的问题。参考 #1265
|
||||
- 修复 插件列表无法搜索的问题。 参考 #1297
|
||||
- 修复 剪贴板文件名丢失 `秒` 的问题。 参考 #1293
|
||||
- 修复 macOS 顶部栏点击图片复制链接失效的问题。 参考 #1280 , #1210
|
||||
- 修复 自动复制 URL 这个开关无法被关闭的问题。参考 #1294 ,感谢 @happy-game
|
||||
- 修复 wayland 里无法使用剪贴板图片的问题。参考 #1261 ,感谢 @happy-game
|
||||
- 修复 直接通过 URL 上传图片的时候,带有汉字的 URL 上传后,文件名被 encode 的问题。 参考 #1339
|
||||
|
||||
## Other
|
||||
- 由于 PicGo 存储的 COS 空间被恶意刷大量流量导致欠费,暂时停止 COS 渠道的 PicGo 分发。
|
||||
- 更新至 Vue3
|
||||
- 重构部分代码
|
||||
- 重构部分代码
|
||||
- 补充更详细的快捷键相关日志,参考 #1031
|
||||
- 已知问题:部分按钮样式有点问题,下个版本会修复
|
||||
- 更新 README中的 scoop 下载安装的部分,感谢 @wuhang2003
|
||||
|
||||
----------
|
||||
|
||||
# PicGo 2.4.0 Changelog
|
||||
|
||||
## Features
|
||||
- Add filename display in the gallery (#1050).
|
||||
- Add default placeholders when images/URLs cannot be shown in the gallery or tray window (#1050).
|
||||

|
||||
- Add filename display in the macOS tray window (#1054).
|
||||
- Allow multiple configs per uploader type and choose one for upload (thanks @STDSuperman, #1016).
|
||||

|
||||
- Allow the uploader selection menu to pick a specific config entry for that uploader type.
|
||||

|
||||
- Add Dock icon visibility option (#1045).
|
||||
<img width="787" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/bb9492f1-6522-45ce-ae5d-c614901d8b06" />
|
||||
- Add PicGo Repair Toolbox for self-diagnosis.
|
||||
<img width="324" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/98cf6e64-7313-4ebe-83d0-aa86e4514109" />
|
||||
- Add option to encode/escape output URLs (#731).
|
||||
<img width="775" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/6e0c13dd-3404-4b07-9c99-412321835b27" />
|
||||
- Support drag-and-drop uploads for arbitrary file formats (#1052).
|
||||
- Tencent COS: add Endpoint and Intelligent Compression settings (thanks @palmcivet @yc910920).
|
||||

|
||||
- Add Markdown-rendered tips for uploader config (see tcyun uploader config).
|
||||

|
||||
- Show the active uploader config name in the upload screen.
|
||||

|
||||
- Gallery toolbar: bulk replace image URL host (#875).
|
||||
Note: select images first; you can filter by uploader; e.g., replace `https://www.a.com/...` with `https://www.b.com/...`.
|
||||

|
||||
- Add “Startup Mode”: Silent (default) / Main Window; Windows/Linux also support Mini Window (#915).
|
||||
<img width="577" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/7e63bb5c-44b0-480e-824f-3c2edbed4fbb" />
|
||||
- PicGo Server: support multipart form uploads (field `files`, #428, thanks @happy-game).
|
||||
<img width="951" alt="image" src="https://github.com/user-attachments/assets/14244f1d-60f5-487f-bde6-6d0e009645fb" />
|
||||
|
||||
## Bug Fixes
|
||||
- Fix Windows context-menu config script generation (#1019).
|
||||
- Fix custom link URL encoding (#1112).
|
||||
- Fix potential logging dead loop (#1101).
|
||||
- Fix drag-to-tray upload error (#1107).
|
||||
- Fix filename encoding (#1121).
|
||||
- Prevent errors on GitHub uploads with duplicate filenames.
|
||||
- Fix beta.2 style issues.
|
||||
- Fix slow filename display in the rename window (#1130).
|
||||
- Fix “open config file” actually opening the log file (#1163).
|
||||
- Fix plugin config dialog not reading/saving correctly.
|
||||
- Fix inability to add new uploader configs (created as edit) (#1198, #1196).
|
||||
- Fix macOS context menu disappearing (thanks @muwoo, #1179).
|
||||
- Fix macOS tray window flashing after right-click (thanks @QThans, #1217).
|
||||
- Fix uploader config page not scrollable when many fields (#1237).
|
||||
- Fix Tencent COS URL encoding (#1265).
|
||||
- Fix plugin list search not working (#1297).
|
||||
- Fix clipboard filename losing “seconds” (#1293).
|
||||
- Fix macOS tray copy-link failing on click (#1280, #1210).
|
||||
- Fix auto-copy URL toggle not turning off (#1294, thanks @happy-game).
|
||||
- Fix Wayland clipboard image upload issue (#1261, thanks @happy-game).
|
||||
- Fix URL uploads with Chinese characters being encoded in filenames (#1339).
|
||||
|
||||
## Other
|
||||
- COS distribution paused due to malicious traffic charges.
|
||||
- Upgrade to Vue3.
|
||||
- Refactor parts of the code.
|
||||
- Refactor parts of the code (again).
|
||||
- Add more detailed hotkey logs (#1031).
|
||||
- Known issue: some button styles were incorrect (fixed in later betas).
|
||||
- Update README scoop installation section (thanks @wuhang2003).
|
||||
@@ -0,0 +1,48 @@
|
||||
# gen_changelog.md
|
||||
|
||||
This guide describes how to generate a consolidated changelog for any PicGo release series (e.g., 2.4.0, 2.5.0) from the GitHub release notes of its betas and finals.
|
||||
|
||||
## Source of truth
|
||||
- Use GitHub release pages only (e.g., `https://github.com/Molunerfinn/PicGo/releases/tag/vX.Y.Z-beta.N` and `vX.Y.Z` if present). You can fetch these via MCP GitHub APIs (e.g., `get_release_by_tag`, `list_releases`) instead of manual browsing.
|
||||
- Do **not** use `CHANGELOG.md` in the repo.
|
||||
- Preserve all text and images exactly as written in the releases.
|
||||
|
||||
## Structure
|
||||
- Target file: `changelog/X.Y.Z.md` (replace with the series version).
|
||||
- Three top-level sections: `## Features`, `## Bug Fixes`, `## Other`.
|
||||
- Do **not** nest content under individual beta version headers; merge all items into these sections.
|
||||
- Keep items in chronological order (early → late) within each section, mirroring the beta sequence.
|
||||
- Keep inline issue references, thanks, and notes as-is.
|
||||
- Remove download-link sections entirely.
|
||||
- After the Chinese sections, insert a line with `----------`, then add a full English translation with the same structure/content (including images).
|
||||
|
||||
## Formatting rules
|
||||
- Markdown, plain ASCII.
|
||||
- Bullet lists only; no numbered lists required.
|
||||
- Indent images under their bullet with two spaces to keep association clear.
|
||||
- Keep inline HTML image tags from the releases (e.g., `<img width="...">`) untouched.
|
||||
- Keep line breaks and multi-line notes intact.
|
||||
- Do not reword or summarize; copy content verbatim except for removing beta headers and download sections.
|
||||
- English translation must mirror the Chinese bullets in order and content (keep images alongside the translated bullets).
|
||||
|
||||
## Regeneration steps
|
||||
1) Collect all release bodies for the target series (e.g., `vX.Y.Z-beta.0…N` and, if present, `vX.Y.Z`) from GitHub releases.
|
||||
2) For each release, copy bullets and images into the appropriate section:
|
||||
- Features ↔ “Feature(s)” or “Features” blocks.
|
||||
- Bug Fixes ↔ “Bug Fixes” blocks.
|
||||
- Other ↔ “Other”, “Notice”, or misc notes that are not features/bugs.
|
||||
3) Omit “国内可下载链接” (or any download links) entirely.
|
||||
4) Drop beta subheadings; keep only section-level bullets in chronological order.
|
||||
5) Ensure images remain adjacent to their bullets with indentation.
|
||||
6) After the Chinese sections are complete, insert `----------` on its own line.
|
||||
7) Append the English translation, preserving bullet order and images, under `## Features`, `## Bug Fixes`, `## Other` again (same section titles, just English content; no “(English)” suffix).
|
||||
8) Save the result to `changelog/X.Y.Z.md`.
|
||||
|
||||
## Quick checklist
|
||||
- [ ] All features present with images kept.
|
||||
- [ ] All bug fixes present.
|
||||
- [ ] All “Other” notes present.
|
||||
- [ ] No beta headers.
|
||||
- [ ] No download links.
|
||||
- [ ] Chronological ordering preserved.
|
||||
- [ ] English translation present with matching bullets/images after `----------`.
|
||||
+19
-10
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "picgo",
|
||||
"version": "2.4.0-beta.1",
|
||||
"version": "2.4.0-beta.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vue-cli-service electron:build",
|
||||
@@ -15,31 +15,34 @@
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postuninstall": "electron-builder install-app-deps",
|
||||
"release": "vue-cli-service electron:build --publish always",
|
||||
"upload-dist": "node ./scripts/upload-dist.js"
|
||||
"upload-dist": "node ./scripts/upload-dist.js",
|
||||
"lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/background.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"@picgo/i18n": "^1.0.0",
|
||||
"@picgo/store": "^2.0.4",
|
||||
"@picgo/store": "^2.1.0",
|
||||
"axios": "^0.19.0",
|
||||
"compare-versions": "^4.1.3",
|
||||
"core-js": "^3.27.1",
|
||||
"element-plus": "^2.2.28",
|
||||
"element-plus": "^2.3.7",
|
||||
"epipebomb": "^1.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"keycode": "^2.2.0",
|
||||
"lodash-id": "^0.14.0",
|
||||
"lowdb": "^1.0.0",
|
||||
"marked": "^7.0.4",
|
||||
"mitt": "^3.0.0",
|
||||
"picgo": "^1.5.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"picgo": "^1.5.11",
|
||||
"qrcode.vue": "^3.3.3",
|
||||
"shell-path": "2.1.0",
|
||||
"uuidv4": "^6.2.11",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.2",
|
||||
"vue3-lazyload": "^0.3.6",
|
||||
"vue3-photo-preview": "^0.2.9",
|
||||
"vue3-photo-preview": "^0.3.0",
|
||||
"write-file-atomic": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -52,10 +55,12 @@
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/lowdb": "^1.0.9",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^16.10.2",
|
||||
"@types/request-promise-native": "^1.0.17",
|
||||
"@types/semver": "^7.3.8",
|
||||
"@types/tunnel": "^0.0.3",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@types/write-file-atomic": "^4.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
@@ -67,9 +72,11 @@
|
||||
"@vue/eslint-config-standard": "^8.0.1",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"@vue/runtime-dom": "^3.2.45",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"conventional-changelog": "^3.1.18",
|
||||
"cz-customizable": "^6.2.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"dpdm": "^3.13.1",
|
||||
"electron": "^16.0.6",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint": "^8.31.0",
|
||||
@@ -79,8 +86,10 @@
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"husky": "^3.1.0",
|
||||
"postcss": "^8.4.23",
|
||||
"stylus": "^0.54.7",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^4.4.3",
|
||||
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4"
|
||||
},
|
||||
@@ -99,7 +108,7 @@
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
"commit-msg": "npm run lint:dpdm && commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
autoprefixer: {},
|
||||
tailwindcss: {}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-6
@@ -1,4 +1,4 @@
|
||||
LANG_DISPLAY_LABEL: 'English'
|
||||
LANG_DISPLAY_LABEL: "English"
|
||||
ABOUT: About
|
||||
OPEN_MAIN_WINDOW: Open Main Window
|
||||
CHOOSE_DEFAULT_PICBED: Choose Default Picbed
|
||||
@@ -28,7 +28,7 @@ OPEN_TOOLBOX: Open Toolbox
|
||||
|
||||
# ---renderer i18n begin---
|
||||
|
||||
CHOOSE_YOUR_DEFAULT_PICBED: 'Choose ${d} as your default picbed:'
|
||||
CHOOSE_YOUR_DEFAULT_PICBED: "Choose ${d} as your default picbed:"
|
||||
UPLOAD_AREA: Upload Area
|
||||
GALLERY: Gallery
|
||||
PICBEDS_SETTINGS: Picbeds Settings
|
||||
@@ -56,6 +56,8 @@ BATCH_COPY_LINK_SUCCEED: Batch Copy Link Succeed
|
||||
FILE_RENAME: File Rename
|
||||
COPY_FILE_PATH: Copy file path
|
||||
OPEN_FILE_PATH: Open file path
|
||||
SUCCESS: Success
|
||||
FAILED: Failed
|
||||
|
||||
# settings
|
||||
|
||||
@@ -83,7 +85,7 @@ SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD: Auto Copy URL After Upload
|
||||
SETTINGS_TIPS_PLACEHOLDER_URL: Use $url to represent url position
|
||||
SETTINGS_TIPS_PLACEHOLDER_FILENAME: Use $fileName to represent file name position
|
||||
SETTINGS_TIPS_PLACEHOLDER_EXTNAME: Use $extName to represent file's ext position
|
||||
SETTINGS_TIPS_SUCH_AS: 'Such as: $url/$fileName'
|
||||
SETTINGS_TIPS_SUCH_AS: "Such as: $url/$fileName"
|
||||
SETTINGS_UPLOAD_PROXY: Upload Proxy
|
||||
SETTINGS_PLUGIN_INSTALL_PROXY: Proxy for Plugin Install
|
||||
SETTINGS_PLUGIN_INSTALL_MIRROR: Mirror for Plugin Install
|
||||
@@ -119,6 +121,10 @@ UPLOADER_CONFIG_PLACEHOLDER: Please Enter Configuration Name
|
||||
SELECTED_SETTING_HINT: Selected
|
||||
SETTINGS_ENCODE_OUTPUT_URL: Encode Output(or Copyed) URL
|
||||
SETTINGS_SHOW_DOCK_ICON: Show Dock icon
|
||||
SETTINGS_STARTUP_MODE: Startup Mode
|
||||
SETTINGS_STARTUP_MODE_MAIN_WINDOW: Open Main Window
|
||||
SETTINGS_STARTUP_MODE_MINI_WINDOW: Open Mini Window
|
||||
SETTINGS_STARTUP_MODE_HIDE: Silent Startup
|
||||
|
||||
# shortcut-page
|
||||
|
||||
@@ -134,6 +140,13 @@ SHORTCUT_DISABLE: Disable
|
||||
SHORTCUT_EDIT: Edit
|
||||
SHORTCUT_CHANGE_UPLOAD: Change Upload Shortcut
|
||||
|
||||
# gallery-page
|
||||
CHANGE_IMAGE_URL_HOST: Change Image URL HOST
|
||||
NEW_IMAGE_URL_HOST: New Image URL HOST
|
||||
SELECTED_IMAGE_URL_HOST: Selected Image URL HOST
|
||||
CHANGE_IMAGE_URL_HOST_RESULT: The result of changing image URL HOST
|
||||
CHANGE_IMAGE_URL_HOST_WARN: You must select at least one picture first
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: Wait to Upload
|
||||
@@ -193,10 +206,10 @@ UPDATE_PLUGIN: Update Plugin
|
||||
|
||||
# toolbox
|
||||
TOOLBOX: Toolbox
|
||||
TOOLBOX_TITLE: Troubleshoot PicGo runtime issues
|
||||
TOOLBOX_TITLE: Troubleshoot PicGo runtime issues
|
||||
TOOLBOX_SUB_TITLE: Scan the following items immediately to fix usage issues
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN: Check if the configuration file is damaged
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN: Check if the album file is damaged
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN: Check if the album file is damaged
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD: Check if there is a problem with clipboard picture upload
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_PROXY: Check if the proxy settings are normal
|
||||
TOOLBOX_FIX_DONE_NEED_RELOAD: Repair completed, need to restart to take effect, restart or not
|
||||
@@ -227,7 +240,7 @@ TIPS_PLUGIN_OVERWRITE_GALLERY: Plugin is trying to overwrite the album gallery,
|
||||
TIPS_UPLOAD_NOT_PICTURES: The latest clipboard item is not a picture
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo config file broken, has been restored to default
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo config file broken, has been restored to backup
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: 'Backup file version: ${v}'
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: "Backup file version: ${v}"
|
||||
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: Custom config file parse error, please check the path content
|
||||
TIPS_SHORTCUT_MODIFIED_SUCCEED: Shortcut modified successfully
|
||||
TIPS_SHORTCUT_MODIFIED_CONFLICT: Shortcut conflict, please reset
|
||||
|
||||
+14
-1
@@ -56,6 +56,8 @@ BATCH_COPY_LINK_SUCCEED: 批量复制链接成功
|
||||
FILE_RENAME: 文件改名
|
||||
COPY_FILE_PATH: 复制文件路径
|
||||
OPEN_FILE_PATH: 打开文件路径
|
||||
SUCCESS: 成功
|
||||
FAILED: 失败
|
||||
|
||||
# settings
|
||||
|
||||
@@ -119,6 +121,10 @@ UPLOADER_CONFIG_PLACEHOLDER: 请输入配置名称
|
||||
SELECTED_SETTING_HINT: 已选中
|
||||
SETTINGS_ENCODE_OUTPUT_URL: 输出(复制) URL 时进行转义
|
||||
SETTINGS_SHOW_DOCK_ICON: 显示 Dock 栏图标
|
||||
SETTINGS_STARTUP_MODE: 启动模式
|
||||
SETTINGS_STARTUP_MODE_MAIN_WINDOW: 打开主窗口
|
||||
SETTINGS_STARTUP_MODE_MINI_WINDOW: 打开 Mini 窗口
|
||||
SETTINGS_STARTUP_MODE_HIDE: 静默启动
|
||||
|
||||
# shortcut-page
|
||||
|
||||
@@ -134,6 +140,13 @@ SHORTCUT_DISABLE: 禁用
|
||||
SHORTCUT_EDIT: 编辑
|
||||
SHORTCUT_CHANGE_UPLOAD: 修改上传快捷键
|
||||
|
||||
# gallery-page
|
||||
CHANGE_IMAGE_URL_HOST: 修改图片 URL HOST
|
||||
NEW_IMAGE_URL_HOST: 新的图片 URL HOST
|
||||
SELECTED_IMAGE_URL_HOST: 已选中的图片 URL HOST
|
||||
CHANGE_IMAGE_URL_HOST_RESULT: 修改图片 URL HOST 结果
|
||||
CHANGE_IMAGE_URL_HOST_WARN: 你必须先选中至少一张图片
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: 等待上传
|
||||
@@ -227,7 +240,7 @@ TIPS_PLUGIN_OVERWRITE_GALLERY: 有插件正在试图覆盖相册列表,是否
|
||||
TIPS_UPLOAD_NOT_PICTURES: 剪贴板最新的一条记录不是图片
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo 配置文件损坏,已经恢复为默认配置
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo 配置文件损坏,已经恢复为备份配置
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: '备份文件版本: ${v}'
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: "备份文件版本: ${v}"
|
||||
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: 自定义文件解析出错,请检查路径内容是否正确
|
||||
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷键已经修改成功
|
||||
TIPS_SHORTCUT_MODIFIED_CONFLICT: 快捷键冲突,请重新设置
|
||||
|
||||
+20
-7
@@ -56,6 +56,8 @@ BATCH_COPY_LINK_SUCCEED: 批量複製連結成功
|
||||
FILE_RENAME: 檔案改名
|
||||
COPY_FILE_PATH: 複製檔案路徑
|
||||
OPEN_FILE_PATH: 打開檔案路徑
|
||||
SUCCESS: 成功
|
||||
FAILED: 失敗
|
||||
|
||||
# settings
|
||||
|
||||
@@ -119,6 +121,10 @@ UPLOADER_CONFIG_PLACEHOLDER: 請輸入配置名稱
|
||||
SELECTED_SETTING_HINT: 已選中
|
||||
SETTINGS_ENCODE_OUTPUT_URL: 輸出(複製) URL 時進行轉義
|
||||
SETTINGS_SHOW_DOCK_ICON: 顯示 Dock 欄圖示
|
||||
SETTINGS_STARTUP_MODE: 啟動模式
|
||||
SETTINGS_STARTUP_MODE_MAIN_WINDOW: 打開主視窗
|
||||
SETTINGS_STARTUP_MODE_MINI_WINDOW: 打開 Mini 視窗
|
||||
SETTINGS_STARTUP_MODE_HIDE: 靜默啟動
|
||||
|
||||
# shortcut-page
|
||||
|
||||
@@ -134,6 +140,13 @@ SHORTCUT_DISABLE: 禁用
|
||||
SHORTCUT_EDIT: 編輯
|
||||
SHORTCUT_CHANGE_UPLOAD: 修改上傳快捷鍵
|
||||
|
||||
# gallery-page
|
||||
CHANGE_IMAGE_URL_HOST: 修改圖片 URL HOST
|
||||
NEW_IMAGE_URL_HOST: 新的圖片 URL HOST
|
||||
SELECTED_IMAGE_URL_HOST: 已選中的圖片 URL HOST
|
||||
CHANGE_IMAGE_URL_HOST_RESULT: 修改圖片 URL HOST 結果
|
||||
CHANGE_IMAGE_URL_HOST_WARN: 你必須先選中至少一張圖片
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: 等待上傳
|
||||
@@ -193,27 +206,27 @@ UPDATE_PLUGIN: 更新插件
|
||||
|
||||
# toolbox
|
||||
TOOLBOX: 工具箱
|
||||
TOOLBOX_TITLE: 排查 PicGo 執行時問題
|
||||
TOOLBOX_TITLE: 排查 PicGo 執行時問題
|
||||
TOOLBOX_SUB_TITLE: 立即掃描以下項目,修復使用問題
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN: 檢查配置文件是否損壞
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN: 檢查相冊文件是否損壞
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD: 檢查剪貼板圖片上傳是否存在問題
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_PROXY: 檢查代理設置是否正常
|
||||
TOOLBOX_FIX_DONE_NEED_RELOAD: 修復完成,需要重啓生效,是否重啓
|
||||
TOOLBOX_CANT_AUTO_FIX: 無法自動修復,請自行修復以下問題
|
||||
TOOLBOX_CANT_AUTO_FIX: 無法自動修復,請自行修復以下問題
|
||||
TOOLBOX_START_SCAN: 開始掃描
|
||||
TOOLBOX_RE_SCAN: 重新掃描
|
||||
TOOLBOX_START_FIX: 開始修復
|
||||
TOOLBOX_SUCCESS_TIPS: 恭喜你,沒有檢查出問題
|
||||
TOOLBOX_CHECK_CONFIG_FILE_PATH_TIPS: 配置文件路徑是:${path}
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN_TIPS: 配置文件已損壞
|
||||
TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS: 相冊文件路徑是:${path}
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN_TIPS: 配置文件已損壞
|
||||
TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS: 相冊文件路徑是:${path}
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN_TIPS: 相冊文件已損壞
|
||||
TOOLBOX_CHECK_PROXY_SUCCESS_TIPS: 代理設置正常
|
||||
TOOLBOX_CHECK_PROXY_SUCCESS_TIPS: 代理設置正常
|
||||
TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS: 無代理設置
|
||||
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT: 代理設置不正確
|
||||
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING: 代理設置不可用
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_TIPS: 剪貼板圖片臨時文件夾路徑是:${path}
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_TIPS: 剪貼板圖片臨時文件夾路徑是:${path}
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS: 剪貼板圖片臨時文件夾不存在:${path}
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: 請自行創建文件夾:${path}
|
||||
|
||||
@@ -227,7 +240,7 @@ TIPS_PLUGIN_OVERWRITE_GALLERY: 有插件正在試圖覆蓋相簿列表,是否
|
||||
TIPS_UPLOAD_NOT_PICTURES: 剪貼簿最新的一條記錄不是圖片
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo 設定檔案已損壞,已經恢復為預設設定
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo 設定檔案已損壞,已經恢復為備份設定
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: '備份檔案版本: ${v}'
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: "備份檔案版本: ${v}"
|
||||
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: 自訂設定檔案解析出錯,請檢查路徑內容是否正確
|
||||
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷鍵已經修改成功
|
||||
TIPS_SHORTCUT_MODIFIED_CONFLICT: 快捷鍵衝突,請重新設定
|
||||
|
||||
+12
-12
@@ -17,18 +17,18 @@ if [ "$XDG_SESSION_TYPE" = "x11" ]; then
|
||||
echo $filePath
|
||||
fi
|
||||
elif [ "$XDG_SESSION_TYPE" = "wayland" ]; then
|
||||
command -v wl-copy >/dev/null 2>&1 || { echo >&1 "no wl-clipboard"; exit 1; }
|
||||
filePath=`wl-copy -o 2>/dev/null | grep ^file:// | cut -c8-`
|
||||
if [ ! -n "$filePath" ] ;then
|
||||
if
|
||||
wl-copy -t image/png -o >/dev/null 2>&1
|
||||
then
|
||||
wl-copy -t image/png image/png -o >$1 2>/dev/null
|
||||
echo $1
|
||||
else
|
||||
echo "no image"
|
||||
fi
|
||||
command -v wl-paste >/dev/null 2>&1 || { echo >&1 "no wl-clipboard"; exit 1; }
|
||||
isImage=`wl-paste --list-types | grep image`
|
||||
if [ -n "$isImage" ]; then
|
||||
wl-paste --type image/png > $1 2>/dev/null
|
||||
echo $1
|
||||
else
|
||||
echo $filePath
|
||||
echo "no image"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# fallback for unsupported session types
|
||||
echo >&2 "Error: Unsupported session type '$XDG_SESSION_TYPE'."
|
||||
echo >&2 "Solution: The variable of XDG_SESSION_TYPE must set as 'x11' or 'wayland'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import './renderer/assets/css/tailwind.css'
|
||||
import { createApp } from 'vue'
|
||||
import App from './renderer/App.vue'
|
||||
import router from './renderer/router'
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { SHORTKEY_COMMAND_UPLOAD } from '../../core/bus/constants'
|
||||
import { uploadClipboardFiles } from '../uploader/apis'
|
||||
|
||||
export const BuiltinShortKeyMap: Record<string, FN> = {
|
||||
[SHORTKEY_COMMAND_UPLOAD]: uploadClipboardFiles
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import db from '~/main/apis/core/datastore'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import shortKeyService from './shortKeyService'
|
||||
import picgo from '@core/picgo'
|
||||
import { BuiltinShortKeyMap } from './builtin'
|
||||
import { SHORTKEY_BUILTIN_PREFIX } from '../../core/bus/constants'
|
||||
|
||||
class ShortKeyHandler {
|
||||
private isInModifiedMode: boolean = false
|
||||
@@ -29,10 +31,14 @@ class ShortKeyHandler {
|
||||
.forEach(command => {
|
||||
const config = commands[command]
|
||||
// if disabled, don't register #534
|
||||
logger.info(`register builtin shortKey command: [${command}] - [${config.key}]`)
|
||||
if (config.enable) {
|
||||
logger.info(`register builtin shortKey command: [${command}] - [${config.key}] successfully`)
|
||||
globalShortcut.register(config.key, () => {
|
||||
this.handler(command)
|
||||
})
|
||||
} else {
|
||||
logger.warn(`builtin shortKey command: [${command}] - [${config.key}] register failed, it's disabled`)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -76,7 +82,7 @@ class ShortKeyHandler {
|
||||
} else {
|
||||
logger.warn(`${command} do not provide a key to bind`)
|
||||
}
|
||||
// if the configfile already had this command
|
||||
// if the config file already had this command
|
||||
// then writeFlag -> false
|
||||
if (writeFlag) {
|
||||
picgo.saveConfig({
|
||||
@@ -130,17 +136,25 @@ class ShortKeyHandler {
|
||||
|
||||
private async handler (command: string) {
|
||||
if (this.isInModifiedMode) {
|
||||
logger.warn(`in modified mode, ignore shortKey command: [${command}]`)
|
||||
return
|
||||
}
|
||||
if (command.includes('picgo:')) {
|
||||
bus.emit(command)
|
||||
if (command.includes(SHORTKEY_BUILTIN_PREFIX)) {
|
||||
const handler = BuiltinShortKeyMap[command]
|
||||
if (handler) {
|
||||
logger.info(`get builtin shortKey handler for command: [${command}]`)
|
||||
return handler()
|
||||
} else {
|
||||
logger.warn(`can't find builtin shortKey handler for command: [${command}]`)
|
||||
}
|
||||
} else if (command.includes('picgo-plugin-')) {
|
||||
const handler = shortKeyService.getShortKeyHandler(command)
|
||||
if (handler) {
|
||||
logger.info(`get plugin shortKey handler for command: [${command}]`)
|
||||
return handler(picgo, GuiApi.getInstance())
|
||||
}
|
||||
} else {
|
||||
logger.warn(`can not find command: ${command}`)
|
||||
logger.warn(`can not find command: [${command}]`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import pkg from 'root/package.json'
|
||||
import { ensureFilePath, handleCopyUrl } from '~/main/utils/common'
|
||||
import { privacyManager } from '~/main/utils/privacyManager'
|
||||
// import { T } from '#/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
import { isMacOSVersionGreaterThanOrEqualTo } from '~/main/utils/getMacOSVersion'
|
||||
import { buildPicBedListMenu } from '~/main/events/remotes/menu'
|
||||
import { isLinux, isMacOS } from '~/universal/utils/common'
|
||||
let contextMenu: Menu | null
|
||||
let menu: Menu | null
|
||||
let tray: Tray | null
|
||||
@@ -138,7 +138,9 @@ export function createContextMenu () {
|
||||
const getTrayIcon = () => {
|
||||
if (process.platform === 'darwin') {
|
||||
const isMacOSGreaterThan11 = isMacOSVersionGreaterThanOrEqualTo('11')
|
||||
return isMacOSGreaterThan11 ? `${__static}/menubar-newdarwinTemplate.png` : `${__static}/menubar.png`
|
||||
return isMacOSGreaterThan11
|
||||
? `${__static}/menubar-newdarwinTemplate.png`
|
||||
: `${__static}/menubar.png`
|
||||
} else {
|
||||
return `${__static}/menubar-nodarwin.png`
|
||||
}
|
||||
@@ -154,7 +156,9 @@ export function createTray () {
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
|
||||
}
|
||||
createContextMenu()
|
||||
tray!.popUpContextMenu(contextMenu!)
|
||||
setTimeout(() => {
|
||||
tray!.popUpContextMenu(contextMenu!)
|
||||
}, 0)
|
||||
})
|
||||
tray.on('click', (event, bounds) => {
|
||||
if (process.platform === 'darwin') {
|
||||
@@ -175,7 +179,10 @@ export function createTray () {
|
||||
} else {
|
||||
if (decodePath !== '') {
|
||||
// 带有中文的路径,无法直接被img.src所使用,会被转义
|
||||
const base64 = await fs.readFile(decodePath.replace('file://', ''), { encoding: 'base64' })
|
||||
const base64 = await fs.readFile(
|
||||
decodePath.replace('file://', ''),
|
||||
{ encoding: 'base64' }
|
||||
)
|
||||
obj.push({
|
||||
imgUrl: `data:image/png;base64,${base64}`
|
||||
})
|
||||
@@ -191,7 +198,9 @@ export function createTray () {
|
||||
})
|
||||
}
|
||||
}
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('clipboardFiles', obj)
|
||||
windowManager
|
||||
.get(IWindowList.TRAY_WINDOW)!
|
||||
.webContents.send('clipboardFiles', obj)
|
||||
}, 0)
|
||||
} else {
|
||||
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
@@ -230,7 +239,9 @@ export function createTray () {
|
||||
if (imgs !== false) {
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
pasteText.push(
|
||||
pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink'))
|
||||
)
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: imgs[i].imgUrl!
|
||||
@@ -246,16 +257,16 @@ export function createTray () {
|
||||
}
|
||||
})
|
||||
// toggleWindow()
|
||||
} else if (process.platform === 'linux') {
|
||||
// click事件在Ubuntu上无法触发,Unity不支持(在Mac和Windows上可以触发)
|
||||
// 需要使用 setContextMenu 设置菜单
|
||||
} else if (isLinux) {
|
||||
// click事件在Ubuntu上无法触发,Unity不支持(在Mac和Windows上可以触发)
|
||||
// 需要使用 setContextMenu 设置菜单
|
||||
createContextMenu()
|
||||
tray!.setContextMenu(contextMenu)
|
||||
}
|
||||
}
|
||||
|
||||
export function handleDockIcon () {
|
||||
if (process.platform === 'darwin') {
|
||||
if (isMacOS) {
|
||||
if (db.get('settings.showDockIcon') !== false) {
|
||||
app.dock.show()
|
||||
app.dock.setMenu(createContextMenu())
|
||||
@@ -270,25 +281,35 @@ export function createMenu () {
|
||||
return menu
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
const template = [{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
|
||||
{ label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
|
||||
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
|
||||
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
|
||||
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' },
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'CmdOrCtrl+Q',
|
||||
click () {
|
||||
app.quit()
|
||||
const template = [
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
|
||||
{
|
||||
label: 'Redo',
|
||||
accelerator: 'Shift+CmdOrCtrl+Z',
|
||||
selector: 'redo:'
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
|
||||
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
|
||||
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
|
||||
{
|
||||
label: 'Select All',
|
||||
accelerator: 'CmdOrCtrl+A',
|
||||
selector: 'selectAll:'
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'CmdOrCtrl+Q',
|
||||
click () {
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
]
|
||||
// @ts-ignore
|
||||
menu = Menu.buildFromTemplate(template)
|
||||
Menu.setApplicationMenu(menu)
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
WebContents
|
||||
} from 'electron'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { IRPCActionType, IWindowList } from '#/types/enum'
|
||||
import uploader from '.'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
import { handleUrlEncode } from '#/utils/common'
|
||||
import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/main/utils/common'
|
||||
import { T } from '~/main/i18n/index'
|
||||
import logger from '@core/picgo/logger'
|
||||
// import dayjs from 'dayjs'
|
||||
|
||||
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
@@ -22,8 +22,8 @@ const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
}
|
||||
|
||||
export const uploadClipboardFiles = async (): Promise<string> => {
|
||||
logger.info('upload clipboard file')
|
||||
const img = await handleClipboardUploading()
|
||||
console.log(img)
|
||||
if (img !== false) {
|
||||
if (img.length > 0) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
@@ -42,9 +42,9 @@ export const uploadClipboardFiles = async (): Promise<string> => {
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', img)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
return handleUrlEncode(img[0].imgUrl as string)
|
||||
return handleUrlEncodeWithSetting(img[0].imgUrl as string)
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_FAILED'),
|
||||
@@ -76,13 +76,13 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
|
||||
notification.show()
|
||||
}, i * 100)
|
||||
await GalleryDB.getInstance().insert(imgs[i])
|
||||
result.push(handleUrlEncode(imgs[i].imgUrl!))
|
||||
result.push(handleUrlEncodeWithSetting(imgs[i].imgUrl!))
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)?.webContents?.send('uploadFiles', imgs)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IWindowList } from '#/types/enum'
|
||||
import util from 'util'
|
||||
import { IPicGo } from 'picgo'
|
||||
import { showNotification, calcDurationRange, getClipboardFilePath } from '~/main/utils/common'
|
||||
import { RENAME_FILE_NAME, TALKING_DATA_EVENT } from '~/universal/events/constants'
|
||||
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME, TALKING_DATA_EVENT } from '~/universal/events/constants'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { T } from '~/main/i18n'
|
||||
import fse from 'fs-extra'
|
||||
@@ -21,14 +21,7 @@ import path from 'path'
|
||||
import { privacyManager } from '~/main/utils/privacyManager'
|
||||
import writeFile from 'write-file-atomic'
|
||||
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static'
|
||||
|
||||
const waitForShow = (webcontent: WebContents) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
webcontent.on('did-finish-load', () => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
import { cleanupFormUploaderFiles } from '~/main/utils/cleanupFormUploaderFiles'
|
||||
|
||||
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
|
||||
return new Promise((resolve) => {
|
||||
@@ -93,14 +86,19 @@ class Uploader {
|
||||
let name: undefined | string | null
|
||||
let fileName: string | undefined
|
||||
if (autoRename) {
|
||||
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS') + item.extname
|
||||
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmssSSS') + item.extname
|
||||
} else {
|
||||
fileName = item.fileName
|
||||
}
|
||||
if (rename) {
|
||||
const window = windowManager.create(IWindowList.RENAME_WINDOW)!
|
||||
await waitForShow(window.webContents)
|
||||
window.webContents.send(RENAME_FILE_NAME, fileName, item.fileName, window.webContents.id)
|
||||
logger.info('wait for rename window ready...')
|
||||
ipcMain.on(GET_RENAME_FILE_NAME, (evt) => {
|
||||
if (evt.sender.id === window.webContents.id) {
|
||||
logger.info('rename window ready, wait for rename...')
|
||||
window.webContents.send(RENAME_FILE_NAME, fileName, item.fileName, window.webContents.id)
|
||||
}
|
||||
})
|
||||
name = await waitForRename(window, window.webContents.id)
|
||||
}
|
||||
item.fileName = name || fileName
|
||||
@@ -129,7 +127,7 @@ class Uploader {
|
||||
}
|
||||
const buffer = nativeImage.toPNG()
|
||||
const baseDir = picgo.baseDir
|
||||
const fileName = `${dayjs().format('YYYYMMDDHHmmSSS')}.png`
|
||||
const fileName = `${dayjs().format('YYYYMMDDHHmmssSSS')}.png`
|
||||
filePath = path.join(baseDir, CLIPBOARD_IMAGE_FOLDER, fileName)
|
||||
await writeFile(filePath, buffer)
|
||||
return await this.upload([filePath])
|
||||
@@ -177,6 +175,9 @@ class Uploader {
|
||||
})
|
||||
}, 500)
|
||||
return false
|
||||
} finally {
|
||||
ipcMain.removeAllListeners(GET_RENAME_FILE_NAME)
|
||||
cleanupFormUploaderFiles(img)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import {
|
||||
RENAME_WINDOW_URL,
|
||||
TOOLBOX_WINDOW_URL
|
||||
} from './constants'
|
||||
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
|
||||
import { IStartupMode, IWindowList } from '#/types/enum'
|
||||
import bus from '@core/bus'
|
||||
import { CREATE_APP_MENU } from '@core/bus/constants'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import { app } from 'electron'
|
||||
import { remoteNoticeHandler } from '../remoteNotice'
|
||||
import { T } from '~/main/i18n'
|
||||
import { isLinux } from '~/universal/utils/common'
|
||||
// import { URLSearchParams } from 'url'
|
||||
|
||||
const windowList = new Map<IWindowList, IWindowListItem>()
|
||||
@@ -26,6 +26,21 @@ const handleWindowParams = (windowURL: string) => {
|
||||
return windowURL
|
||||
}
|
||||
|
||||
export const isWindowShouldShowOnStartup = (currentWindow: IWindowList) => {
|
||||
const startupMode = db.get('settings.startupMode') || (isLinux ? IStartupMode.SHOW_MINI_WINDOW : IStartupMode.HIDE)
|
||||
switch (currentWindow) {
|
||||
case IWindowList.MINI_WINDOW: {
|
||||
return startupMode === IStartupMode.SHOW_MINI_WINDOW
|
||||
}
|
||||
case IWindowList.SETTING_WINDOW: {
|
||||
return startupMode === IStartupMode.SHOW_MAIN_WINDOW
|
||||
}
|
||||
default: {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
isValid: process.platform !== 'linux',
|
||||
multiple: false,
|
||||
@@ -93,9 +108,6 @@ windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
return options
|
||||
},
|
||||
callback (window, windowManager) {
|
||||
window.once('show', () => {
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.SETTING_WINDOW_OPEN)
|
||||
})
|
||||
window.loadURL(handleWindowParams(SETTING_WINDOW_URL))
|
||||
window.on('closed', () => {
|
||||
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
||||
@@ -117,7 +129,7 @@ windowList.set(IWindowList.MINI_WINDOW, {
|
||||
const obj: IBrowserWindowOptions = {
|
||||
height: 64,
|
||||
width: 64,
|
||||
show: process.platform === 'linux',
|
||||
show: isLinux,
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
skipTaskbar: true,
|
||||
@@ -132,7 +144,7 @@ windowList.set(IWindowList.MINI_WINDOW, {
|
||||
}
|
||||
}
|
||||
|
||||
if (db.get('settings.miniWindowOntop')) {
|
||||
if (db.get('settings.miniWindowOnTop')) {
|
||||
obj.alwaysOnTop = true
|
||||
}
|
||||
return obj
|
||||
|
||||
@@ -7,3 +7,7 @@ export const UPLOAD_WITH_FILES_RESPONSE = 'UPLOAD_WITH_FILES_RESPONSE'
|
||||
export const UPLOAD_WITH_CLIPBOARD_FILES = 'UPLOAD_WITH_CLIPBOARD_FILES'
|
||||
export const UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE = 'UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE'
|
||||
export const CREATE_APP_MENU = 'CREATE_APP_MENU'
|
||||
|
||||
// shortkey command
|
||||
export const SHORTKEY_BUILTIN_PREFIX = 'picgo:'
|
||||
export const SHORTKEY_COMMAND_UPLOAD = `${SHORTKEY_BUILTIN_PREFIX}upload`
|
||||
|
||||
@@ -5,6 +5,7 @@ import { app as APP } from 'electron'
|
||||
import { getLogger } from '@core/utils/localLogger'
|
||||
import dayjs from 'dayjs'
|
||||
import { T } from '~/main/i18n'
|
||||
import { FORM_IMAGE_FOLDER } from '~/universal/utils/static'
|
||||
const STORE_PATH = APP.getPath('userData')
|
||||
const configFilePath = path.join(STORE_PATH, 'data.json')
|
||||
const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json')
|
||||
@@ -131,9 +132,19 @@ function getGalleryDBPath (): {
|
||||
}
|
||||
}
|
||||
|
||||
function getFormImageFolderPath (): string {
|
||||
const STORE_PATH = dbPathDir()
|
||||
const formImagesPath = path.join(STORE_PATH, FORM_IMAGE_FOLDER)
|
||||
if (!fs.existsSync(formImagesPath)) {
|
||||
fs.mkdirSync(formImagesPath, { recursive: true })
|
||||
}
|
||||
return formImagesPath
|
||||
}
|
||||
|
||||
export {
|
||||
dbChecker,
|
||||
dbPathChecker,
|
||||
dbPathDir,
|
||||
getGalleryDBPath
|
||||
getGalleryDBPath,
|
||||
getFormImageFolderPath
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from '~/universal/events/constants'
|
||||
import { DBStore } from '@picgo/store'
|
||||
import { T } from '~/main/i18n'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
|
||||
// Cross-process support may be required in the future
|
||||
class GuiApi implements IGuiApi {
|
||||
@@ -26,7 +27,7 @@ class GuiApi implements IGuiApi {
|
||||
private windowId: number = -1
|
||||
private settingWindowId: number = -1
|
||||
private constructor () {
|
||||
console.log('init guiapi')
|
||||
console.log('init gui api')
|
||||
}
|
||||
|
||||
public static getInstance (): GuiApi {
|
||||
@@ -50,7 +51,7 @@ class GuiApi implements IGuiApi {
|
||||
})
|
||||
}
|
||||
|
||||
private getWebcontentsByWindowId (id: number) {
|
||||
private getWebContentsByWindowId (id: number) {
|
||||
return BrowserWindow.fromId(id)?.webContents
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ class GuiApi implements IGuiApi {
|
||||
placeholder: ''
|
||||
}) {
|
||||
await this.showSettingWindow()
|
||||
this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
|
||||
this.getWebContentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
|
||||
return new Promise<string>((resolve) => {
|
||||
ipcMain.once(SHOW_INPUT_BOX, (event: Event, value: string) => {
|
||||
resolve(value)
|
||||
@@ -75,7 +76,7 @@ class GuiApi implements IGuiApi {
|
||||
|
||||
async upload (input: IUploadOption) {
|
||||
this.windowId = await getWindowId()
|
||||
const webContents = this.getWebcontentsByWindowId(this.windowId)
|
||||
const webContents = this.getWebContentsByWindowId(this.windowId)
|
||||
const imgs = await uploader.setWebContents(webContents!).upload(input)
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
@@ -94,7 +95,7 @@ class GuiApi implements IGuiApi {
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
webContents?.send('uploadFiles', imgs)
|
||||
webContents?.send('updateGallery')
|
||||
webContents?.send(IRPCActionType.UPDATE_GALLERY)
|
||||
return imgs
|
||||
}
|
||||
return []
|
||||
@@ -131,6 +132,20 @@ class GuiApi implements IGuiApi {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* v2.4.0+
|
||||
* @param options
|
||||
*/
|
||||
async showConfigDialog<T extends IStringKeyMap> (options: IPicGoPluginShowConfigDialogOption) {
|
||||
await this.showSettingWindow()
|
||||
this.getWebContentsByWindowId(this.settingWindowId)?.send(IRPCActionType.OPEN_CONFIG_DIALOG, options)
|
||||
return new Promise<T | false>((resolve) => {
|
||||
ipcMain.once(IRPCActionType.OPEN_CONFIG_DIALOG, (event: Event, value: T | false) => {
|
||||
resolve(value)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* get picgo config/data path
|
||||
*/
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
} from '@core/bus/constants'
|
||||
function initEventCenter () {
|
||||
const eventList: any = {
|
||||
'picgo:upload': uploadClipboardFiles,
|
||||
[UPLOAD_WITH_CLIPBOARD_FILES]: busCallUploadClipboardFiles,
|
||||
[UPLOAD_WITH_FILES]: busCallUploadFiles,
|
||||
[GET_WINDOW_ID]: busCallGetWindowId,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
BrowserWindow
|
||||
} from 'electron'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { IRPCActionType, IWindowList } from '#/types/enum'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
@@ -62,7 +62,7 @@ export default {
|
||||
await GalleryDB.getInstance().insert(img[0])
|
||||
trayWindow.webContents.send('clipboardFiles', [])
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
}
|
||||
trayWindow.webContents.send('uploadFiles')
|
||||
@@ -137,7 +137,7 @@ export default {
|
||||
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
|
||||
if (db.get('settings.miniWindowOntop')) {
|
||||
if (db.get('settings.miniWindowOnTop')) {
|
||||
miniWindow.setAlwaysOnTop(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ import {
|
||||
OPEN_WINDOW,
|
||||
GET_LANGUAGE_LIST,
|
||||
SET_CURRENT_LANGUAGE,
|
||||
GET_CURRENT_LANGUAGE
|
||||
GET_CURRENT_LANGUAGE,
|
||||
GET_PICBED_CONFIG
|
||||
} from '#/events/constants'
|
||||
|
||||
import { GalleryDB } from 'apis/core/datastore'
|
||||
@@ -223,14 +224,14 @@ const handleNPMError = (): IDispose => {
|
||||
}
|
||||
|
||||
const handleGetPicBedConfig = () => {
|
||||
ipcMain.on('getPicBedConfig', (event: IpcMainEvent, type: string) => {
|
||||
ipcMain.on(GET_PICBED_CONFIG, (event: IpcMainEvent, type: string) => {
|
||||
const name = picgo.helper.uploader.get(type)?.name || type
|
||||
if (picgo.helper.uploader.get(type)?.config) {
|
||||
const _config = picgo.helper.uploader.get(type)!.config!(picgo)
|
||||
const config = handleConfigWithFunction(_config)
|
||||
event.sender.send('getPicBedConfig', config, name)
|
||||
event.sender.send(GET_PICBED_CONFIG, config, name)
|
||||
} else {
|
||||
event.sender.send('getPicBedConfig', [], name)
|
||||
event.sender.send(GET_PICBED_CONFIG, [], name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { configRouter } from './routes/config'
|
||||
import { versionRouter } from './routes/version'
|
||||
import { toolboxRouter } from './routes/toolbox'
|
||||
import { systemRouter } from './routes/system'
|
||||
import { galleryToolboxRouter } from './routes/galleryToolbox'
|
||||
|
||||
class RPCServer implements IRPCServer {
|
||||
private routes: IRPCRoutes = new Map()
|
||||
@@ -53,6 +54,7 @@ rpcServer.use(configRouter.routes())
|
||||
rpcServer.use(versionRouter.routes())
|
||||
rpcServer.use(toolboxRouter.routes())
|
||||
rpcServer.use(systemRouter.routes())
|
||||
rpcServer.use(galleryToolboxRouter.routes())
|
||||
|
||||
export {
|
||||
rpcServer
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { logger } from '@picgo/i18n'
|
||||
import { IPicGo } from 'picgo'
|
||||
import { T } from '~/main/i18n'
|
||||
import { getHost, removeProtocolAndSuffix, replaceHost } from '~/main/utils/common'
|
||||
|
||||
export const galleryMenu = () => {
|
||||
return [{
|
||||
label: T('CHANGE_IMAGE_URL_HOST'),
|
||||
async handle (ctx: IPicGo, guiApi: IGuiApi, selectedList: ImgInfo[] = []) {
|
||||
const hostList = [...new Set(selectedList.map((item) => {
|
||||
return getHost(item.imgUrl)
|
||||
}).filter(Boolean) as string[])]
|
||||
if (hostList.length === 0) {
|
||||
guiApi.showNotification({
|
||||
title: T('CHANGE_IMAGE_URL_HOST'),
|
||||
body: T('CHANGE_IMAGE_URL_HOST_WARN')
|
||||
})
|
||||
logger.warn(T('CHANGE_IMAGE_URL_HOST_WARN'))
|
||||
return
|
||||
}
|
||||
const config: IPicGoPluginConfig[] = [
|
||||
{
|
||||
alias: T('SELECTED_IMAGE_URL_HOST'),
|
||||
name: 'selectedHost',
|
||||
type: 'checkbox',
|
||||
choices: hostList.map(item => ({
|
||||
name: item,
|
||||
value: item,
|
||||
checked: true
|
||||
})),
|
||||
required: true
|
||||
},
|
||||
{
|
||||
alias: T('NEW_IMAGE_URL_HOST'),
|
||||
name: 'newHost',
|
||||
type: 'input',
|
||||
message: 'www.example.com',
|
||||
default: '',
|
||||
required: true
|
||||
}
|
||||
]
|
||||
const options: IPicGoPluginShowConfigDialogOption = {
|
||||
title: T('CHANGE_IMAGE_URL_HOST'),
|
||||
config
|
||||
}
|
||||
const res = await guiApi.showConfigDialog<{
|
||||
selectedHost: string[]
|
||||
newHost: string
|
||||
}>(options)
|
||||
|
||||
if (res) {
|
||||
const selectedHost = res.selectedHost
|
||||
const newHost = removeProtocolAndSuffix(res.newHost)
|
||||
const changedList = selectedList.map((item) => {
|
||||
try {
|
||||
const url = new URL(item.imgUrl || '')
|
||||
const host = url.host
|
||||
if (selectedHost.includes(host)) {
|
||||
item.imgUrl = replaceHost(item.imgUrl!, host, newHost)
|
||||
return item
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch (e: any) {
|
||||
ctx.log.error(e)
|
||||
return false
|
||||
}
|
||||
}).filter(Boolean) as ImgInfo[]
|
||||
const updateRes = await guiApi.galleryDB.updateMany(changedList)
|
||||
guiApi.showNotification({
|
||||
title: T('CHANGE_IMAGE_URL_HOST_RESULT'),
|
||||
body: `${T('SUCCESS')}: ${updateRes.success} ${T('FAILED')}: ${updateRes.total - updateRes.success}}`
|
||||
})
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { galleryMenu as changeHostGalleryMenu } from './changeHost'
|
||||
export const builtInGalleryToolboxMenu = () => {
|
||||
const menuList = [...changeHostGalleryMenu()]
|
||||
|
||||
return menuList
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { IRPCActionType, IWindowList } from '~/universal/types/enum'
|
||||
import { RPCRouter } from '../../router'
|
||||
import windowManager from '~/main/apis/app/window/windowManager'
|
||||
import { galleryMenuListManager } from './menuListManager'
|
||||
|
||||
const galleryToolboxRouter = new RPCRouter()
|
||||
|
||||
galleryToolboxRouter.add(IRPCActionType.GET_GALLERY_MENU_LIST, async (args) => {
|
||||
const [selectedList] = args as IGetGalleryMenuListArgs
|
||||
const win = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const menu = galleryMenuListManager.getMenu(selectedList)
|
||||
|
||||
menu.popup({
|
||||
window: win
|
||||
})
|
||||
})
|
||||
|
||||
export {
|
||||
galleryToolboxRouter
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Menu, MenuItemConstructorOptions } from 'electron'
|
||||
import { builtInGalleryToolboxMenu } from './builtIn'
|
||||
import picgo from '@core/picgo'
|
||||
import GuiApi from 'apis/gui'
|
||||
import windowManager from '~/main/apis/app/window/windowManager'
|
||||
import { IRPCActionType, IWindowList } from '~/universal/types/enum'
|
||||
import logger from '~/main/apis/core/picgo/logger'
|
||||
|
||||
class GalleryMenuListManager {
|
||||
private menuList: MenuItemConstructorOptions[] = []
|
||||
private menu: Menu | null = null
|
||||
|
||||
private getBuiltInMenuList (selectedList: IGalleryItem[]): MenuItemConstructorOptions[] {
|
||||
const builtInMenu = builtInGalleryToolboxMenu().map(item => {
|
||||
return {
|
||||
label: item.label,
|
||||
async click () {
|
||||
try {
|
||||
await item.handle(picgo, GuiApi.getInstance(), selectedList)
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
} finally {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)?.webContents.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) as MenuItemConstructorOptions[]
|
||||
this.menuList = [...builtInMenu]
|
||||
|
||||
return this.menuList
|
||||
}
|
||||
|
||||
private getMenuItemList (selectedList: IGalleryItem[]) {
|
||||
// current only support built-in menu
|
||||
return this.getBuiltInMenuList(selectedList)
|
||||
}
|
||||
|
||||
public getMenu (selectedList: IGalleryItem[]): Menu {
|
||||
this.menu = Menu.buildFromTemplate(this.getMenuItemList(selectedList))
|
||||
return this.menu
|
||||
}
|
||||
}
|
||||
|
||||
const galleryMenuListManager = new GalleryMenuListManager()
|
||||
|
||||
export {
|
||||
galleryMenuListManager
|
||||
}
|
||||
@@ -7,14 +7,14 @@ import { builtinI18nList } from '#/i18n'
|
||||
class I18nManager {
|
||||
private i18n: I18n | null = null
|
||||
private builtinI18nFolder = path.join(__static, 'i18n')
|
||||
private outterI18nFolder = ''
|
||||
private outerI18nFolder = ''
|
||||
private localesMap: Map<string, ILocales> = new Map()
|
||||
private currentLanguage: string = 'zh-CN'
|
||||
readonly defaultLanguage: string = 'zh-CN'
|
||||
private i18nFileList: II18nItem[] = builtinI18nList
|
||||
|
||||
setOutterI18nFolder (folder: string) {
|
||||
this.outterI18nFolder = folder
|
||||
setOuterI18nFolder (folder: string) {
|
||||
this.outerI18nFolder = folder
|
||||
}
|
||||
|
||||
addI18nFile (file: string, label: string) {
|
||||
@@ -30,7 +30,7 @@ class I18nManager {
|
||||
}
|
||||
let localesPath = path.join(this.builtinI18nFolder, `${lang}.yml`)
|
||||
if (!fs.existsSync(localesPath)) {
|
||||
localesPath = path.join(this.outterI18nFolder, `${lang}.yml`)
|
||||
localesPath = path.join(this.outerI18nFolder, `${lang}.yml`)
|
||||
if (!fs.existsSync(localesPath)) {
|
||||
localesPath = path.join(this.builtinI18nFolder, `${this.defaultLanguage}.yml`)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import fixPath from './fixPath'
|
||||
import { initI18n } from '~/main/utils/handleI18n'
|
||||
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
|
||||
import { isMacOS } from '../utils/getMacOSVersion'
|
||||
import { isWindowShouldShowOnStartup } from '../apis/app/window/windowList'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
@@ -79,11 +80,25 @@ class LifeCycle {
|
||||
try {
|
||||
await installExtension(VUEJS_DEVTOOLS)
|
||||
} catch (e: any) {
|
||||
console.error('Vue Devtools failed to install:', e.toString())
|
||||
console.error('Vue Devtools failed to install:', e?.toString())
|
||||
}
|
||||
}
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
const settingWindow = windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
settingWindow?.once('show', () => {
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.SETTING_WINDOW_OPEN)
|
||||
})
|
||||
if (isWindowShouldShowOnStartup(IWindowList.SETTING_WINDOW)) {
|
||||
settingWindow?.show()
|
||||
settingWindow?.focus()
|
||||
}
|
||||
if (!isMacOS) {
|
||||
if (isWindowShouldShowOnStartup(IWindowList.MINI_WINDOW)) {
|
||||
const miniWindow = windowManager.create(IWindowList.MINI_WINDOW)
|
||||
miniWindow?.show()
|
||||
miniWindow?.focus()
|
||||
}
|
||||
}
|
||||
createTray()
|
||||
handleDockIcon()
|
||||
db.set('needReload', false)
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from 'path'
|
||||
import fse from 'fs-extra'
|
||||
import { PicGo as PicGoCore } from 'picgo'
|
||||
import { T } from '~/main/i18n'
|
||||
import { SHORTKEY_COMMAND_UPLOAD } from 'apis/core/bus/constants'
|
||||
// from v2.1.2
|
||||
const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
|
||||
// #557 极端情况可能会出现配置不存在,需要重新写入
|
||||
@@ -19,7 +20,7 @@ const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IS
|
||||
}
|
||||
if (shortKeyConfig.upload) {
|
||||
// @ts-ignore
|
||||
shortKeyConfig['picgo:upload'] = {
|
||||
shortKeyConfig[SHORTKEY_COMMAND_UPLOAD] = {
|
||||
enable: true,
|
||||
key: shortKeyConfig.upload,
|
||||
name: 'upload',
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import axios from 'axios'
|
||||
import formUploader from './middlewares/formUploader'
|
||||
|
||||
class Server {
|
||||
private httpServer: http.Server
|
||||
@@ -56,6 +57,8 @@ class Server {
|
||||
success: false
|
||||
}
|
||||
})
|
||||
} else if (formUploader.isFileUpload(request)) {
|
||||
formUploader.handleFileUpload(request, response)
|
||||
} else {
|
||||
let body: string = ''
|
||||
let postObj: IObj
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import http from 'http'
|
||||
import multer from 'multer'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { dbPathDir } from 'apis/core/datastore/dbChecker'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { handleResponse } from '../utils'
|
||||
import routers from '../routerManager'
|
||||
import { FORM_IMAGE_FOLDER } from '~/universal/utils/static'
|
||||
|
||||
// Multer 错误类型定义
|
||||
export interface MulterError extends Error {
|
||||
code: string
|
||||
field?: string
|
||||
storageErrors?: any[]
|
||||
}
|
||||
|
||||
// Multer 中间件适配器类型
|
||||
export type MulterMiddleware = (
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
callback: (error?: MulterError) => void
|
||||
) => void;
|
||||
|
||||
// 扩展 multer 函数的返回类型
|
||||
declare module 'multer' {
|
||||
interface Multer {
|
||||
array(fieldname: string, maxCount?: number): MulterMiddleware;
|
||||
single(fieldname: string): MulterMiddleware;
|
||||
fields(fields: { name: string; maxCount?: number }[]): MulterMiddleware;
|
||||
none(): MulterMiddleware;
|
||||
}
|
||||
}
|
||||
|
||||
class FormUploader {
|
||||
private upload!: multer.Multer
|
||||
private formImagesPath!: string
|
||||
|
||||
constructor () {
|
||||
this.initializeStorage()
|
||||
this.setupMulter()
|
||||
}
|
||||
|
||||
private initializeStorage () {
|
||||
const STORE_PATH = dbPathDir()
|
||||
this.formImagesPath = path.join(STORE_PATH, FORM_IMAGE_FOLDER)
|
||||
if (!fs.existsSync(this.formImagesPath)) {
|
||||
fs.mkdirSync(this.formImagesPath, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
private setupMulter () {
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req: http.IncomingMessage, file: Express.Multer.File, cb: (error: Error | null, destination: string) => void) => {
|
||||
cb(null, this.formImagesPath)
|
||||
},
|
||||
filename: (req: http.IncomingMessage, file: Express.Multer.File, cb: (error: Error | null, filename: string) => void) => {
|
||||
cb(null, file.originalname || Date.now() + path.extname(file.originalname))
|
||||
}
|
||||
})
|
||||
|
||||
this.upload = multer({
|
||||
storage
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文件上传的中间件
|
||||
*/
|
||||
public handleFileUpload = (request: http.IncomingMessage, response: http.ServerResponse): void => {
|
||||
logger.info('[PicGo Server] handling file upload')
|
||||
|
||||
this.upload.array('files')(request, response, async (err?: MulterError) => {
|
||||
if (err) {
|
||||
logger.error('[PicGo Server] file upload error', err)
|
||||
return handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'File upload failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const files = request.files
|
||||
if (!files || files.length === 0) {
|
||||
return handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'No files were uploaded'
|
||||
}
|
||||
})
|
||||
}
|
||||
const filePaths = files.map((file) => file.path)
|
||||
logger.info('[PicGo Server] files uploaded: ' + filePaths.join(', '))
|
||||
|
||||
const handler = routers.getHandler(request.url!)
|
||||
handler!({
|
||||
list: filePaths,
|
||||
response
|
||||
})
|
||||
} catch (err: any) {
|
||||
logger.error('[PicGo Server] process upload files error', err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'Failed to process uploaded files'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查请求是否为文件上传
|
||||
*/
|
||||
public isFileUpload (request: http.IncomingMessage): boolean {
|
||||
return !!(request.headers['content-type'] && request.headers['content-type'].includes('multipart/form-data'))
|
||||
}
|
||||
}
|
||||
|
||||
export default new FormUploader()
|
||||
@@ -16,6 +16,29 @@ function beforeOpen () {
|
||||
resolveClipboardImageGenerator()
|
||||
resolveOtherI18nFiles()
|
||||
}
|
||||
function copyFileOutsideOfElectronAsar (
|
||||
sourceInAsarArchive: string,
|
||||
destOutsideAsarArchive: string
|
||||
) {
|
||||
if (fs.existsSync(sourceInAsarArchive)) {
|
||||
// file will be copied
|
||||
if (fs.statSync(sourceInAsarArchive).isFile()) {
|
||||
const file = destOutsideAsarArchive;
|
||||
const dir = path.dirname(file)
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
fs.writeFileSync(file, fs.readFileSync(sourceInAsarArchive))
|
||||
} else if (fs.statSync(sourceInAsarArchive).isDirectory()) {
|
||||
fs.readdirSync(sourceInAsarArchive).forEach(function (fileOrFolderName) {
|
||||
copyFileOutsideOfElectronAsar(
|
||||
`${sourceInAsarArchive}/${fileOrFolderName}`,
|
||||
`${destOutsideAsarArchive}/${fileOrFolderName}`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS 右键菜单
|
||||
@@ -26,7 +49,7 @@ function resolveMacWorkFlow () {
|
||||
return true
|
||||
} else {
|
||||
try {
|
||||
fs.copySync(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
|
||||
copyFileOutsideOfElectronAsar(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
@@ -88,7 +111,7 @@ function resolveOtherI18nFiles () {
|
||||
if (!fs.pathExistsSync(i18nFolder)) {
|
||||
fs.mkdirSync(i18nFolder)
|
||||
}
|
||||
i18nManager.setOutterI18nFolder(i18nFolder)
|
||||
i18nManager.setOuterI18nFolder(i18nFolder)
|
||||
const i18nFiles = fs.readdirSync(path.join(CONFIG_DIR, 'i18n'), {
|
||||
withFileTypes: true
|
||||
})
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { getFormImageFolderPath } from '@core/datastore/dbChecker'
|
||||
import logger from '@core/picgo/logger'
|
||||
|
||||
export const cleanupFormUploaderFiles = (filePathList?: string[]): void => {
|
||||
const formImageFolderPath = getFormImageFolderPath()
|
||||
if (Array.isArray(filePathList)) {
|
||||
filePathList.forEach(async filePath => {
|
||||
try {
|
||||
// 检查文件路径是否在 formImageFolderPath 目录下
|
||||
const relativePath = path.relative(formImageFolderPath, filePath)
|
||||
const isWithinFolder = !relativePath.startsWith('..') && !path.isAbsolute(relativePath)
|
||||
|
||||
if (isWithinFolder && await fs.pathExists(filePath)) {
|
||||
await fs.remove(filePath)
|
||||
logger.info(`[PicGo] Deleted temp file: ${filePath}`)
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error(`[PicGo] Failed to delete temp file ${filePath}:`, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import fs from 'fs-extra'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { clipboard, Notification, dialog } from 'electron'
|
||||
import { handleUrlEncode } from '~/universal/utils/common'
|
||||
|
||||
export const handleCopyUrl = (str: string): void => {
|
||||
if (db.get('settings.autoCopy') !== false) {
|
||||
if (db.get('settings.autoCopyUrl') !== false) {
|
||||
clipboard.writeText(str)
|
||||
}
|
||||
}
|
||||
@@ -117,3 +118,38 @@ export const getClipboardFilePath = (): string => {
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
export const handleUrlEncodeWithSetting = (url: string) => {
|
||||
if (db.get('settings.encodeOutputURL') === true) {
|
||||
url = handleUrlEncode(url)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
export const replaceHost = (url: string, oldHost: string, newHost: string) => {
|
||||
try {
|
||||
const parsedUrl = new URL(url)
|
||||
if (parsedUrl.host === oldHost) {
|
||||
parsedUrl.host = newHost
|
||||
}
|
||||
return parsedUrl.toString()
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
export const getHost = (url: string = '') => {
|
||||
try {
|
||||
const parsedUrl = new URL(url)
|
||||
return parsedUrl.host
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove protocol and suffix
|
||||
*/
|
||||
export const removeProtocolAndSuffix = (url: string = '') => {
|
||||
return url.replace(/^(https?:\/\/)?/, '').replace(/\/$/, '')
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { uuid } from 'uuidv4'
|
||||
import { trimValues } from '#/utils/common'
|
||||
import picgo from '@core/picgo'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IPicGoPluginConfig[] => {
|
||||
for (const i in config) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { IPasteStyle } from '#/types/enum'
|
||||
import db from 'apis/core/datastore'
|
||||
import { handleUrlEncode } from '~/universal/utils/common'
|
||||
import { handleUrlEncodeWithSetting } from './common'
|
||||
|
||||
const formatCustomLink = (customLink: string, item: ImgInfo) => {
|
||||
const fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')
|
||||
@@ -22,10 +21,7 @@ const formatCustomLink = (customLink: string, item: ImgInfo) => {
|
||||
}
|
||||
|
||||
export default (style: IPasteStyle, item: ImgInfo, customLink: string | undefined) => {
|
||||
let url = item.url || item.imgUrl
|
||||
if (db.get('settings.encodeOutputURL') !== false) {
|
||||
url = handleUrlEncode(url)
|
||||
}
|
||||
const url = handleUrlEncodeWithSetting(item.url || item.imgUrl)
|
||||
const _customLink = customLink || '$url'
|
||||
const tpl = {
|
||||
markdown: ``,
|
||||
|
||||
@@ -11,6 +11,9 @@ import { getConfig } from './utils/dataSender'
|
||||
import type { IConfig } from 'picgo'
|
||||
import bus from './utils/bus'
|
||||
import { FORCE_UPDATE } from '~/universal/events/constants'
|
||||
import { useATagClick } from './hooks/useATagClick'
|
||||
|
||||
useATagClick()
|
||||
|
||||
const store = useStore()
|
||||
onBeforeMount(async () => {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -3,81 +3,35 @@
|
||||
id="config-form"
|
||||
:class="props.colorMode === 'white' ? 'white' : ''"
|
||||
>
|
||||
<el-form
|
||||
<BaseConfigForm
|
||||
ref="$form"
|
||||
label-position="left"
|
||||
label-width="50%"
|
||||
:model="ruleForm"
|
||||
size="small"
|
||||
:config="configList"
|
||||
:form-model="formModel"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('UPLOADER_CONFIG_NAME')"
|
||||
required
|
||||
prop="_configName"
|
||||
>
|
||||
<el-input
|
||||
v-model="ruleForm._configName"
|
||||
type="input"
|
||||
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- dynamic config -->
|
||||
<el-form-item
|
||||
v-for="(item, index) in configList"
|
||||
:key="item.name + index"
|
||||
:label="item.alias || item.name"
|
||||
:required="item.required"
|
||||
:prop="item.name"
|
||||
>
|
||||
<el-input
|
||||
v-if="item.type === 'input' || item.type === 'password'"
|
||||
v-model="ruleForm[item.name]"
|
||||
:type="item.type === 'password' ? 'password' : 'input'"
|
||||
:placeholder="item.message || item.name"
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="item.type === 'list' && item.choices"
|
||||
v-model="ruleForm[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
<template #extra-form>
|
||||
<el-form-item
|
||||
v-if="isUploader"
|
||||
:label="$T('UPLOADER_CONFIG_NAME')"
|
||||
required
|
||||
prop="_configName"
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:key="choice.name || choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
<el-input
|
||||
v-model="formModel._configName"
|
||||
type="input"
|
||||
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="item.type === 'checkbox' && item.choices"
|
||||
v-model="ruleForm[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
multiple
|
||||
collapse-tags
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:key="choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
/>
|
||||
</el-select>
|
||||
<el-switch
|
||||
v-else-if="item.type === 'confirm'"
|
||||
v-model="ruleForm[item.name]"
|
||||
active-text="yes"
|
||||
inactive-text="no"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<slot />
|
||||
</el-form>
|
||||
</BaseConfigForm>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch, toRefs } from 'vue'
|
||||
import { cloneDeep, union } from 'lodash'
|
||||
import { getConfig } from '@/utils/dataSender'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import BaseConfigForm from './form/BaseConfigForm.vue'
|
||||
import { useConfigForm } from '@/hooks/useConfigForm'
|
||||
|
||||
interface IProps {
|
||||
config: any[]
|
||||
@@ -88,33 +42,14 @@ interface IProps {
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
const $route = useRoute()
|
||||
const $form = ref<FormInstance>()
|
||||
|
||||
const $form = ref<IFormInstance>()
|
||||
const configList = ref<IPicGoPluginConfig[]>([])
|
||||
const ruleForm = reactive<IStringKeyMap>({})
|
||||
|
||||
watch(toRefs(props.config), (val: IPicGoPluginConfig[]) => {
|
||||
handleConfigChange(val)
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
|
||||
function handleConfigChange (val: any) {
|
||||
handleConfig(val)
|
||||
}
|
||||
const formModel = reactive<IStringKeyMap>({})
|
||||
const isUploader = props.type === 'uploader'
|
||||
|
||||
async function validate (): Promise<IStringKeyMap | false> {
|
||||
return new Promise((resolve) => {
|
||||
$form.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(ruleForm)
|
||||
} else {
|
||||
resolve(false)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
const res = await $form.value?.validate() || false
|
||||
return res
|
||||
}
|
||||
|
||||
function getConfigType () {
|
||||
@@ -133,45 +68,50 @@ function getConfigType () {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfig (val: IPicGoPluginConfig[]) {
|
||||
const config = await getCurConfigFormData()
|
||||
const configId = $route.params.configId
|
||||
Object.assign(ruleForm, config)
|
||||
if (val.length > 0) {
|
||||
configList.value = cloneDeep(val).map((item) => {
|
||||
if (!configId) return item
|
||||
let defaultValue = item.default !== undefined
|
||||
? item.default
|
||||
: item.type === 'checkbox'
|
||||
? []
|
||||
: null
|
||||
if (item.type === 'checkbox') {
|
||||
const defaults = item.choices?.filter((i: any) => {
|
||||
return i.checked
|
||||
}).map((i: any) => i.value) || []
|
||||
defaultValue = union(defaultValue, defaults)
|
||||
}
|
||||
if (config && config[item.name] !== undefined) {
|
||||
defaultValue = config[item.name]
|
||||
}
|
||||
ruleForm[item.name] = defaultValue
|
||||
return item
|
||||
})
|
||||
}
|
||||
const handleConfigForm = useConfigForm()
|
||||
|
||||
async function handleConfig (inputConfig: IPicGoPluginConfig[]) {
|
||||
const currentConfig = await getCurConfigFormData()
|
||||
const resetConfig = isUploader && !$route.params.configId
|
||||
Object.assign(formModel, currentConfig)
|
||||
configList.value = handleConfigForm(inputConfig, formModel, currentConfig, resetConfig)
|
||||
}
|
||||
|
||||
async function getCurConfigFormData () {
|
||||
const configId = $route.params.configId
|
||||
const curTypeConfigList = await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`) || []
|
||||
return curTypeConfigList.find(i => i._id === configId) || {}
|
||||
const configType = getConfigType()
|
||||
if (isUploader) {
|
||||
const curTypeConfigList = await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`) || []
|
||||
return curTypeConfigList.find(i => i._id === configId) || {}
|
||||
} else {
|
||||
const config = await getConfig<IStringKeyMap>(configType)
|
||||
return config || {}
|
||||
}
|
||||
}
|
||||
|
||||
async function resetConfig () {
|
||||
const config = await getCurConfigFormData()
|
||||
Object.assign(formModel, config)
|
||||
}
|
||||
|
||||
watch(toRefs(props.config), (val: IPicGoPluginConfig[]) => {
|
||||
handleConfig(val)
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
getConfigType
|
||||
getConfigType,
|
||||
resetConfig
|
||||
})
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.config-form-common-tips
|
||||
a
|
||||
color #409EFF
|
||||
text-decoration none
|
||||
#config-form
|
||||
.el-form
|
||||
label
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<ConfirmDialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
@confirm="handleConfirm"
|
||||
@cancel="handleCancel"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<template #body>
|
||||
<BaseConfigForm
|
||||
ref="$form"
|
||||
:config="configList"
|
||||
:form-model="formModel"
|
||||
theme="light"
|
||||
/>
|
||||
</template>
|
||||
</ConfirmDialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import ConfirmDialog from '@/components/dialog/ConfirmDialog.vue'
|
||||
import { useIPCOn } from '@/hooks/useIPC'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import BaseConfigForm from '@/components/form/BaseConfigForm.vue'
|
||||
import { useConfigForm } from '@/hooks/useConfigForm'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
|
||||
const configList = ref<IPicGoPluginConfig[]>([])
|
||||
const formModel = reactive<IStringKeyMap>({})
|
||||
const $form = ref<IFormInstance>()
|
||||
const title = ref('')
|
||||
|
||||
const handleConfigForm = useConfigForm()
|
||||
|
||||
const visible = ref(false)
|
||||
useIPCOn(IRPCActionType.OPEN_CONFIG_DIALOG, (event, options: IPicGoPluginShowConfigDialogOption) => {
|
||||
visible.value = true
|
||||
configList.value = handleConfigForm(options.config, formModel)
|
||||
title.value = options.title
|
||||
})
|
||||
const handleConfirm = async () => {
|
||||
const res = await $form.value?.validate() || false
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
sendToMain(IRPCActionType.OPEN_CONFIG_DIALOG, res)
|
||||
visible.value = false
|
||||
}
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
sendToMain(IRPCActionType.OPEN_CONFIG_DIALOG, false)
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ConfigFormDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
@close="handleClose"
|
||||
>
|
||||
<slot name="body" />
|
||||
<template #footer>
|
||||
<slot
|
||||
v-if="footerSlots"
|
||||
name="footer"
|
||||
/>
|
||||
<template v-else>
|
||||
<ElButton
|
||||
type="default"
|
||||
round
|
||||
@click="handleCancel"
|
||||
>
|
||||
{{ cancelButtonText }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
round
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ confirmButtonText }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { useSlots, ref } from 'vue'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
title: string
|
||||
confirmButtonText?: string
|
||||
cancelButtonText?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
confirmButtonText: $T('CONFIRM'),
|
||||
cancelButtonText: $T('CANCEL')
|
||||
})
|
||||
const $emit = defineEmits(['confirm', 'cancel', 'close'])
|
||||
|
||||
const isCancel = ref(false)
|
||||
const isConfirm = ref(false)
|
||||
|
||||
const visible = useVModel(props, 'modelValue')
|
||||
|
||||
const handleClose = () => {
|
||||
if (isConfirm.value) {
|
||||
return
|
||||
}
|
||||
if (isCancel.value) {
|
||||
$emit('cancel')
|
||||
isCancel.value = false
|
||||
} else {
|
||||
$emit('close')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
isCancel.value = true
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
isConfirm.value = true
|
||||
$emit('confirm')
|
||||
}
|
||||
|
||||
const footerSlots = !!useSlots().footer
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ConfirmDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div
|
||||
id="base-config-form"
|
||||
:class="theme ?? 'dark'"
|
||||
>
|
||||
<el-form
|
||||
ref="$form"
|
||||
label-position="left"
|
||||
label-width="50%"
|
||||
:model="form"
|
||||
size="small"
|
||||
>
|
||||
<slot name="extra-form" />
|
||||
<!-- dynamic config -->
|
||||
<el-form-item
|
||||
v-for="(item, index) in config"
|
||||
:key="item.name + index"
|
||||
:required="item.required"
|
||||
:prop="item.name"
|
||||
:class="index !== config.length - 1 ? 'has-border' : ''"
|
||||
>
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
{{ item.alias || item.name }}
|
||||
<template v-if="item.tips">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
placement="right"
|
||||
>
|
||||
<template #content>
|
||||
<span
|
||||
class="config-form-common-tips"
|
||||
v-html="transformMarkdownToHTML(item.tips)"
|
||||
/>
|
||||
</template>
|
||||
<el-icon class="ml-[4px] cursor-pointer hover:text-blue">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-input
|
||||
v-if="item.type === 'input' || item.type === 'password'"
|
||||
v-model="form[item.name]"
|
||||
:type="item.type === 'password' ? 'password' : 'input'"
|
||||
:placeholder="item.message || item.name"
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="item.type === 'list' && item.choices"
|
||||
v-model="form[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:key="choice.name || choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="item.type === 'checkbox' && item.choices"
|
||||
v-model="form[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
multiple
|
||||
collapse-tags
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:key="choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
/>
|
||||
</el-select>
|
||||
<el-switch
|
||||
v-else-if="item.type === 'confirm'"
|
||||
v-model="form[item.name]"
|
||||
:active-text="item.confirmText || 'yes'"
|
||||
:inactive-text="item.cancelText || 'no'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<slot />
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { marked } from 'marked'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
|
||||
const $form = ref<FormInstance>()
|
||||
|
||||
interface IProps {
|
||||
config: IPicGoPluginConfig[]
|
||||
formModel: IStringKeyMap
|
||||
theme?: 'light' | 'dark'
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const form = useVModel(props, 'formModel')
|
||||
|
||||
function transformMarkdownToHTML (markdown: string) {
|
||||
try {
|
||||
return marked.parse(markdown)
|
||||
} catch (e) {
|
||||
return markdown
|
||||
}
|
||||
}
|
||||
|
||||
async function validate (): Promise<IStringKeyMap | false> {
|
||||
return new Promise((resolve) => {
|
||||
$form.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(form.value)
|
||||
} else {
|
||||
resolve(false)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'BaseConfigForm'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#base-config-form
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
&-item
|
||||
display: flex
|
||||
justify-content space-between
|
||||
border-bottom 1px solid darken(#eee, 50%)
|
||||
padding-bottom 16px
|
||||
&:last-child
|
||||
border-bottom none
|
||||
&__content
|
||||
justify-content flex-end
|
||||
.el-button-group
|
||||
width 100%
|
||||
.el-button
|
||||
width 50%
|
||||
.el-radio-group
|
||||
margin-left 25px
|
||||
.el-switch__label
|
||||
&.is-active
|
||||
color #409EFF
|
||||
&.light
|
||||
.el-form-item.has-border
|
||||
border-bottom 1px solid #ddd
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<el-form-item
|
||||
:label="label"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="handleClick"
|
||||
>
|
||||
{{ buttonLabel }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
interface IProps {
|
||||
label: string
|
||||
buttonLabel: string
|
||||
}
|
||||
|
||||
defineProps<IProps>()
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const handleClick = () => {
|
||||
emit('click')
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ButtonFormItem'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<el-form
|
||||
label-position="left"
|
||||
label-width="50%"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
:label="label"
|
||||
>
|
||||
<el-select
|
||||
v-model="currentValue"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
:placeholder="placeholder"
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in list"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup generic="T extends string | number">
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
|
||||
interface IProps {
|
||||
modelValue: T
|
||||
label: string
|
||||
placeholder?: string
|
||||
list: {
|
||||
label: string
|
||||
value: T
|
||||
}[]
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const currentValue = useVModel(props, 'modelValue')
|
||||
|
||||
const handleChange = (val: T) => {
|
||||
emit('change', val)
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SelectFormItem'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
+15
-17
@@ -2,7 +2,7 @@
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
{{ props.settingText }}
|
||||
{{ props.label }}
|
||||
<template v-if="props.tooltips">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
@@ -10,7 +10,7 @@
|
||||
:content="props.tooltips"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon style="margin-left: 4px">
|
||||
<el-icon class="ml-[4px] cursor-pointer hover:text-blue">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
@@ -28,37 +28,35 @@
|
||||
<script lang="ts" setup>
|
||||
import { T as $T } from '@/i18n'
|
||||
import { saveConfig } from '@/utils/dataSender'
|
||||
import { ref, watch } from 'vue'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
import { showNotification } from '@/utils/common'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
|
||||
interface IProps {
|
||||
tooltips?: string
|
||||
settingProps: string
|
||||
settingText: string
|
||||
defaultValue: boolean
|
||||
settingProps: keyof ISettingForm
|
||||
label: string
|
||||
modelValue: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
// to fix v-model blank bug
|
||||
watch(() => props.defaultValue, (v) => {
|
||||
value.value = v
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
const value = ref<boolean>(props.defaultValue)
|
||||
const emit = defineEmits(['change'])
|
||||
const value = useVModel(props, 'modelValue')
|
||||
|
||||
const handleChange = (val: ICheckBoxValueType) => {
|
||||
saveConfig(props.settingProps, val)
|
||||
emit('change', val)
|
||||
const handleChange = (value: ISwitchValueType) => {
|
||||
saveConfig(`settings.${props.settingProps}`, value)
|
||||
emit('update:modelValue', value)
|
||||
emit('change', value)
|
||||
showNotification(props.label, $T('TIPS_SET_SUCCEED'))
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'CheckboxFormItem'
|
||||
name: 'SwitchFormItem'
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,19 @@
|
||||
import { openURL } from '@/utils/common'
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export function useATagClick () {
|
||||
const handleATagClick = (e: MouseEvent) => {
|
||||
if (e.target instanceof HTMLAnchorElement) {
|
||||
if (e.target.href) {
|
||||
e.preventDefault()
|
||||
openURL(e.target.href)
|
||||
}
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleATagClick)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleATagClick)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { cloneDeep, union } from 'lodash'
|
||||
/**
|
||||
*
|
||||
* @param configList origin config list
|
||||
* @param formModel el-form form model for default value
|
||||
* @param currentConfig current config
|
||||
* @param resetConfigForm reset form model -> clear default value
|
||||
* @returns transformed config list
|
||||
*/
|
||||
export const useConfigForm = () => {
|
||||
return (configList: IPicGoPluginConfig[], formModel: IStringKeyMap, currentConfig?: IStringKeyMap, resetConfigForm?: boolean) => {
|
||||
if (configList.length > 0) {
|
||||
return cloneDeep(configList).map((item) => {
|
||||
// if (!configId) return item
|
||||
if (resetConfigForm) return item
|
||||
let defaultValue = item.default !== undefined
|
||||
? item.default
|
||||
: item.type === 'checkbox'
|
||||
? []
|
||||
: null
|
||||
if (item.type === 'checkbox') {
|
||||
const defaults = item.choices?.filter((i: any) => {
|
||||
return i.checked
|
||||
}).map((i: any) => i.value) || []
|
||||
defaultValue = union(defaultValue, defaults)
|
||||
}
|
||||
if (currentConfig && currentConfig[item.name] !== undefined) {
|
||||
defaultValue = currentConfig[item.name]
|
||||
}
|
||||
formModel[item.name] = defaultValue
|
||||
return item
|
||||
})
|
||||
}
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { computed, getCurrentInstance } from 'vue'
|
||||
|
||||
export type VModelObject = object
|
||||
|
||||
/**
|
||||
* v-model for single prop
|
||||
*/
|
||||
export function useVModel<T extends VModelObject, K extends keyof T> (props: T, key: K) {
|
||||
const vm = getCurrentInstance()
|
||||
return computed({
|
||||
get: () => props[key],
|
||||
set: (value) => {
|
||||
vm?.emit(`update:${key as string}`, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { getCurrentInstance, reactive, UnwrapNestedRefs, watch } from 'vue'
|
||||
|
||||
/**
|
||||
* v-model for multiple props
|
||||
*/
|
||||
export function useVModelValues<T extends object> (props: T, keys: Array<keyof T>) {
|
||||
const vm = getCurrentInstance()
|
||||
const obj = {} as T
|
||||
keys.forEach(key => {
|
||||
obj[key] = props[key]
|
||||
})
|
||||
const mutableValue = reactive(obj) as T
|
||||
|
||||
watch(() => props, (val) => {
|
||||
keys.forEach(key => {
|
||||
mutableValue[key] = val[key]
|
||||
})
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
|
||||
function updateProps () {
|
||||
for (const key of keys) {
|
||||
vm?.emit(`update:${key as string}`, mutableValue[key])
|
||||
}
|
||||
}
|
||||
|
||||
return [mutableValue as UnwrapNestedRefs<T>, updateProps] as const
|
||||
}
|
||||
@@ -214,7 +214,7 @@ import {
|
||||
Close
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage as $message } from 'element-plus'
|
||||
import { T } from '@/i18n/index'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { ref, onBeforeUnmount, Ref, onBeforeMount, watch, nextTick, reactive } from 'vue'
|
||||
import { onBeforeRouteUpdate, useRouter } from 'vue-router'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
@@ -226,7 +226,7 @@ import {
|
||||
IpcRendererEvent,
|
||||
clipboard
|
||||
} from 'electron'
|
||||
import InputBoxDialog from '@/components/InputBoxDialog.vue'
|
||||
import InputBoxDialog from '@/components/dialog/InputBoxDialog.vue'
|
||||
import {
|
||||
MINIMIZE_WINDOW,
|
||||
CLOSE_WINDOW,
|
||||
@@ -324,7 +324,7 @@ function openMiniWindow () {
|
||||
|
||||
function handleCopyPicBedConfig () {
|
||||
clipboard.writeText(picBedConfigString.value)
|
||||
$message.success(T('COPY_PICBED_CONFIG_SUCCEED'))
|
||||
$message.success($T('COPY_PICBED_CONFIG_SUCCEED'))
|
||||
}
|
||||
|
||||
function getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
|
||||
|
||||
+58
-110
@@ -23,7 +23,7 @@
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-select
|
||||
v-model="choosedPicBed"
|
||||
v-model="selectedPicBed"
|
||||
multiple
|
||||
collapse-tags
|
||||
size="small"
|
||||
@@ -76,33 +76,15 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div
|
||||
class="item-base copy round"
|
||||
:class="{ active: isMultiple(choosedList)}"
|
||||
@click="multiCopy"
|
||||
>
|
||||
{{ $T('COPY') }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div
|
||||
class="item-base delete round"
|
||||
:class="{ active: isMultiple(choosedList)}"
|
||||
@click="multiRemove"
|
||||
>
|
||||
{{ $T('DELETE') }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div
|
||||
class="item-base all-pick round"
|
||||
:class="{ active: filterList.length > 0}"
|
||||
@click="toggleSelectAll"
|
||||
>
|
||||
{{ isAllSelected ? $T('CANCEL') : $T('SELECT_ALL') }}
|
||||
</div>
|
||||
</el-col>
|
||||
<GalleryToolbar
|
||||
:selected-list="selectedList"
|
||||
:filter-list="filterList"
|
||||
:is-all-selected="isAllSelected"
|
||||
@multi-copy="multiCopy"
|
||||
@multi-remove="multiRemove"
|
||||
@toggle-select-all="toggleSelectAll"
|
||||
@select-more="selectMore"
|
||||
/>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -172,7 +154,7 @@
|
||||
</el-icon>
|
||||
</el-row>
|
||||
<el-checkbox
|
||||
v-model="choosedList[item.id ? item.id : '']"
|
||||
v-model="selectedList[item.id ? item.id : '']"
|
||||
@change="(val) => handleChooseImage(val, index)"
|
||||
/>
|
||||
</el-row>
|
||||
@@ -214,10 +196,13 @@ import {
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
import { computed, nextTick, onActivated, onBeforeUnmount, onBeforeMount, reactive, ref, watch } from 'vue'
|
||||
import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { getConfig, saveConfig, sendRPC, sendToMain } from '@/utils/dataSender'
|
||||
import { onBeforeRouteUpdate } from 'vue-router'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import $$db from '@/utils/db'
|
||||
import GalleryToolbar from './components/gallery/GalleryToolbar.vue'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { getRawData } from '@/utils/common'
|
||||
const images = ref<ImgInfo[]>([])
|
||||
const dialogVisible = ref(false)
|
||||
const imgInfo = reactive({
|
||||
@@ -225,13 +210,13 @@ const imgInfo = reactive({
|
||||
imgUrl: ''
|
||||
})
|
||||
const $confirm = ElMessageBox.confirm
|
||||
const choosedList: IObjT<boolean> = reactive({})
|
||||
const selectedList: IObjT<boolean> = reactive({})
|
||||
const gallerySliderControl = reactive({
|
||||
visible: false,
|
||||
index: 0
|
||||
})
|
||||
const choosedPicBed = ref<string[]>([])
|
||||
const lastChoosed = ref<number>(-1)
|
||||
const selectedPicBed = ref<string[]>([])
|
||||
const lastSelected = ref<number>(-1)
|
||||
const isShiftKeyPress = ref<boolean>(false)
|
||||
const searchText = ref<string>('')
|
||||
const handleBarActive = ref<boolean>(false)
|
||||
@@ -246,7 +231,7 @@ const pasteStyleMap = {
|
||||
const picBed = ref<IPicBedType[]>([])
|
||||
onBeforeRouteUpdate((to, from) => {
|
||||
if (from.name === 'gallery') {
|
||||
clearChoosedList()
|
||||
clearSelectedList()
|
||||
}
|
||||
if (to.name === 'gallery') {
|
||||
updateGallery()
|
||||
@@ -254,7 +239,7 @@ onBeforeRouteUpdate((to, from) => {
|
||||
})
|
||||
|
||||
onBeforeMount(async () => {
|
||||
ipcRenderer.on('updateGallery', () => {
|
||||
ipcRenderer.on(IRPCActionType.UPDATE_GALLERY, () => {
|
||||
nextTick(async () => {
|
||||
updateGallery()
|
||||
})
|
||||
@@ -282,12 +267,12 @@ const filterList = computed(() => {
|
||||
})
|
||||
|
||||
const isAllSelected = computed(() => {
|
||||
const values = Object.values(choosedList)
|
||||
const values = Object.values(selectedList)
|
||||
if (values.length === 0) {
|
||||
return false
|
||||
} else {
|
||||
return filterList.value.every(item => {
|
||||
return choosedList[item.id!]
|
||||
return selectedList[item.id!]
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -297,18 +282,18 @@ function getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
|
||||
}
|
||||
|
||||
function getGallery (): IGalleryItem[] {
|
||||
if (searchText.value || choosedPicBed.value.length > 0) {
|
||||
if (searchText.value || selectedPicBed.value.length > 0) {
|
||||
return images.value
|
||||
.filter(item => {
|
||||
let isInChoosedPicBed = true
|
||||
let isInSelectedPicBed = true
|
||||
let isIncludesSearchText = true
|
||||
if (choosedPicBed.value.length > 0) {
|
||||
isInChoosedPicBed = choosedPicBed.value.some(type => type === item.type)
|
||||
if (selectedPicBed.value.length > 0) {
|
||||
isInSelectedPicBed = selectedPicBed.value.some(type => type === item.type)
|
||||
}
|
||||
if (searchText.value) {
|
||||
isIncludesSearchText = item.fileName?.includes(searchText.value) || false
|
||||
}
|
||||
return isIncludesSearchText && isInChoosedPicBed
|
||||
return isIncludesSearchText && isInSelectedPicBed
|
||||
}).map(item => {
|
||||
return {
|
||||
...item,
|
||||
@@ -334,30 +319,30 @@ async function updateGallery () {
|
||||
}
|
||||
|
||||
watch(() => filterList, () => {
|
||||
clearChoosedList()
|
||||
clearSelectedList()
|
||||
})
|
||||
|
||||
function handleChooseImage (val: CheckboxValueType, index: number) {
|
||||
if (val === true) {
|
||||
handleBarActive.value = true
|
||||
if (lastChoosed.value !== -1 && isShiftKeyPress.value) {
|
||||
const min = Math.min(lastChoosed.value, index)
|
||||
const max = Math.max(lastChoosed.value, index)
|
||||
if (lastSelected.value !== -1 && isShiftKeyPress.value) {
|
||||
const min = Math.min(lastSelected.value, index)
|
||||
const max = Math.max(lastSelected.value, index)
|
||||
for (let i = min + 1; i < max; i++) {
|
||||
const id = filterList.value[i].id!
|
||||
choosedList[id] = true
|
||||
selectedList[id] = true
|
||||
}
|
||||
}
|
||||
lastChoosed.value = index
|
||||
lastSelected.value = index
|
||||
}
|
||||
}
|
||||
|
||||
function clearChoosedList () {
|
||||
function clearSelectedList () {
|
||||
isShiftKeyPress.value = false
|
||||
Object.keys(choosedList).forEach(key => {
|
||||
choosedList[key] = false
|
||||
Object.keys(selectedList).forEach(key => {
|
||||
selectedList[key] = false
|
||||
})
|
||||
lastChoosed.value = -1
|
||||
lastSelected.value = -1
|
||||
}
|
||||
|
||||
function zoomImage (index: number) {
|
||||
@@ -383,7 +368,7 @@ function handleClose () {
|
||||
}
|
||||
|
||||
async function copy (item: ImgInfo) {
|
||||
const copyLink = await ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
const copyLink = await ipcRenderer.invoke(PASTE_TEXT, getRawData(item))
|
||||
const obj = {
|
||||
title: $T('COPY_LINK_SUCCEED'),
|
||||
body: copyLink
|
||||
@@ -445,33 +430,26 @@ async function confirmModify () {
|
||||
updateGallery()
|
||||
}
|
||||
|
||||
// function choosePicBed (type: string) {
|
||||
// const idx = choosedPicBed.value.indexOf(type)
|
||||
// if (idx !== -1) {
|
||||
// choosedPicBed.value.splice(idx, 1)
|
||||
// } else {
|
||||
// choosedPicBed.value.push(type)
|
||||
// }
|
||||
// }
|
||||
|
||||
function cleanSearch () {
|
||||
searchText.value = ''
|
||||
}
|
||||
|
||||
function isMultiple (obj: IObj) {
|
||||
return Object.values(obj).some(item => item)
|
||||
}
|
||||
|
||||
function toggleSelectAll () {
|
||||
const result = !isAllSelected.value
|
||||
filterList.value.forEach(item => {
|
||||
choosedList[item.id!] = result
|
||||
selectedList[item.id!] = result
|
||||
})
|
||||
}
|
||||
|
||||
function selectMore () {
|
||||
sendRPC(IRPCActionType.GET_GALLERY_MENU_LIST, filterList.value.filter(item => {
|
||||
return selectedList[item.id!]
|
||||
}))
|
||||
}
|
||||
|
||||
function multiRemove () {
|
||||
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
|
||||
const multiRemoveNumber = Object.values(choosedList).filter(item => item).length
|
||||
// selectedList -> { [id]: true or false }; true means selected. false means not selected.
|
||||
const multiRemoveNumber = Object.values(selectedList).filter(item => item).length
|
||||
if (multiRemoveNumber) {
|
||||
$confirm($T('TIPS_WILL_REMOVE_CHOOSED_IMAGES', {
|
||||
m: multiRemoveNumber
|
||||
@@ -481,10 +459,10 @@ function multiRemove () {
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const files: IResult<ImgInfo>[] = []
|
||||
const imageIDList = Object.keys(choosedList)
|
||||
const imageIDList = Object.keys(selectedList)
|
||||
for (let i = 0; i < imageIDList.length; i++) {
|
||||
const key = imageIDList[i]
|
||||
if (choosedList[key]) {
|
||||
if (selectedList[key]) {
|
||||
const file = await $$db.getById<ImgInfo>(key)
|
||||
if (file) {
|
||||
files.push(file)
|
||||
@@ -492,9 +470,9 @@ function multiRemove () {
|
||||
}
|
||||
}
|
||||
}
|
||||
clearChoosedList()
|
||||
clearSelectedList()
|
||||
// TODO: check this
|
||||
// choosedList = {} // 只有删除才能将这个置空
|
||||
// selectedList = {} // 只有删除才能将这个置空
|
||||
const obj = {
|
||||
title: $T('OPERATION_SUCCEED'),
|
||||
body: ''
|
||||
@@ -512,18 +490,18 @@ function multiRemove () {
|
||||
}
|
||||
|
||||
async function multiCopy () {
|
||||
if (Object.values(choosedList).some(item => item)) {
|
||||
if (Object.values(selectedList).some(item => item)) {
|
||||
const copyString: string[] = []
|
||||
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
|
||||
const imageIDList = Object.keys(choosedList)
|
||||
// selectedList -> { [id]: true or false }; true means selected. false means not selected.
|
||||
const imageIDList = Object.keys(selectedList)
|
||||
for (let i = 0; i < imageIDList.length; i++) {
|
||||
const key = imageIDList[i]
|
||||
if (choosedList[key]) {
|
||||
if (selectedList[key]) {
|
||||
const item = await $$db.getById<ImgInfo>(key)
|
||||
if (item) {
|
||||
const txt = await ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
const txt = await ipcRenderer.invoke(PASTE_TEXT, getRawData(item))
|
||||
copyString.push(txt)
|
||||
choosedList[key] = false
|
||||
selectedList[key] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -586,36 +564,6 @@ export default {
|
||||
height 100%
|
||||
.cursor-pointer
|
||||
cursor pointer
|
||||
.item-base
|
||||
background #2E2E2E
|
||||
text-align center
|
||||
padding 5px 0
|
||||
cursor pointer
|
||||
font-size 13px
|
||||
transition all .2s ease-in-out
|
||||
height: 28px
|
||||
box-sizing: border-box
|
||||
&.copy
|
||||
cursor not-allowed
|
||||
background #49B1F5
|
||||
&.active
|
||||
cursor pointer
|
||||
background #1B9EF3
|
||||
color #fff
|
||||
&.delete
|
||||
cursor not-allowed
|
||||
background #F47466
|
||||
&.active
|
||||
cursor pointer
|
||||
background #F15140
|
||||
color #fff
|
||||
&.all-pick
|
||||
cursor not-allowed
|
||||
background #69C282
|
||||
&.active
|
||||
cursor pointer
|
||||
background #44B363
|
||||
color #fff
|
||||
#gallery-view
|
||||
position relative
|
||||
.round
|
||||
@@ -659,7 +607,7 @@ export default {
|
||||
transform scale(1.1)
|
||||
&-img
|
||||
width 100%
|
||||
object-fit fill
|
||||
object-fit contain
|
||||
&__tool-panel
|
||||
color #ddd
|
||||
margin-bottom 4px
|
||||
|
||||
@@ -24,553 +24,48 @@
|
||||
label-width="50%"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_CHOOSE_LANGUAGE')"
|
||||
>
|
||||
<!-- <el-button type="primary" round size="small" @click="openFile('data.json')">{{ $T('SETTINGS_CLICK_TO_OPEN') }}</el-button> -->
|
||||
<el-select
|
||||
v-model="currentLanguage"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
:placeholder="$T('SETTINGS_CHOOSE_LANGUAGE')"
|
||||
@change="handleLanguageChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in languageList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_OPEN_CONFIG_FILE')"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="openFile('data.json')"
|
||||
>
|
||||
{{ $T('SETTINGS_CLICK_TO_OPEN') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_LOG_FILE')"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="openLogSetting"
|
||||
>
|
||||
{{ $T('SETTINGS_CLICK_TO_SET') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SHORTCUT')"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="goShortCutPage"
|
||||
>
|
||||
{{ $T('SETTINGS_CLICK_TO_SET') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="customLinkVisible = true"
|
||||
>
|
||||
{{ $T('SETTINGS_CLICK_TO_SET') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="proxyVisible = true"
|
||||
>
|
||||
{{ $T('SETTINGS_CLICK_TO_SET') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SERVER')"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="serverVisible = true"
|
||||
>
|
||||
{{ $T('SETTINGS_CLICK_TO_SET') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_CHECK_UPDATE')"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="checkUpdate"
|
||||
>
|
||||
{{ $T('SETTINGS_CLICK_TO_CHECK') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_OPEN_UPDATE_HELPER')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.updateHelper"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="updateHelperChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-show="form.updateHelper"
|
||||
:label="$T('SETTINGS_ACCEPT_BETA_UPDATE')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.checkBetaUpdate"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="checkBetaUpdateChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_LAUNCH_ON_BOOT')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.autoStart"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleAutoStartChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_RENAME_BEFORE_UPLOAD')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.rename"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleRename"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_TIMESTAMP_RENAME')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.autoRename"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleAutoRename"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_OPEN_UPLOAD_TIPS')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.uploadNotification"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleUploadNotification"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="os !== 'darwin'"
|
||||
:label="$T('SETTINGS_MINI_WINDOW_ON_TOP')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.miniWindowOntop"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleMiniWindowOntop"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.autoCopyUrl"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleAutoCopyUrl"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
{{ $T('SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD') }}
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="$T('BUILTIN_CLIPBOARD_TIPS')"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon style="margin-left: 4px">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-switch
|
||||
v-model="form.useBuiltinClipboard"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="useBuiltinClipboardChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_ENCODE_OUTPUT_URL')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.encodeOutputURL"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleEncodeOutputURL"
|
||||
/>
|
||||
</el-form-item>
|
||||
<checkbox-form-item
|
||||
v-if="os === 'darwin'"
|
||||
:default-value="form.showDockIcon"
|
||||
setting-props="settings.showDockIcon"
|
||||
:setting-text="$T('SETTINGS_SHOW_DOCK_ICON')"
|
||||
@change="handleShowDockIcon"
|
||||
<SelectAreaSettings
|
||||
:settings="form"
|
||||
/>
|
||||
<ButtonAreaSettings
|
||||
v-model:proxy="proxy"
|
||||
:settings="form"
|
||||
/>
|
||||
<SwitchAreaSettings
|
||||
:settings="form"
|
||||
/>
|
||||
<CustomAreaSettings
|
||||
:settings="form"
|
||||
/>
|
||||
<el-form-item
|
||||
:style="{ marginRight: '-64px' }"
|
||||
:label="$T('CHOOSE_SHOWED_PICBED')"
|
||||
>
|
||||
<el-checkbox-group
|
||||
v-model="form.showPicBedList"
|
||||
@change="handleShowPicBedListChange"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="item in picBed"
|
||||
:key="item.name"
|
||||
:label="item.name"
|
||||
/>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog
|
||||
v-model="customLinkVisible"
|
||||
:title="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<el-form
|
||||
ref="$customLink"
|
||||
label-position="top"
|
||||
:model="customLink"
|
||||
:rules="rules"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
prop="value"
|
||||
>
|
||||
<div class="custom-title">
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_URL') }}
|
||||
</div>
|
||||
<div class="custom-title">
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_FILENAME') }}
|
||||
</div>
|
||||
<div class="custom-title">
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_EXTNAME') }}
|
||||
</div>
|
||||
<el-input
|
||||
v-model="customLink.value"
|
||||
class="align-center"
|
||||
:autofocus="true"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div>
|
||||
{{ $T('SETTINGS_TIPS_SUCH_AS') }}[$fileName]($url)
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button
|
||||
round
|
||||
@click="cancelCustomLink"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmCustomLink"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="proxyVisible"
|
||||
:title="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
|
||||
:modal-append-to-body="false"
|
||||
width="70%"
|
||||
>
|
||||
<el-form
|
||||
label-position="right"
|
||||
:model="customLink"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_UPLOAD_PROXY')"
|
||||
>
|
||||
<el-input
|
||||
v-model="proxy"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:http://127.0.0.1:1080`"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_PLUGIN_INSTALL_PROXY')"
|
||||
>
|
||||
<el-input
|
||||
v-model="npmProxy"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:http://127.0.0.1:1080`"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_PLUGIN_INSTALL_MIRROR')"
|
||||
>
|
||||
<el-input
|
||||
v-model="npmRegistry"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:https://registry.npmmirror.com`"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button
|
||||
round
|
||||
@click="cancelProxy"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmProxy"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="checkUpdateVisible"
|
||||
:title="$T('SETTINGS_CHECK_UPDATE')"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<div>
|
||||
{{ $T('SETTINGS_CURRENT_VERSION') }}: {{ version }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $T('SETTINGS_NEWEST_VERSION') }}: {{ latestVersion ? latestVersion : `${$T('SETTINGS_GETING')}...` }}
|
||||
</div>
|
||||
<div v-if="needUpdate">
|
||||
{{ $T('SETTINGS_TIPS_HAS_NEW_VERSION') }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button
|
||||
round
|
||||
@click="cancelCheckVersion"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmCheckVersion"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="logFileVisible"
|
||||
:title="$T('SETTINGS_SET_LOG_FILE')"
|
||||
:modal-append-to-body="false"
|
||||
width="500px"
|
||||
>
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="150px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_LOG_FILE')"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="openFile('picgo.log')"
|
||||
>
|
||||
{{ $T('SETTINGS_CLICK_TO_OPEN') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_LOG_LEVEL')"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.logLevel"
|
||||
multiple
|
||||
collapse-tags
|
||||
style="width: 100%;"
|
||||
>
|
||||
<el-option
|
||||
v-for="(value, key) of logLevel"
|
||||
:key="key"
|
||||
:label="value"
|
||||
:value="key"
|
||||
:disabled="handleLevelDisabled(key)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$T('SETTINGS_LOG_FILE_SIZE')} (MB)`"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.logFileSizeLimit"
|
||||
style="width: 100%;"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:10`"
|
||||
:controls="false"
|
||||
:min="1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button
|
||||
round
|
||||
@click="cancelLogLevelSetting"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmLogLevelSetting"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="serverVisible"
|
||||
class="server-dialog"
|
||||
width="60%"
|
||||
:title="$T('SETTINGS_SET_PICGO_SERVER')"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<div class="notice-text">
|
||||
{{ $T('SETTINGS_TIPS_SERVER_NOTICE') }}
|
||||
</div>
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_ENABLE_SERVER')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="server.enable"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<template v-if="server.enable">
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SERVER_HOST')"
|
||||
>
|
||||
<el-input
|
||||
v-model="server.host"
|
||||
type="input"
|
||||
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_HOST')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SERVER_PORT')"
|
||||
>
|
||||
<el-input
|
||||
v-model="server.port"
|
||||
type="number"
|
||||
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_PORT')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button
|
||||
round
|
||||
@click="cancelServerSetting"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmServerSetting"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ElForm, ElMessage as $message, FormRules } from 'element-plus'
|
||||
import { Reading, QuestionFilled } from '@element-plus/icons-vue'
|
||||
import pkg from 'root/package.json'
|
||||
import { ElForm } from 'element-plus'
|
||||
import { Reading } from '@element-plus/icons-vue'
|
||||
import { IConfig } from 'picgo'
|
||||
import { PICGO_OPEN_FILE, OPEN_URL, GET_PICBEDS } from '#/events/constants'
|
||||
import {
|
||||
ipcRenderer
|
||||
} from 'electron'
|
||||
import { i18nManager, T as $T } from '@/i18n/index'
|
||||
import { enforceNumber } from '~/universal/utils/common'
|
||||
import { compare } from 'compare-versions'
|
||||
import { STABLE_RELEASE_URL, BETA_RELEASE_URL } from '#/utils/static'
|
||||
import { computed, onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue'
|
||||
import { getConfig, saveConfig, sendRPC, sendToMain, triggerRPC } from '@/utils/dataSender'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { SHORTKEY_PAGE } from '@/router/config'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import CheckboxFormItem from '@/components/settings/CheckboxFormItem.vue'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { enforceNumber, isLinux } from '~/universal/utils/common'
|
||||
import { onBeforeMount, reactive, ref } from 'vue'
|
||||
import { getConfig } from '@/utils/dataSender'
|
||||
import ButtonAreaSettings from './components/settings/buttonArea/ButtonAreaSettings.vue'
|
||||
import SwitchAreaSettings from './components/settings/switchArea/SwitchAreaSettings.vue'
|
||||
import CustomAreaSettings from './components/settings/customArea/CustomAreaSettings.vue'
|
||||
import SelectAreaSettings from './components/settings/selectArea/SelectAreaSettings.vue'
|
||||
import { openURL } from '@/utils/common'
|
||||
import { IStartupMode } from '#/types/enum'
|
||||
|
||||
const $customLink = ref<InstanceType<typeof ElForm> | null>(null)
|
||||
|
||||
const customLinkRule = (rule: any, value: string, callback: (arg0?: Error) => void) => {
|
||||
if (!/\$url/.test(value) && !/\$fileName/.test(value) && !/\$extName/.test(value)) {
|
||||
return callback(new Error($T('TIPS_MUST_CONTAINS_URL')))
|
||||
} else {
|
||||
return callback()
|
||||
}
|
||||
}
|
||||
const $router = useRouter()
|
||||
const form = reactive<ISettingForm>({
|
||||
updateHelper: false,
|
||||
showUpdateTip: false,
|
||||
showPicBedList: [],
|
||||
autoStart: false,
|
||||
rename: false,
|
||||
autoRename: false,
|
||||
uploadNotification: false,
|
||||
miniWindowOntop: false,
|
||||
miniWindowOnTop: false,
|
||||
logLevel: ['all'],
|
||||
autoCopyUrl: true,
|
||||
checkBetaUpdate: true,
|
||||
@@ -578,72 +73,21 @@ const form = reactive<ISettingForm>({
|
||||
language: 'zh-CN',
|
||||
logFileSizeLimit: 10,
|
||||
encodeOutputURL: true,
|
||||
showDockIcon: true
|
||||
})
|
||||
|
||||
const languageList = i18nManager.languageList.map(item => ({
|
||||
label: item.label,
|
||||
value: item.value
|
||||
}))
|
||||
|
||||
const currentLanguage = ref('zh-CN')
|
||||
|
||||
const picBed = ref<IPicBedType[]>([])
|
||||
|
||||
const logFileVisible = ref(false)
|
||||
const customLinkVisible = ref(false)
|
||||
const checkUpdateVisible = ref(false)
|
||||
const serverVisible = ref(false)
|
||||
const proxyVisible = ref(false)
|
||||
|
||||
const customLink = reactive({
|
||||
value: '$url'
|
||||
})
|
||||
|
||||
const shortKey = reactive<IShortKeyMap>({
|
||||
upload: ''
|
||||
showDockIcon: true,
|
||||
customLink: '$url',
|
||||
npmProxy: '',
|
||||
npmRegistry: '',
|
||||
server: {
|
||||
port: 36677,
|
||||
host: '127.0.0.1',
|
||||
enable: true
|
||||
},
|
||||
startupMode: IStartupMode.HIDE
|
||||
})
|
||||
|
||||
const proxy = ref('')
|
||||
const npmRegistry = ref('')
|
||||
const npmProxy = ref('')
|
||||
const rules = reactive<FormRules>({
|
||||
value: [
|
||||
{ validator: customLinkRule, trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const logLevel = {
|
||||
all: $T('SETTINGS_LOG_LEVEL_ALL'),
|
||||
success: $T('SETTINGS_LOG_LEVEL_SUCCESS'),
|
||||
error: $T('SETTINGS_LOG_LEVEL_ERROR'),
|
||||
info: $T('SETTINGS_LOG_LEVEL_INFO'),
|
||||
warn: $T('SETTINGS_LOG_LEVEL_WARN'),
|
||||
none: $T('SETTINGS_LOG_LEVEL_NONE')
|
||||
}
|
||||
|
||||
const server = ref({
|
||||
port: 36677,
|
||||
host: '127.0.0.1',
|
||||
enable: true
|
||||
})
|
||||
|
||||
const version = pkg.version
|
||||
const latestVersion = ref('')
|
||||
const os = ref('')
|
||||
|
||||
const needUpdate = computed(() => {
|
||||
if (latestVersion.value) {
|
||||
return compareVersion2Update(version, latestVersion.value)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
os.value = process.platform
|
||||
sendToMain(GET_PICBEDS)
|
||||
ipcRenderer.on(GET_PICBEDS, getPicBeds)
|
||||
initData()
|
||||
})
|
||||
|
||||
@@ -652,31 +96,26 @@ async function initData () {
|
||||
if (config !== undefined) {
|
||||
const settings = config.settings || {}
|
||||
const picBed = config.picBed
|
||||
form.updateHelper = settings.showUpdateTip || false
|
||||
form.showUpdateTip = settings.showUpdateTip || false
|
||||
form.autoStart = settings.autoStart || false
|
||||
form.rename = settings.rename || false
|
||||
form.autoRename = settings.autoRename || false
|
||||
form.uploadNotification = settings.uploadNotification || false
|
||||
form.miniWindowOntop = settings.miniWindowOntop || false
|
||||
form.miniWindowOnTop = settings.miniWindowOnTop || false
|
||||
form.logLevel = initLogLevel(settings.logLevel || [])
|
||||
form.autoCopyUrl = settings.autoCopy === undefined ? true : settings.autoCopy
|
||||
form.autoCopyUrl = settings.autoCopyUrl === undefined ? true : settings.autoCopyUrl
|
||||
form.checkBetaUpdate = settings.checkBetaUpdate === undefined ? true : settings.checkBetaUpdate
|
||||
form.useBuiltinClipboard = settings.useBuiltinClipboard === undefined ? false : settings.useBuiltinClipboard
|
||||
form.language = settings.language ?? 'zh-CN'
|
||||
form.encodeOutputURL = settings.encodeOutputURL === undefined ? true : settings.encodeOutputURL
|
||||
currentLanguage.value = settings.language ?? 'zh-CN'
|
||||
customLink.value = settings.customLink || '$url'
|
||||
shortKey.upload = settings.shortKey.upload
|
||||
form.encodeOutputURL = settings.encodeOutputURL === undefined ? false : settings.encodeOutputURL
|
||||
form.customLink = settings.customLink || '$url'
|
||||
form.npmProxy = settings.npmProxy || ''
|
||||
form.npmRegistry = settings.npmRegistry || ''
|
||||
proxy.value = picBed.proxy || ''
|
||||
npmRegistry.value = settings.registry || ''
|
||||
npmProxy.value = settings.proxy || ''
|
||||
server.value = settings.server || {
|
||||
port: 36677,
|
||||
host: '127.0.0.1',
|
||||
enable: true
|
||||
}
|
||||
form.server = settings.server
|
||||
form.logFileSizeLimit = enforceNumber(settings.logFileSizeLimit) || 10
|
||||
form.showDockIcon = settings.showDockIcon === undefined ? true : settings.showDockIcon
|
||||
form.startupMode = settings.startupMode || (isLinux ? IStartupMode.SHOW_MINI_WINDOW : IStartupMode.HIDE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,264 +130,10 @@ function initLogLevel (logLevel: string | string[]) {
|
||||
return logLevel
|
||||
}
|
||||
|
||||
function getPicBeds (event: Event, picBeds: IPicBedType[]) {
|
||||
picBed.value = picBeds
|
||||
form.showPicBedList = picBed.value.map(item => {
|
||||
if (item.visible) {
|
||||
return item.name
|
||||
}
|
||||
return null
|
||||
}).filter(item => item) as string[]
|
||||
}
|
||||
|
||||
function openFile (file: string) {
|
||||
sendToMain(PICGO_OPEN_FILE, file)
|
||||
}
|
||||
|
||||
function openLogSetting () {
|
||||
logFileVisible.value = true
|
||||
}
|
||||
|
||||
async function cancelCustomLink () {
|
||||
customLinkVisible.value = false
|
||||
customLink.value = await getConfig<string>('settings.customLink') || '$url'
|
||||
}
|
||||
|
||||
function confirmCustomLink () {
|
||||
$customLink.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
saveConfig('settings.customLink', customLink.value)
|
||||
customLinkVisible.value = false
|
||||
sendToMain('updateCustomLink')
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function cancelProxy () {
|
||||
proxyVisible.value = false
|
||||
proxy.value = await getConfig<string>('picBed.proxy') || ''
|
||||
}
|
||||
|
||||
function confirmProxy () {
|
||||
proxyVisible.value = false
|
||||
saveConfig({
|
||||
'picBed.proxy': proxy.value,
|
||||
'settings.proxy': npmProxy.value,
|
||||
'settings.registry': npmRegistry.value
|
||||
})
|
||||
const successNotification = new Notification($T('SETTINGS_SET_PROXY_AND_MIRROR'), {
|
||||
body: $T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function updateHelperChange (val: ICheckBoxValueType) {
|
||||
saveConfig('settings.showUpdateTip', val)
|
||||
}
|
||||
|
||||
function checkBetaUpdateChange (val: ICheckBoxValueType) {
|
||||
saveConfig('settings.checkBetaUpdate', val)
|
||||
}
|
||||
|
||||
function useBuiltinClipboardChange (val: ICheckBoxValueType) {
|
||||
saveConfig('settings.useBuiltinClipboard', val)
|
||||
}
|
||||
|
||||
function handleShowPicBedListChange (val: ICheckBoxValueType[]) {
|
||||
const list = picBed.value.map(item => {
|
||||
if (!val.includes(item.name)) {
|
||||
item.visible = false
|
||||
} else {
|
||||
item.visible = true
|
||||
}
|
||||
return item
|
||||
})
|
||||
saveConfig({
|
||||
'picBed.list': list
|
||||
})
|
||||
sendToMain(GET_PICBEDS)
|
||||
}
|
||||
|
||||
function handleAutoStartChange (val: ICheckBoxValueType) {
|
||||
saveConfig('settings.autoStart', val)
|
||||
sendToMain('autoStart', val)
|
||||
}
|
||||
|
||||
function handleRename (val: ICheckBoxValueType) {
|
||||
saveConfig({
|
||||
'settings.rename': val
|
||||
})
|
||||
}
|
||||
|
||||
function handleAutoRename (val: ICheckBoxValueType) {
|
||||
saveConfig({
|
||||
'settings.autoRename': val
|
||||
})
|
||||
}
|
||||
|
||||
function compareVersion2Update (current: string, latest: string): boolean {
|
||||
return compare(current, latest, '<')
|
||||
}
|
||||
|
||||
async function checkUpdate () {
|
||||
checkUpdateVisible.value = true
|
||||
const version = await triggerRPC<string>(IRPCActionType.GET_LATEST_VERSION, form.checkBetaUpdate)
|
||||
if (version) {
|
||||
latestVersion.value = version
|
||||
} else {
|
||||
latestVersion.value = $T('TIPS_NETWORK_ERROR')
|
||||
}
|
||||
}
|
||||
|
||||
function confirmCheckVersion () {
|
||||
if (needUpdate.value) {
|
||||
sendToMain(OPEN_URL, form.checkBetaUpdate ? BETA_RELEASE_URL : STABLE_RELEASE_URL)
|
||||
}
|
||||
checkUpdateVisible.value = false
|
||||
}
|
||||
|
||||
function cancelCheckVersion () {
|
||||
checkUpdateVisible.value = false
|
||||
}
|
||||
|
||||
function handleUploadNotification (val: ICheckBoxValueType) {
|
||||
saveConfig({
|
||||
'settings.uploadNotification': val
|
||||
})
|
||||
}
|
||||
|
||||
function handleMiniWindowOntop (val: ICheckBoxValueType) {
|
||||
saveConfig('settings.miniWindowOntop', val)
|
||||
$message.info($T('TIPS_NEED_RELOAD'))
|
||||
}
|
||||
|
||||
function handleAutoCopyUrl (val: ICheckBoxValueType) {
|
||||
saveConfig('settings.autoCopy', val)
|
||||
const successNotification = new Notification($T('SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD'), {
|
||||
body: $T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function handleEncodeOutputURL (val: ICheckBoxValueType) {
|
||||
saveConfig('settings.encodeOutputURL', val)
|
||||
const successNotification = new Notification($T('SETTINGS_ENCODE_OUTPUT_URL'), {
|
||||
body: $T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function confirmLogLevelSetting () {
|
||||
if (form.logLevel.length === 0) {
|
||||
return $message.error($T('TIPS_PLEASE_CHOOSE_LOG_LEVEL'))
|
||||
}
|
||||
saveConfig({
|
||||
'settings.logLevel': form.logLevel,
|
||||
'settings.logFileSizeLimit': form.logFileSizeLimit
|
||||
})
|
||||
const successNotification = new Notification($T('SETTINGS_SET_LOG_FILE'), {
|
||||
body: $T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
logFileVisible.value = false
|
||||
}
|
||||
|
||||
async function cancelLogLevelSetting () {
|
||||
logFileVisible.value = false
|
||||
let logLevel = await getConfig<string | string[]>('settings.logLevel')
|
||||
const logFileSizeLimit = await getConfig<number>('settings.logFileSizeLimit') || 10
|
||||
if (!Array.isArray(logLevel)) {
|
||||
if (logLevel && logLevel.length > 0) {
|
||||
logLevel = [logLevel]
|
||||
} else {
|
||||
logLevel = ['all']
|
||||
}
|
||||
}
|
||||
form.logLevel = logLevel
|
||||
form.logFileSizeLimit = logFileSizeLimit
|
||||
}
|
||||
|
||||
function confirmServerSetting () {
|
||||
server.value.port = parseInt(server.value.port as unknown as string, 10)
|
||||
saveConfig({
|
||||
'settings.server': server.value
|
||||
})
|
||||
const successNotification = new Notification($T('SETTINGS_SET_PICGO_SERVER'), {
|
||||
body: $T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
serverVisible.value = false
|
||||
sendToMain('updateServer')
|
||||
}
|
||||
|
||||
async function cancelServerSetting () {
|
||||
serverVisible.value = false
|
||||
server.value = await getConfig('settings.server') || {
|
||||
port: 36677,
|
||||
host: '127.0.0.1',
|
||||
enable: true
|
||||
}
|
||||
}
|
||||
|
||||
function handleLevelDisabled (val: string) {
|
||||
const currentLevel = val
|
||||
let flagLevel
|
||||
const result = form.logLevel.some(item => {
|
||||
if (item === 'all' || item === 'none') {
|
||||
flagLevel = item
|
||||
}
|
||||
return (item === 'all' || item === 'none')
|
||||
})
|
||||
if (result) {
|
||||
if (currentLevel !== flagLevel) {
|
||||
return true
|
||||
}
|
||||
} else if (form.logLevel.length > 0) {
|
||||
if (val === 'all' || val === 'none') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function handleLanguageChange (val: string) {
|
||||
i18nManager.setCurrentLanguage(val)
|
||||
saveConfig({
|
||||
'settings.language': val
|
||||
})
|
||||
sendToMain(GET_PICBEDS)
|
||||
}
|
||||
|
||||
function handleShowDockIcon (val: ICheckBoxValueType) {
|
||||
sendRPC(IRPCActionType.SHOW_DOCK_ICON, val)
|
||||
}
|
||||
|
||||
function goConfigPage () {
|
||||
sendToMain(OPEN_URL, 'https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
|
||||
openURL('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
|
||||
}
|
||||
|
||||
function goShortCutPage () {
|
||||
$router.push({
|
||||
name: SHORTKEY_PAGE
|
||||
})
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeListener(GET_PICBEDS, getPicBeds)
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
<template #footer>
|
||||
<el-button
|
||||
round
|
||||
@click="dialogVisible = false"
|
||||
@click="handleCancelConfig"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
@@ -450,6 +450,11 @@ async function handleConfirmConfig () {
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancelConfig () {
|
||||
dialogVisible.value = false
|
||||
$configForm.value?.resetConfig()
|
||||
}
|
||||
|
||||
function _getSearchResult (val: string) {
|
||||
// this.$http.get(`https://api.npms.io/v2/search?q=${val}`)
|
||||
axios.get(`https://registry.npmjs.com/-/v1/search?text=${val}`)
|
||||
@@ -480,7 +485,7 @@ function handleSearchResult (item: INPMSearchResultObject) {
|
||||
return {
|
||||
name,
|
||||
fullName: item.package.name,
|
||||
author: item.package.author.name,
|
||||
author: item.package.maintainers[0]?.username || '',
|
||||
description: item.package.description,
|
||||
logo: `https://cdn.jsdelivr.net/npm/${item.package.name}/logo.png`,
|
||||
config: {},
|
||||
|
||||
@@ -1,20 +1,37 @@
|
||||
<template>
|
||||
<div id="rename-page">
|
||||
<div class="p-[20px] flex flex-col justify-between">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('FILE_RENAME')"
|
||||
prop="fileName"
|
||||
:rules="[
|
||||
{ required: true, message: 'file name is required', trigger: 'blur' }
|
||||
]"
|
||||
>
|
||||
<el-input
|
||||
v-model="fileName"
|
||||
v-model="form.fileName"
|
||||
size="small"
|
||||
autofocus
|
||||
@keyup.enter="confirmName"
|
||||
/>
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon
|
||||
class="el-input__icon"
|
||||
style="cursor: pointer;"
|
||||
@click="form.fileName = ''"
|
||||
>
|
||||
<close />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row>
|
||||
<div class="pull-right">
|
||||
<div class="w-full flex justify-end mt-[20px]">
|
||||
<el-button
|
||||
round
|
||||
size="small"
|
||||
@@ -35,35 +52,52 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { RENAME_FILE_NAME } from '#/events/constants'
|
||||
import { Close } from '@element-plus/icons-vue'
|
||||
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME } from '#/events/constants'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
import { onBeforeUnmount, onBeforeMount, ref } from 'vue'
|
||||
const fileName = ref('')
|
||||
const originName = ref('')
|
||||
import { onBeforeUnmount, onBeforeMount, ref, reactive } from 'vue'
|
||||
import { useIPCOn } from '@/hooks/useIPC'
|
||||
import { FormInstance } from 'element-plus'
|
||||
const id = ref<string | null>(null)
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const form = reactive({
|
||||
fileName: '',
|
||||
originName: ''
|
||||
})
|
||||
|
||||
const handleFileName = (event: IpcRendererEvent, newName: string, _originName: string, _id: string) => {
|
||||
form.fileName = newName
|
||||
form.originName = _originName
|
||||
id.value = _id
|
||||
}
|
||||
|
||||
useIPCOn(RENAME_FILE_NAME, handleFileName)
|
||||
|
||||
onBeforeMount(() => {
|
||||
ipcRenderer.on(RENAME_FILE_NAME, (event: IpcRendererEvent, newName: string, _originName: string, _id: string) => {
|
||||
fileName.value = newName
|
||||
originName.value = _originName
|
||||
id.value = _id
|
||||
})
|
||||
ipcRenderer.send(GET_RENAME_FILE_NAME)
|
||||
})
|
||||
|
||||
function confirmName () {
|
||||
sendToMain(`${RENAME_FILE_NAME}${id.value}`, fileName.value)
|
||||
formRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
sendToMain(`${RENAME_FILE_NAME}${id.value}`, form.fileName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function cancel () {
|
||||
// if cancel, use origin file name
|
||||
sendToMain(`${RENAME_FILE_NAME}${id.value}`, originName.value)
|
||||
sendToMain(`${RENAME_FILE_NAME}${id.value}`, form.originName)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeAllListeners('rename')
|
||||
ipcRenderer.removeAllListeners(RENAME_FILE_NAME)
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -73,10 +107,6 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#rename-page
|
||||
padding 0 20px
|
||||
.pull-right
|
||||
float right
|
||||
.el-form-item__label
|
||||
color #ddd
|
||||
.el-form-item__label
|
||||
color #ddd
|
||||
</style>
|
||||
|
||||
@@ -119,6 +119,7 @@ import { ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import { onBeforeUnmount, onBeforeMount, ref, watch } from 'vue'
|
||||
import { getConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
|
||||
const list = ref<IShortKeyConfig[]>([])
|
||||
const keyBindingVisible = ref(false)
|
||||
|
||||
@@ -70,6 +70,7 @@ import { IResult } from '@picgo/store/dist/types'
|
||||
import { PASTE_TEXT, OPEN_WINDOW } from '#/events/constants'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
import { getRawData } from '@/utils/common'
|
||||
|
||||
const files = ref<IResult<ImgInfo>[]>([])
|
||||
const notification = reactive({
|
||||
@@ -92,8 +93,8 @@ async function getData () {
|
||||
|
||||
async function copyTheLink (item: ImgInfo) {
|
||||
notification.body = item.imgUrl!
|
||||
await ipcRenderer.invoke(PASTE_TEXT, getRawData(item))
|
||||
const myNotification = new Notification(notification.title, notification)
|
||||
ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
:span="20"
|
||||
:offset="2"
|
||||
>
|
||||
<div class="view-title">
|
||||
{{ $T('PICTURE_UPLOAD') }} - {{ picBedName }}
|
||||
<div class="view-title text-[22px]">
|
||||
{{ $T('PICTURE_UPLOAD') }} - {{ picBedName }} - {{ configName }}
|
||||
<el-icon
|
||||
style="cursor: pointer; margin-left: 4px;"
|
||||
@click="handleChangePicBed"
|
||||
@@ -23,7 +23,7 @@
|
||||
>
|
||||
<div
|
||||
id="upload-dragger"
|
||||
@click="openUplodWindow"
|
||||
@click="openUploadWindow"
|
||||
>
|
||||
<el-icon>
|
||||
<UploadFilled />
|
||||
@@ -100,25 +100,25 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
import { UploadFilled, CaretBottom } from '@element-plus/icons-vue'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
import { ref, onBeforeMount, onBeforeUnmount, watch } from 'vue'
|
||||
import { T as $T } from '@/i18n'
|
||||
import $bus from '@/utils/bus'
|
||||
import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { CaretBottom, UploadFilled } from '@element-plus/icons-vue'
|
||||
import {
|
||||
IpcRendererEvent,
|
||||
ipcRenderer
|
||||
} from 'electron'
|
||||
import { ElMessage as $message } from 'element-plus'
|
||||
import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
|
||||
import {
|
||||
GET_PICBEDS,
|
||||
SHOW_INPUT_BOX,
|
||||
SHOW_INPUT_BOX_RESPONSE,
|
||||
SHOW_UPLOAD_PAGE_MENU,
|
||||
GET_PICBEDS
|
||||
SHOW_UPLOAD_PAGE_MENU
|
||||
} from '~/universal/events/constants'
|
||||
import {
|
||||
isUrl
|
||||
} from '~/universal/utils/common'
|
||||
import { ElMessage as $message } from 'element-plus'
|
||||
import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
const dragover = ref(false)
|
||||
const progress = ref(0)
|
||||
const showProgress = ref(false)
|
||||
@@ -126,6 +126,7 @@ const showError = ref(false)
|
||||
const pasteStyle = ref('')
|
||||
const picBed = ref<IPicBedType[]>([])
|
||||
const picBedName = ref('')
|
||||
const configName = ref('')
|
||||
onBeforeMount(() => {
|
||||
ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, _progress: number) => {
|
||||
if (_progress !== -1) {
|
||||
@@ -205,7 +206,7 @@ function handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer)
|
||||
}
|
||||
}
|
||||
|
||||
function openUplodWindow () {
|
||||
function openUploadWindow () {
|
||||
document.getElementById('file-uploader')!.click()
|
||||
}
|
||||
|
||||
@@ -262,11 +263,13 @@ function handleInputBoxValue (val: string) {
|
||||
|
||||
async function getDefaultPicBed () {
|
||||
const currentPicBed = await getConfig<string>('picBed.current')
|
||||
const currentConfigName = await getConfig<string>(`picBed.${currentPicBed}._configName`) || 'Default'
|
||||
picBed.value.forEach(item => {
|
||||
if (item.type === currentPicBed) {
|
||||
picBedName.value = item.name
|
||||
}
|
||||
})
|
||||
configName.value = currentConfigName
|
||||
}
|
||||
|
||||
function getPicBeds (event: Event, picBeds: IPicBedType[]) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div id="config-list-view">
|
||||
<div
|
||||
id="config-list-view"
|
||||
class="h-[425px]"
|
||||
>
|
||||
<div class="view-title">
|
||||
{{ $T('SETTINGS') }}
|
||||
</div>
|
||||
@@ -193,7 +196,7 @@ export default {
|
||||
flex-wrap wrap
|
||||
width: 98%
|
||||
.config-item
|
||||
height 85px
|
||||
height 100px
|
||||
margin-bottom 20px
|
||||
border-radius 4px
|
||||
cursor pointer
|
||||
@@ -239,9 +242,10 @@ export default {
|
||||
.selected
|
||||
border 1px solid #409EFF
|
||||
.set-default-container
|
||||
position absolute
|
||||
bottom 10px
|
||||
position fixed
|
||||
bottom 20px
|
||||
width 100%
|
||||
.set-default-btn
|
||||
width 250px
|
||||
transform translateX(-50%)
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<el-col :span="12">
|
||||
<div class="w-full flex justify-between gap-x-[6px]">
|
||||
<div
|
||||
class="item-base copy round gap-x-[10px]"
|
||||
:class="{ active: isMultiple(selectedList)}"
|
||||
@click="$emit('multiCopy')"
|
||||
>
|
||||
{{ $T('COPY') }}
|
||||
</div>
|
||||
<div
|
||||
class="item-base delete round"
|
||||
:class="{ active: isMultiple(selectedList)}"
|
||||
@click="$emit('multiRemove')"
|
||||
>
|
||||
{{ $T('DELETE') }}
|
||||
</div>
|
||||
<div
|
||||
class="item-base all-pick round"
|
||||
:class="{ active: filterList.length > 0}"
|
||||
@click="$emit('toggleSelectAll')"
|
||||
>
|
||||
{{ isAllSelected ? $T('CANCEL') : $T('SELECT_ALL') }}
|
||||
</div>
|
||||
<div
|
||||
class="item-base select-more round !w-[18%]"
|
||||
:class="{ active: filterList.length > 0}"
|
||||
@click="$emit('selectMore')"
|
||||
>
|
||||
<el-icon name="el-icon-arrow-left">
|
||||
<MoreFilled />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<ConfigFormDialog />
|
||||
</el-col>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { T as $T } from '@/i18n'
|
||||
import { MoreFilled } from '@element-plus/icons-vue'
|
||||
import ConfigFormDialog from '@/components/dialog/ConfigFormDialog.vue'
|
||||
|
||||
interface IProps {
|
||||
selectedList: IObjT<boolean>
|
||||
filterList: IGalleryItem[]
|
||||
isAllSelected: boolean
|
||||
}
|
||||
defineProps<IProps>()
|
||||
|
||||
defineEmits(['multiCopy', 'multiRemove', 'toggleSelectAll', 'selectMore'])
|
||||
|
||||
function isMultiple (obj: IObj) {
|
||||
return Object.values(obj).some(item => item)
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'GalleryToolbar'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.item-base
|
||||
background #2E2E2E
|
||||
text-align center
|
||||
width: 22%
|
||||
flex-grow: 1
|
||||
cursor pointer
|
||||
font-size 12px
|
||||
transition all .2s ease-in-out
|
||||
height: 24px
|
||||
line-height: 28px
|
||||
box-sizing: border-box
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
&.copy
|
||||
cursor not-allowed
|
||||
background #49B1F5
|
||||
&.active
|
||||
cursor pointer
|
||||
background #1B9EF3
|
||||
color #fff
|
||||
&.delete
|
||||
cursor not-allowed
|
||||
background #F47466
|
||||
&.active
|
||||
cursor pointer
|
||||
background #F15140
|
||||
color #fff
|
||||
&.all-pick
|
||||
cursor not-allowed
|
||||
background #69C282
|
||||
&.active
|
||||
cursor pointer
|
||||
background #44B363
|
||||
color #fff
|
||||
&.select-more
|
||||
cursor pointer
|
||||
background #858585
|
||||
color #fff
|
||||
</style>
|
||||
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_OPEN_CONFIG_FILE')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_OPEN')"
|
||||
@click="openFile('data.json')"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_SET_LOG_FILE')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_SET')"
|
||||
@click="logFileDialogVisible = true"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_SET_SHORTCUT')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_SET')"
|
||||
@click="goShortCutPage"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_SET')"
|
||||
@click="customLinkDialogVisible = true"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_SET')"
|
||||
@click="proxySettingDialogVisible = true"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_SET_SERVER')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_SET')"
|
||||
@click="serverSettingDialogVisible = true"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_CHECK_UPDATE')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_CHECK')"
|
||||
@click="checkUpdateDialogVisible = true"
|
||||
/>
|
||||
<LogSettingDialog
|
||||
v-model="logFileDialogVisible"
|
||||
v-model:log-level="form.logLevel"
|
||||
v-model:log-file-size-limit="form.logFileSizeLimit"
|
||||
/>
|
||||
<CustomLinkDialog
|
||||
v-model="customLinkDialogVisible"
|
||||
v-model:custom-link="form.customLink"
|
||||
/>
|
||||
<ProxySettingDialog
|
||||
v-model="proxySettingDialogVisible"
|
||||
v-model:proxy="proxyString"
|
||||
v-model:npm-proxy="form.npmProxy"
|
||||
v-model:npm-registry="form.npmRegistry"
|
||||
/>
|
||||
<ServerSettingsDialog
|
||||
v-model="serverSettingDialogVisible"
|
||||
v-model:host="form.server.host"
|
||||
v-model:port="form.server.port"
|
||||
v-model:enable="form.server.enable"
|
||||
/>
|
||||
<CheckUpdateDialog
|
||||
v-model="checkUpdateDialogVisible"
|
||||
v-model:checkBetaUpdate="form.checkBetaUpdate"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ButtonFormItem from '@/components/settings/ButtonFormItem.vue'
|
||||
import CustomLinkDialog from './CustomLinkDialog.vue'
|
||||
import ProxySettingDialog from './ProxySettingDialog.vue'
|
||||
import ServerSettingsDialog from './ServerSettingsDialog.vue'
|
||||
import CheckUpdateDialog from './CheckUpdateDialog.vue'
|
||||
import { T as $T } from '@/i18n'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { PICGO_OPEN_FILE } from '~/universal/events/constants'
|
||||
import LogSettingDialog from './LogSettingDialog.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { SHORTKEY_PAGE } from '@/router/config'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
|
||||
const $router = useRouter()
|
||||
|
||||
interface IProps {
|
||||
settings: ISettingForm
|
||||
proxy: string
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const form = reactive(props.settings)
|
||||
const proxyString = useVModel(props, 'proxy')
|
||||
|
||||
const logFileDialogVisible = ref(false)
|
||||
const customLinkDialogVisible = ref(false)
|
||||
const proxySettingDialogVisible = ref(false)
|
||||
const serverSettingDialogVisible = ref(false)
|
||||
const checkUpdateDialogVisible = ref(false)
|
||||
|
||||
function openFile (file: string) {
|
||||
sendToMain(PICGO_OPEN_FILE, file)
|
||||
}
|
||||
|
||||
function goShortCutPage () {
|
||||
$router.push({
|
||||
name: SHORTKEY_PAGE
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ButtonAreaSettings'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$T('SETTINGS_CHECK_UPDATE')"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<div>
|
||||
{{ $T('SETTINGS_CURRENT_VERSION') }}: {{ version }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $T('SETTINGS_NEWEST_VERSION') }}: {{ latestVersion ? latestVersion : `${$T('SETTINGS_GETING')}...` }}
|
||||
</div>
|
||||
<div v-if="needUpdate">
|
||||
{{ $T('SETTINGS_TIPS_HAS_NEW_VERSION') }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button
|
||||
size="default"
|
||||
round
|
||||
@click="cancelCheckVersion"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmCheckVersion"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { T as $T } from '@/i18n'
|
||||
import { STABLE_RELEASE_URL, BETA_RELEASE_URL } from '#/utils/static'
|
||||
import pkg from 'root/package.json'
|
||||
import { compare } from 'compare-versions'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { triggerRPC } from '@/utils/dataSender'
|
||||
import { openURL } from '@/utils/common'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
checkBetaUpdate: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const dialogVisible = useVModel(props, 'modelValue')
|
||||
const checkBetaUpdate = useVModel(props, 'checkBetaUpdate')
|
||||
|
||||
const version = pkg.version
|
||||
const latestVersion = ref('')
|
||||
|
||||
function compareVersion2Update (current: string, latest: string): boolean {
|
||||
return compare(current, latest, '<')
|
||||
}
|
||||
|
||||
const needUpdate = computed(() => {
|
||||
if (latestVersion.value) {
|
||||
return compareVersion2Update(version, latestVersion.value)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => dialogVisible.value, (value) => {
|
||||
if (value) {
|
||||
checkUpdate()
|
||||
}
|
||||
})
|
||||
|
||||
async function checkUpdate () {
|
||||
const version = await triggerRPC<string>(IRPCActionType.GET_LATEST_VERSION, checkBetaUpdate.value)
|
||||
if (version) {
|
||||
latestVersion.value = version
|
||||
} else {
|
||||
latestVersion.value = $T('TIPS_NETWORK_ERROR')
|
||||
}
|
||||
}
|
||||
|
||||
function confirmCheckVersion () {
|
||||
if (needUpdate.value) {
|
||||
openURL(checkBetaUpdate.value ? BETA_RELEASE_URL : STABLE_RELEASE_URL)
|
||||
}
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
function cancelCheckVersion () {
|
||||
latestVersion.value = ''
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'CheckUpdateDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<el-form
|
||||
ref="$customLink"
|
||||
label-position="top"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
prop="customLink"
|
||||
>
|
||||
<div class="flex flex-col mb-[8px]">
|
||||
<div>
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_URL') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_FILENAME') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_EXTNAME') }}
|
||||
</div>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="form.customLink"
|
||||
class="align-center"
|
||||
:autofocus="true"
|
||||
size="default"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div>
|
||||
{{ $T('SETTINGS_TIPS_SUCH_AS') }}[$fileName]($url)
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button
|
||||
size="default"
|
||||
round
|
||||
@click="cancelCustomLink"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmCustomLink"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { T as $T } from '@/i18n'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { ElForm, FormRules } from 'element-plus'
|
||||
import { useVModelValues } from '@/hooks/useVModelValues'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
customLink: string
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const [form, updateProps] = useVModelValues<IProps>(props, ['customLink'])
|
||||
|
||||
const $customLink = ref<InstanceType<typeof ElForm> | null>(null)
|
||||
|
||||
const dialogVisible = useVModel(props, 'modelValue')
|
||||
|
||||
const customLinkRule = (rule: any, value: string, callback: (arg0?: Error) => void) => {
|
||||
if (!/\$url/.test(value) && !/\$fileName/.test(value) && !/\$extName/.test(value)) {
|
||||
return callback(new Error($T('TIPS_MUST_CONTAINS_URL')))
|
||||
} else {
|
||||
return callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
value: [
|
||||
{ validator: customLinkRule, trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
function confirmCustomLink () {
|
||||
$customLink.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
saveConfig('settings.customLink', form.customLink)
|
||||
dialogVisible.value = false
|
||||
sendToMain('updateCustomLink')
|
||||
updateProps()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function cancelCustomLink () {
|
||||
dialogVisible.value = false
|
||||
form.customLink = props.customLink
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'CustomLinkDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$T('SETTINGS_SET_LOG_FILE')"
|
||||
:append-to-body="true"
|
||||
width="500px"
|
||||
>
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="150px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_LOG_FILE')"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="openFile('picgo.log')"
|
||||
>
|
||||
{{ $T('SETTINGS_CLICK_TO_OPEN') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_LOG_LEVEL')"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.logLevel"
|
||||
multiple
|
||||
collapse-tags
|
||||
style="width: 100%;"
|
||||
>
|
||||
<el-option
|
||||
v-for="(value, key) of logLevelMap"
|
||||
:key="key"
|
||||
:label="value"
|
||||
:value="key"
|
||||
:disabled="handleLevelDisabled(key)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$T('SETTINGS_LOG_FILE_SIZE')} (MB)`"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.logFileSizeLimit"
|
||||
style="width: 100%;"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:10`"
|
||||
:controls="false"
|
||||
:min="1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button
|
||||
size="default"
|
||||
round
|
||||
@click="cancelLogLevelSetting"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmLogLevelSetting"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { T as $T } from '@/i18n'
|
||||
import { saveConfig } from '@/utils/dataSender'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { openFile, showNotification } from '@/utils/common'
|
||||
import { ElMessage as $message } from 'element-plus'
|
||||
import { useVModelValues } from '@/hooks/useVModelValues'
|
||||
|
||||
const logLevelMap = {
|
||||
all: $T('SETTINGS_LOG_LEVEL_ALL'),
|
||||
success: $T('SETTINGS_LOG_LEVEL_SUCCESS'),
|
||||
error: $T('SETTINGS_LOG_LEVEL_ERROR'),
|
||||
info: $T('SETTINGS_LOG_LEVEL_INFO'),
|
||||
warn: $T('SETTINGS_LOG_LEVEL_WARN'),
|
||||
none: $T('SETTINGS_LOG_LEVEL_NONE')
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
logLevel: string[]
|
||||
logFileSizeLimit: number
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const dialogVisible = useVModel(props, 'modelValue')
|
||||
|
||||
const [form, updateProps] = useVModelValues<IProps>(props, ['logFileSizeLimit', 'logLevel'])
|
||||
|
||||
async function cancelLogLevelSetting () {
|
||||
dialogVisible.value = false
|
||||
let logLevel = props.logLevel as string | string[]
|
||||
if (!Array.isArray(logLevel)) {
|
||||
if (logLevel && logLevel.length > 0) {
|
||||
logLevel = [logLevel]
|
||||
} else {
|
||||
logLevel = ['all']
|
||||
}
|
||||
}
|
||||
form.logLevel = logLevel
|
||||
form.logFileSizeLimit = props.logFileSizeLimit
|
||||
}
|
||||
|
||||
function confirmLogLevelSetting () {
|
||||
if (form.logLevel.length === 0) {
|
||||
return $message.error($T('TIPS_PLEASE_CHOOSE_LOG_LEVEL'))
|
||||
}
|
||||
saveConfig({
|
||||
'settings.logLevel': form.logLevel,
|
||||
'settings.logFileSizeLimit': form.logFileSizeLimit
|
||||
})
|
||||
showNotification($T('SETTINGS_SET_LOG_FILE'), $T('TIPS_SET_SUCCEED'))
|
||||
updateProps()
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
function handleLevelDisabled (val: string) {
|
||||
const currentLevel = val
|
||||
let flagLevel
|
||||
const result = form.logLevel.some((item: any) => {
|
||||
if (item === 'all' || item === 'none') {
|
||||
flagLevel = item
|
||||
}
|
||||
return (item === 'all' || item === 'none')
|
||||
})
|
||||
if (result) {
|
||||
if (currentLevel !== flagLevel) {
|
||||
return true
|
||||
}
|
||||
} else if (form.logLevel.length > 0) {
|
||||
if (val === 'all' || val === 'none') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'LogSettingDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
|
||||
:append-to-body="true"
|
||||
width="70%"
|
||||
>
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_UPLOAD_PROXY')"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.proxy"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:http://127.0.0.1:1080`"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_PLUGIN_INSTALL_PROXY')"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.npmProxy"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:http://127.0.0.1:1080`"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_PLUGIN_INSTALL_MIRROR')"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.npmRegistry"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:https://registry.npmmirror.com`"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button
|
||||
size="default"
|
||||
round
|
||||
@click="cancelProxy"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmProxy"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { T as $T } from '@/i18n'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { saveConfig } from '@/utils/dataSender'
|
||||
import { useVModelValues } from '@/hooks/useVModelValues'
|
||||
import { showNotification } from '@/utils/common'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
proxy: string
|
||||
npmProxy: string
|
||||
npmRegistry: string
|
||||
}
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const [form, updateProps] = useVModelValues<IProps>(props, ['proxy', 'npmProxy', 'npmRegistry'])
|
||||
|
||||
const dialogVisible = useVModel(props, 'modelValue')
|
||||
|
||||
function confirmProxy () {
|
||||
dialogVisible.value = false
|
||||
saveConfig({
|
||||
'picBed.proxy': form.proxy,
|
||||
'settings.npmProxy': form.npmProxy,
|
||||
'settings.npmRegistry': form.npmRegistry
|
||||
})
|
||||
showNotification($T('SETTINGS_SET_PROXY_AND_MIRROR'), $T('TIPS_SET_SUCCEED'))
|
||||
updateProps()
|
||||
}
|
||||
|
||||
async function cancelProxy () {
|
||||
dialogVisible.value = false
|
||||
form.proxy = props.proxy || ''
|
||||
form.npmProxy = props.npmProxy || ''
|
||||
form.npmRegistry = props.npmRegistry || ''
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ProxySettingDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
class="server-dialog"
|
||||
width="60%"
|
||||
:title="$T('SETTINGS_SET_PICGO_SERVER')"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<div class="notice-text">
|
||||
{{ $T('SETTINGS_TIPS_SERVER_NOTICE') }}
|
||||
</div>
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_ENABLE_SERVER')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.enable"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<template v-if="form.enable">
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SERVER_HOST')"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.host"
|
||||
type="input"
|
||||
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_HOST')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SERVER_PORT')"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.port"
|
||||
type="number"
|
||||
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_PORT')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button
|
||||
size="default"
|
||||
round
|
||||
@click="cancelServerSetting"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmServerSetting"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { T as $T } from '@/i18n'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { useVModelValues } from '@/hooks/useVModelValues'
|
||||
import { saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { showNotification } from '@/utils/common'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
port: number
|
||||
host: string
|
||||
enable: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const dialogVisible = useVModel(props, 'modelValue')
|
||||
const [form, updateProps] = useVModelValues<IProps>(props, ['port', 'host', 'enable'])
|
||||
|
||||
function confirmServerSetting () {
|
||||
form.port = parseInt(form.port as unknown as string, 10)
|
||||
saveConfig({
|
||||
'settings.server': form
|
||||
})
|
||||
showNotification($T('SETTINGS_SET_PICGO_SERVER'), $T('TIPS_SET_SUCCEED'))
|
||||
dialogVisible.value = false
|
||||
sendToMain('updateServer')
|
||||
updateProps()
|
||||
}
|
||||
|
||||
async function cancelServerSetting () {
|
||||
dialogVisible.value = false
|
||||
form.port = props.port
|
||||
form.host = props.host
|
||||
form.enable = props.enable
|
||||
}
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ServerSettingsDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<el-form-item
|
||||
:style="{ marginRight: '-64px' }"
|
||||
:label="$T('CHOOSE_SHOWED_PICBED')"
|
||||
>
|
||||
<el-checkbox-group
|
||||
v-model="showPicBedList"
|
||||
@change="handleShowPicBedListChange"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="item in picBed"
|
||||
:key="item.name"
|
||||
:label="item.name"
|
||||
/>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
import { T as $T } from '@/i18n'
|
||||
import { GET_PICBEDS } from '#/events/constants'
|
||||
import { saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { useIPCOn } from '@/hooks/useIPC'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
|
||||
interface IProps {
|
||||
showPicBedList: string[]
|
||||
}
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const picBed = ref<IPicBedType[]>([])
|
||||
const showPicBedList = useVModel(props, 'showPicBedList')
|
||||
|
||||
function handleShowPicBedListChange (val: ISwitchValueType[]) {
|
||||
const list = picBed.value.map(item => {
|
||||
if (!val.includes(item.name)) {
|
||||
item.visible = false
|
||||
} else {
|
||||
item.visible = true
|
||||
}
|
||||
return item
|
||||
})
|
||||
saveConfig({
|
||||
'picBed.list': list
|
||||
})
|
||||
sendToMain(GET_PICBEDS)
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
sendToMain(GET_PICBEDS)
|
||||
useIPCOn(GET_PICBEDS, getPicBeds)
|
||||
})
|
||||
|
||||
function getPicBeds (event: Event, picBeds: IPicBedType[]) {
|
||||
picBed.value = picBeds
|
||||
showPicBedList.value = picBed.value.map(item => {
|
||||
if (item.visible) {
|
||||
return item.name
|
||||
}
|
||||
return null
|
||||
}).filter(item => item) as string[]
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ChoosePicBed'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<ChoosePicBed
|
||||
v-model:showPicBedList="form.showPicBedList"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue'
|
||||
import ChoosePicBed from './ChoosePicBed.vue'
|
||||
|
||||
interface IProps {
|
||||
settings: ISettingForm
|
||||
}
|
||||
const props = defineProps<IProps>()
|
||||
const form = reactive(props.settings)
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'CustomAreaSettings'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<SelectFormItem
|
||||
v-model="form.language"
|
||||
:list="languageList"
|
||||
:label="$T('SETTINGS_CHOOSE_LANGUAGE')"
|
||||
:placeholder="$T('SETTINGS_CHOOSE_LANGUAGE')"
|
||||
@change="handleLanguageChange"
|
||||
/>
|
||||
<SelectFormItem
|
||||
v-model="form.startupMode"
|
||||
:list="startupModeList"
|
||||
:label="$T('SETTINGS_STARTUP_MODE')"
|
||||
@change="handleChangeStartupMode"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue'
|
||||
import { T as $T, i18nManager } from '@/i18n'
|
||||
import { saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { GET_PICBEDS } from '~/universal/events/constants'
|
||||
import SelectFormItem from '@/components/settings/SelectFormItem.vue'
|
||||
import { IStartupMode } from '~/universal/types/enum'
|
||||
import { isMacOS } from '~/universal/utils/common'
|
||||
|
||||
interface IProps {
|
||||
settings: ISettingForm
|
||||
}
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const form = reactive(props.settings)
|
||||
|
||||
const languageList = i18nManager.languageList.map(item => ({
|
||||
label: item.label,
|
||||
value: item.value
|
||||
}))
|
||||
|
||||
const startupModeList = [
|
||||
{
|
||||
label: $T('SETTINGS_STARTUP_MODE_MAIN_WINDOW'),
|
||||
value: IStartupMode.SHOW_MAIN_WINDOW
|
||||
},
|
||||
{
|
||||
label: $T('SETTINGS_STARTUP_MODE_MINI_WINDOW'),
|
||||
value: IStartupMode.SHOW_MINI_WINDOW,
|
||||
hide: isMacOS
|
||||
},
|
||||
{
|
||||
label: $T('SETTINGS_STARTUP_MODE_HIDE'),
|
||||
value: IStartupMode.HIDE
|
||||
}
|
||||
].filter(item => !item.hide)
|
||||
|
||||
function handleLanguageChange (val: string) {
|
||||
i18nManager.setCurrentLanguage(val)
|
||||
saveConfig({
|
||||
'settings.language': val
|
||||
})
|
||||
sendToMain(GET_PICBEDS)
|
||||
}
|
||||
|
||||
function handleChangeStartupMode (val: IStartupMode) {
|
||||
saveConfig({
|
||||
'settings.startupMode': val
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SelectAreaSettings'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<SwitchFormItem
|
||||
v-model="form.showUpdateTip"
|
||||
setting-props="showUpdateTip"
|
||||
:label="$T('SETTINGS_OPEN_UPDATE_HELPER')"
|
||||
/>
|
||||
<SwitchFormItem
|
||||
v-show="form.showUpdateTip"
|
||||
v-model="form.checkBetaUpdate"
|
||||
setting-props="checkBetaUpdate"
|
||||
:label="$T('SETTINGS_ACCEPT_BETA_UPDATE')"
|
||||
/>
|
||||
<SwitchFormItem
|
||||
v-model="form.autoStart"
|
||||
setting-props="autoStart"
|
||||
:label="$T('SETTINGS_LAUNCH_ON_BOOT')"
|
||||
@change="handleAutoStartChange"
|
||||
/>
|
||||
<SwitchFormItem
|
||||
v-model="form.rename"
|
||||
setting-props="rename"
|
||||
:label="$T('SETTINGS_RENAME_BEFORE_UPLOAD')"
|
||||
/>
|
||||
<SwitchFormItem
|
||||
v-model="form.autoRename"
|
||||
setting-props="autoRename"
|
||||
:label="$T('SETTINGS_TIMESTAMP_RENAME')"
|
||||
/>
|
||||
<SwitchFormItem
|
||||
v-model="form.uploadNotification"
|
||||
setting-props="uploadNotification"
|
||||
:label="$T('SETTINGS_OPEN_UPLOAD_TIPS')"
|
||||
/>
|
||||
<SwitchFormItem
|
||||
v-if="os !== 'darwin'"
|
||||
v-model="form.miniWindowOnTop"
|
||||
:label="$T('SETTINGS_MINI_WINDOW_ON_TOP')"
|
||||
setting-props="miniWindowOnTop"
|
||||
@change="handleMiniWindowOnTop"
|
||||
/>
|
||||
<SwitchFormItem
|
||||
v-model="form.autoCopyUrl"
|
||||
:label="$T('SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD')"
|
||||
setting-props="autoCopyUrl"
|
||||
/>
|
||||
<SwitchFormItem
|
||||
v-model="form.useBuiltinClipboard"
|
||||
:label="$T('SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD')"
|
||||
:tooltips="$T('BUILTIN_CLIPBOARD_TIPS')"
|
||||
setting-props="useBuiltinClipboard"
|
||||
/>
|
||||
<SwitchFormItem
|
||||
v-model="form.encodeOutputURL"
|
||||
:label="$T('SETTINGS_ENCODE_OUTPUT_URL')"
|
||||
setting-props="encodeOutputURL"
|
||||
/>
|
||||
<SwitchFormItem
|
||||
v-if="os === 'darwin'"
|
||||
v-model="form.showDockIcon"
|
||||
setting-props="showDockIcon"
|
||||
:label="$T('SETTINGS_SHOW_DOCK_ICON')"
|
||||
@change="handleShowDockIcon"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { ElMessage as $message } from 'element-plus'
|
||||
import { sendRPC, sendToMain } from '@/utils/dataSender'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import SwitchFormItem from '@/components/settings/SwitchFormItem.vue'
|
||||
|
||||
const os = ref(process.platform)
|
||||
|
||||
interface IProps {
|
||||
settings: ISettingForm
|
||||
}
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const form = reactive(props.settings)
|
||||
|
||||
function handleAutoStartChange (val: ISwitchValueType) {
|
||||
sendToMain('autoStart', val)
|
||||
}
|
||||
|
||||
function handleMiniWindowOnTop () {
|
||||
$message.info($T('TIPS_NEED_RELOAD'))
|
||||
}
|
||||
|
||||
function handleShowDockIcon (val: ISwitchValueType) {
|
||||
sendRPC(IRPCActionType.SHOW_DOCK_ICON, val)
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SwitchAreaSettings'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -43,16 +43,17 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { ref, onBeforeUnmount, onBeforeMount } from 'vue'
|
||||
import { ref, onBeforeMount } from 'vue'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { sendToMain, triggerRPC } from '@/utils/dataSender'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import ConfigForm from '@/components/ConfigForm.vue'
|
||||
// import mixin from '@/utils/ConfirmButtonMixin'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
import { GET_PICBED_CONFIG } from '~/universal/events/constants'
|
||||
import { useIPCOn } from '@/hooks/useIPC'
|
||||
const type = ref('')
|
||||
const config = ref<IPicGoPluginConfig[]>([])
|
||||
const picBedName = ref('')
|
||||
@@ -61,9 +62,10 @@ const $router = useRouter()
|
||||
const $configForm = ref<InstanceType<typeof ConfigForm> | null>(null)
|
||||
type.value = $route.params.type as string
|
||||
|
||||
useIPCOn(GET_PICBED_CONFIG, getPicBeds)
|
||||
|
||||
onBeforeMount(() => {
|
||||
sendToMain('getPicBedConfig', $route.params.type)
|
||||
ipcRenderer.on('getPicBedConfig', getPicBeds)
|
||||
sendToMain(GET_PICBED_CONFIG, $route.params.type)
|
||||
})
|
||||
|
||||
const handleConfirm = async () => {
|
||||
@@ -85,10 +87,6 @@ function getPicBeds (event: IpcRendererEvent, _config: IPicGoPluginConfig[], nam
|
||||
picBedName.value = name
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeListener('getPicBedConfig', getPicBeds)
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { isReactive, isRef, toRaw, unref } from 'vue'
|
||||
import { sendToMain } from './dataSender'
|
||||
import { OPEN_URL, PICGO_OPEN_FILE } from '~/universal/events/constants'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
/* eslint-disable camelcase */
|
||||
@@ -51,3 +53,20 @@ export const getRawData = (args: any): any => {
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
export const showNotification = (title: string, body: string) => {
|
||||
const notification = new Notification(title, {
|
||||
body
|
||||
})
|
||||
notification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export const openFile = (fileName: string) => {
|
||||
sendToMain(PICGO_OPEN_FILE, fileName)
|
||||
}
|
||||
|
||||
export const openURL = (url: string) => {
|
||||
sendToMain(OPEN_URL, url)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import { PICGO_SAVE_CONFIG, PICGO_GET_CONFIG, RPC_ACTIONS } from '#/events/constants'
|
||||
import { uuid } from 'uuidv4'
|
||||
import { PICGO_GET_CONFIG, PICGO_SAVE_CONFIG, RPC_ACTIONS } from '#/events/constants'
|
||||
import { IpcRendererEvent, ipcRenderer } from 'electron'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { getRawData } from './common'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { IObject, IResult, IGetResult, IFilter } from '@picgo/store/dist/types'
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import { uuid } from 'uuidv4'
|
||||
import {
|
||||
PICGO_GET_BY_ID_DB,
|
||||
PICGO_GET_DB,
|
||||
PICGO_INSERT_DB,
|
||||
PICGO_INSERT_MANY_DB,
|
||||
PICGO_UPDATE_BY_ID_DB,
|
||||
PICGO_GET_BY_ID_DB,
|
||||
PICGO_REMOVE_BY_ID_DB
|
||||
PICGO_REMOVE_BY_ID_DB,
|
||||
PICGO_UPDATE_BY_ID_DB
|
||||
} from '#/events/constants'
|
||||
import { IGalleryDB } from '#/types/extra-vue'
|
||||
import { IFilter, IGetResult, IObject, IResult } from '@picgo/store/dist/types'
|
||||
import { IpcRendererEvent, ipcRenderer } from 'electron'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { getRawData } from './common'
|
||||
export class GalleryDB implements IGalleryDB {
|
||||
async get<T> (filter?: IFilter): Promise<IGetResult<T>> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uuid } from 'uuidv4'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export const completeUploaderMetaConfig = (originData: IStringKeyMap): IStringKeyMap => {
|
||||
return Object.assign({
|
||||
|
||||
@@ -29,12 +29,14 @@ export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN'
|
||||
export const PASTE_TEXT = 'PASTE_TEXT'
|
||||
export const SET_MINI_WINDOW_POS = 'SET_MINI_WINDOW_POS'
|
||||
export const RENAME_FILE_NAME = 'RENAME_FILE_NAME'
|
||||
export const GET_RENAME_FILE_NAME = 'GET_RENAME_FILE_NAME'
|
||||
export const SHOW_MAIN_PAGE_QRCODE = 'SHOW_MAIN_PAGE_QRCODE'
|
||||
export const SHOW_MAIN_PAGE_DONATION = 'SHOW_MAIN_PAGE_DONATION'
|
||||
export const FORCE_UPDATE = 'FORCE_UPDATE'
|
||||
export const OPEN_WINDOW = 'OPEN_WINDOW'
|
||||
export const GET_PICBEDS = 'GET_PICBEDS'
|
||||
export const RPC_ACTIONS = 'RPC_ACTIONS'
|
||||
export const GET_PICBED_CONFIG = 'GET_PICBED_CONFIG'
|
||||
// i18n
|
||||
export const GET_CURRENT_LANGUAGE = 'GET_CURRENT_LANGUAGE'
|
||||
export const GET_LANGUAGE_LIST = 'GET_LANGUAGE_LIST'
|
||||
|
||||
@@ -72,6 +72,11 @@ export enum IRPCActionType {
|
||||
OPEN_FILE = 'OPEN_FILE',
|
||||
COPY_TEXT = 'COPY_TEXT',
|
||||
SHOW_DOCK_ICON = 'SHOW_DOCK_ICON',
|
||||
|
||||
// gallery and toolbox rpc
|
||||
UPDATE_GALLERY = 'UPDATE_GALLERY',
|
||||
GET_GALLERY_MENU_LIST = 'GET_GALLERY_MENU_LIST',
|
||||
OPEN_CONFIG_DIALOG = 'OPEN_CONFIG_DIALOG',
|
||||
}
|
||||
|
||||
export enum IToolboxItemType {
|
||||
@@ -87,3 +92,9 @@ export enum IToolboxItemCheckStatus {
|
||||
SUCCESS = 'success',
|
||||
ERROR = 'error',
|
||||
}
|
||||
|
||||
export enum IStartupMode {
|
||||
SHOW_MAIN_WINDOW = 'SHOW_SETTING_WINDOW',
|
||||
SHOW_MINI_WINDOW = 'SHOW_MINI_WINDOW',
|
||||
HIDE = 'HIDE'
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -15,7 +15,7 @@ declare module 'vue/types/vue' {
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
declare module 'vue' {
|
||||
interface ComponentCustomProperties {
|
||||
$http: typeof axios
|
||||
$builtInPicBed: string[]
|
||||
|
||||
Vendored
+14
@@ -7,3 +7,17 @@ declare var notificationList: IAppNotification[]
|
||||
declare module 'epipebomb' {
|
||||
export default function epipebomb(stream: NodeJS.Process['stdout'], callback: () => void): void
|
||||
}
|
||||
|
||||
// 扩展原生 IncomingMessage,添加 multer 字段
|
||||
declare module 'http' {
|
||||
interface IncomingMessage {
|
||||
files?: {
|
||||
path: string;
|
||||
originalname: string;
|
||||
mimetype: string;
|
||||
size: number;
|
||||
[key: string]: any;
|
||||
}[];
|
||||
body?: any;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+11
@@ -54,6 +54,8 @@ interface ILocales {
|
||||
FILE_RENAME: string
|
||||
COPY_FILE_PATH: string
|
||||
OPEN_FILE_PATH: string
|
||||
SUCCESS: string
|
||||
FAILED: string
|
||||
SETTINGS: string
|
||||
SETTINGS_OPEN_CONFIG_FILE: string
|
||||
SETTINGS_CLICK_TO_OPEN: string
|
||||
@@ -114,6 +116,10 @@ interface ILocales {
|
||||
SELECTED_SETTING_HINT: string
|
||||
SETTINGS_ENCODE_OUTPUT_URL: string
|
||||
SETTINGS_SHOW_DOCK_ICON: string
|
||||
SETTINGS_STARTUP_MODE: string
|
||||
SETTINGS_STARTUP_MODE_MAIN_WINDOW: string
|
||||
SETTINGS_STARTUP_MODE_MINI_WINDOW: string
|
||||
SETTINGS_STARTUP_MODE_HIDE: string
|
||||
SHORTCUT_NAME: string
|
||||
SHORTCUT_BIND: string
|
||||
SHORTCUT_STATUS: string
|
||||
@@ -125,6 +131,11 @@ interface ILocales {
|
||||
SHORTCUT_DISABLE: string
|
||||
SHORTCUT_EDIT: string
|
||||
SHORTCUT_CHANGE_UPLOAD: string
|
||||
CHANGE_IMAGE_URL_HOST: string
|
||||
NEW_IMAGE_URL_HOST: string
|
||||
SELECTED_IMAGE_URL_HOST: string
|
||||
CHANGE_IMAGE_URL_HOST_RESULT: string
|
||||
CHANGE_IMAGE_URL_HOST_WARN: string
|
||||
WAIT_TO_UPLOAD: string
|
||||
ALREADY_UPLOAD: string
|
||||
PICTURE_UPLOAD: string
|
||||
|
||||
Vendored
+1
@@ -8,6 +8,7 @@ type IToolboxCheckArgs = [type: import('./enum').IToolboxItemType]
|
||||
type IOpenFileArgs = [filePath: string]
|
||||
type ICopyTextArgs = [text: string]
|
||||
type IShowDockIconArgs = [visible: boolean]
|
||||
type IGetGalleryMenuListArgs = [selectedList: IGalleryItem[]]
|
||||
|
||||
interface IRPCServer {
|
||||
start: () => void
|
||||
|
||||
Vendored
+20
-4
@@ -1,4 +1,7 @@
|
||||
// global
|
||||
|
||||
type FN = (...args: any) => any
|
||||
|
||||
interface IObj {
|
||||
[propName: string]: any
|
||||
}
|
||||
@@ -162,9 +165,19 @@ interface IPicGoPluginConfig {
|
||||
name?: string
|
||||
value?: any
|
||||
}[]
|
||||
/** support markdown */
|
||||
tips?: string
|
||||
[propName: string]: any
|
||||
}
|
||||
|
||||
interface IPicGoPluginShowConfigDialogOption {
|
||||
title: string
|
||||
config: IPicGoPluginConfig[]
|
||||
tips?: string
|
||||
confirmText?: string
|
||||
cancelText?: string
|
||||
}
|
||||
|
||||
interface IPicGoPluginOriginConfig {
|
||||
name: string
|
||||
type: string
|
||||
@@ -200,9 +213,10 @@ interface INPMSearchResultObject {
|
||||
version: string
|
||||
description: string
|
||||
keywords: string[]
|
||||
author: {
|
||||
name: string
|
||||
}
|
||||
maintainers: Array<{
|
||||
email: string
|
||||
username: string
|
||||
}>
|
||||
links: {
|
||||
npm: string
|
||||
homepage: string
|
||||
@@ -219,6 +233,8 @@ interface IGuiApi {
|
||||
upload: (input: IUploadOption) => Promise<ImgInfo[]>
|
||||
showNotification: (options?: IShowNotificationOption) => void
|
||||
showMessageBox: (options?: IShowMessageBoxOption) => Promise<IShowMessageBoxResult>
|
||||
showConfigDialog: <T extends IStringKeyMap>(options: IPicGoPluginShowConfigDialogOption) => Promise<T | false>
|
||||
galleryDB: import('@picgo/store').DBStore
|
||||
}
|
||||
interface IShowInputBoxOption {
|
||||
value?: string
|
||||
@@ -430,4 +446,4 @@ interface IUploaderConfigItem {
|
||||
|
||||
type IUploaderConfigListItem = IStringKeyMap & IUploaderListItemMetaInfo
|
||||
|
||||
type ICheckBoxValueType = boolean | string | number
|
||||
type ISwitchValueType = boolean | string | number
|
||||
|
||||
Vendored
+15
-2
@@ -1,11 +1,11 @@
|
||||
interface ISettingForm {
|
||||
updateHelper: boolean
|
||||
showUpdateTip: boolean
|
||||
showPicBedList: string[]
|
||||
autoStart: boolean
|
||||
rename: boolean
|
||||
autoRename: boolean
|
||||
uploadNotification: boolean
|
||||
miniWindowOntop: boolean
|
||||
miniWindowOnTop: boolean
|
||||
logLevel: string[]
|
||||
autoCopyUrl: boolean
|
||||
checkBetaUpdate: boolean
|
||||
@@ -14,6 +14,15 @@ interface ISettingForm {
|
||||
logFileSizeLimit: number
|
||||
encodeOutputURL: boolean
|
||||
showDockIcon: boolean
|
||||
customLink: string
|
||||
npmProxy: string
|
||||
npmRegistry: string
|
||||
server: {
|
||||
port: number
|
||||
host: string
|
||||
enable: boolean
|
||||
}
|
||||
startupMode: import('#/types/enum').IStartupMode
|
||||
}
|
||||
|
||||
interface IShortKeyMap {
|
||||
@@ -33,3 +42,7 @@ interface IToolboxItem {
|
||||
type IToolboxMap = {
|
||||
[id in import('#/types/enum').IToolboxItemType]: IToolboxItem
|
||||
}
|
||||
|
||||
interface IFormInstance {
|
||||
validate: () => Promise<IStringKeyMap | false>
|
||||
}
|
||||
|
||||
@@ -51,3 +51,7 @@ export const trimValues = (obj: IStringKeyMap) => {
|
||||
})
|
||||
return newObj
|
||||
}
|
||||
|
||||
export const isMacOS = process.platform === 'darwin'
|
||||
export const isWindows = process.platform === 'win32'
|
||||
export const isLinux = process.platform === 'linux'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const CLIPBOARD_IMAGE_FOLDER = 'picgo-clipboard-images'
|
||||
export const FORM_IMAGE_FOLDER = 'picgo-form-images'
|
||||
export const RELEASE_URL = 'https://api.github.com/repos/Molunerfinn/PicGo/releases'
|
||||
export const RELEASE_URL_BACKUP = 'https://picgo-release.molunerfinn.com'
|
||||
export const STABLE_RELEASE_URL = 'https://github.com/Molunerfinn/PicGo/releases/latest'
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./public/index.html',
|
||||
'./src/**/*.{js,ts,jsx,tsx,vue}'
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
blue: '#49B1F5',
|
||||
green: '#44B363',
|
||||
red: '#F15140',
|
||||
yellow: '#F1BE48'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
corePlugins: {
|
||||
// due to https://github.com/tailwindlabs/tailwindcss/issues/6602 - buttons disappear
|
||||
preflight: false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user