Compare commits

...
Author SHA1 Message Date
PiEgg 89c24e7f8d 🎉 Release: v2.4.1-beta.1 2025-12-10 23:18:44 +08:00
PiEgg 54d15a6749 🐛 Fix(custom): the issue that x64 macOS app can't be opened
ISSUES CLOSED: #1363
2025-12-10 23:17:53 +08:00
PiEgg 2cc29833df 📦 Chore: update builder config && add legacy version file upload process 2025-12-09 17:42:22 +08:00
PiEgg f695e7ccaf 🎉 Release: v2.4.1-beta.0 2025-12-09 15:47:26 +08:00
PiEgg 366ac11ee2 📦 Chore: disabled build universal installer 2025-12-09 15:46:32 +08:00
PiEgg 9d4ea8277d 📦 Chore: change release url 2025-12-08 21:27:03 +08:00
PiEggandGitHub 56f61d458e 🐛 Fix: tray window clipboard image not show bug (#1362) 2025-12-08 21:16:25 +08:00
PiEgg 8e94b2a4d4 📦 Chore: fix upload dist arch bug 2025-12-08 18:47:05 +08:00
PiEgg db627a450f 📦 Chore: fix some workflow bug 2025-12-08 18:12:55 +08:00
PiEggandGitHub 070ce2b666 📦 Chore: upgrade electron version && migrate to electron-vite (#1361) 2025-12-08 17:42:35 +08:00
PiEgg b421c4b42a 🐛 Fix: pic-migrater post handler error 2025-11-28 21:18:52 +08:00
PiEgg d6c0a85a0f 🐛 Fix: fix docs link 2025-11-27 11:16:23 +08:00
PiEgg ca805f36f8 🎉 Release: v2.4.0 2025-11-23 08:30:58 +08:00
PiEgg cccd2954db 📝 Docs: add 2.4.0 changelog 2025-11-18 16:55:04 +08:00
PiEgg cfb6146de5 📦 Chore: update picgo core to 1.5.11 to solve url encode bug 2025-11-14 14:51:50 +08:00
PiEgg 45b3227456 📝 Docs: update readme & warp sponsor link 2025-11-03 17:29:14 +08:00
PiEgg 2450a524ff 📦 Chore: change funding yml 2025-10-30 15:49:08 +08:00
PiEgg 4a29bf2b50 📝 Docs: update docs 2025-10-28 14:45:59 +08:00
PiEgg 24e4a829d8 📝 Docs: update docs 2025-10-28 14:43:21 +08:00
PiEgg e0d45fa7a2 📝 Docs: add warp sponsor shoutout 2025-10-27 16:38:49 +08:00
PiEgg de441a892e 📝 Docs: add warp sponsor shoutout 2025-10-27 16:33:39 +08:00
PiEgg f1817eb1e1 🎉 Release: v2.4.0-beta.10 2025-06-08 10:38:22 +08:00
PiEgg 2bd5d0653b 🐛 Fix: encoded url filename unreadable 2025-06-08 10:37:31 +08:00
PiEgg 9841418779 Feature: finish form uploader for picgo server 2025-06-07 17:09:46 +08:00
PiEgg 7bc70628f9 🔨 Refactor: refactor the form uploader 2025-06-02 17:16:52 +08:00
happy-gameandGitHub a928b4c8ab Feature(server): add support for form upload in PicGo Server (#1327)
ISSUES CLOSED: #428
2025-03-17 09:01:15 +08:00
happy-gameandGitHub 7df37a2526 🐛 Fix: unable to use clip in wayland (#1301) 2024-12-11 17:29:09 +08:00
happy-gameandGitHub dacb926f17 🐛 Fix: auto-copy url can't be turned off (#1300) 2024-12-06 11:35:59 +08:00
PiEgg 1ea074e75f 🎉 Release: v2.4.0-beta.9 2024-12-02 20:02:06 +08:00
PiEgg 04140def7c 🐛 Fix: plugin list search bug
ISSUES CLOSED: #1297
2024-12-02 20:01:15 +08:00
PiEgg c9fe4023f0 🐛 Fix: clipboard filename missing second
ISSUES CLOSED: #1293
2024-11-25 14:51:41 +08:00
PiEgg c8ba547edb 🐛 Fix: copy text bug
ISSUES CLOSED: #1210, #1280
2024-10-05 08:34:04 +08:00
PiEgg 316928edc3 📦 Chore: update ci macos version 2024-07-16 23:03:20 +08:00
PiEgg ff35335126 🎉 Release: v2.4.0-beta.8 2024-07-16 22:58:25 +08:00
PiEgg ff7336b99e 🐛 Fix: tencent cos url encode bug
ISSUES CLOSED: #1265
2024-07-16 22:57:59 +08:00
PiEgg 917ec73027 🎉 Release: v2.4.0-beta.7 2024-04-22 09:53:45 +08:00
PiEgg 8e91582adc 🐛 Fix: config page scroll bug
ISSUES CLOSED: #1237
2024-04-19 09:39:44 +08:00
PiEgg aaec99f466 Feature: add startup mode
ISSUES CLOSED: #915
2024-02-28 15:59:20 +08:00
PiEgg 50e0a64519 🐛 Fix: tray menu open bug
ISSUES CLOSED: #1217
2024-02-25 09:55:41 +08:00
PiEgg 8cede85732 🎉 Release: v2.4.0-beta.6 2023-11-19 09:27:56 +08:00
PiEgg 050a3dd602 🐛 Fix: can't add new config for picbed
ISSUES CLOSED: #1184
2023-10-22 14:00:51 +08:00
木偶andGitHub cd07b33e6b 🐛 Fix: app.asar directroy copy error (#1180)
ISSUES CLOSED: #1179
2023-10-22 13:23:37 +08:00
PiEgg 6d4e1c27d5 🎉 Release: v2.4.0-beta.5 2023-09-10 17:41:19 +08:00
PiEgg 8fc65f4dcc ⬆️ Upgrade: picgo version 2023-09-10 17:40:28 +08:00
PiEggandGitHub 0f7b07dd7b Feature: add gallery toolbox menu (#1177) 2023-09-10 17:35:43 +08:00
PiEgg dca6667a81 🎉 Release: v2.4.0-beta.4 2023-08-26 12:37:03 +08:00
PiEgg 1b92f20a7d Feature: support "tips" option for uploader 2023-08-26 12:33:42 +08:00
barnettyangandGitHub a2320c3503 Feature(tcyun): add slim section (#1165) 2023-08-23 16:46:40 +08:00
PiEgg 2db0fea6ee 🐛 Fix: open config file bug
ISSUES CLOSED: #1163
2023-08-15 20:22:50 +08:00
PiEgg 894d0a243e Feature: add configName for upload page 2023-07-23 11:03:07 +08:00
PiEgg 536b70832e 🎉 Release: v2.4.0-beta.3 2023-07-09 14:57:11 +08:00
PiEgg bc2e92836c 🐛 Fix: rename page bug
ISSUES CLOSED: #1130
2023-07-09 14:19:01 +08:00
浮心物语andGitHub a5ed1d107e 📝 Docs: update scoop install (#1109) 2023-07-09 13:07:47 +08:00
PiEgg e3566b5a8e 🐛 Fix: tailwind css bug 2023-07-09 13:02:16 +08:00
PiEgg 64eb832e06 🎉 Release: v2.4.0-beta.2 2023-07-09 10:13:22 +08:00
PiEgg 10e0584c96 🔨 Refactor: setting page 2023-07-09 10:12:49 +08:00
PiEgg 8d9a4000f3 🐛 Fix: fileName encode bug
ISSUES CLOSED: #1121
2023-05-07 13:48:45 +08:00
PiEgg 80c05a173e 🎉 Release: v2.4.0-beta.1 2023-05-03 22:14:03 +08:00
PiEgg 46f54e12b7 Feature: add showDockIcon option
ISSUES CLOSED: #1045
2023-05-03 22:13:48 +08:00
PiEgg 32eb17660d Feature: add showDockIcon option
ISSUES CLOSED: #1045
2023-05-03 21:53:07 +08:00
PiEgg dfbc96f1c0 Feature: add picgo toolbox for auto detect & fix problems 2023-05-03 16:41:28 +08:00
PiEgg d8b89da1ad 🚧 WIP: toolbox 2023-04-17 19:53:11 +08:00
PiEgg 063962d1ae 🐛 Fix: custom url template encode bug
ISSUES CLOSED: #1112
2023-04-17 19:52:38 +08:00
PiEgg 7363be798c 🐛 Fix: console.log EPIPE error
ISSUES CLOSED: #1101
2023-04-15 11:35:43 +08:00
PiEgg b7d2edbe23 🐛 Fix: isDarkMode() error when dragging file to tray icon
ISSUES CLOSED: #1107
2023-04-12 19:02:53 +08:00
PiEgg f75514d05d Feature: add settings.encodeOutputURL options
ISSUES CLOSED: #731
2023-04-09 17:49:08 +08:00
PiEgg 34657ae1fa 🐛 Fix: fix copy link encoding bug
ISSUES CLOSED: #731
2023-03-25 11:54:25 +08:00
PiEgg 455cb4937e 🐛 Fix: typescript nightly build bug
ISSUES CLOSED: #1082
2023-03-08 10:17:05 +08:00
PiEgg 4f392f3628 📦 Chore: change version files' upload dest & dist files' upload dest 2023-02-22 19:06:49 +08:00
PiEgg 6801334216 📝 Docs: update FAQ
ISSUES CLOSED: #1067
2023-02-04 11:59:07 +08:00
PiEgg 520d6d3fa8 Feature: support dragging any type of file to upload
ISSUES CLOSED: #1052
2023-01-11 21:26:36 +08:00
PiEgg 911e34e98d 🐛 Fix: i18n bug 2023-01-07 20:50:59 +08:00
PiEgg 66d8d714db 🔨 Refactor: upgrade vue2 -> vue3 2023-01-07 17:43:35 +08:00
PiEgg 624e5738d1 🎉 Release: v2.4.0-beta.0 2023-01-05 09:05:17 +08:00
PiEgg c011654a57 Feature: add file-name in tray-page
ISSUES CLOSED: #1054
2023-01-02 14:27:44 +08:00
PiEgg fc897cea04 🐛 Fix: handleUploaderConfig should be placed in main/utils 2023-01-02 14:16:52 +08:00
PiEgg cbafe66a75 🔨 Refactor: rebuild multi-config communication way 2023-01-02 14:13:27 +08:00
PiEgg 5969daedac 🚧 WIP: add uploader config list menu & rebuild multi-config communication way 2022-12-31 20:44:19 +08:00
PiEgg f0787d3ec2 Feature: add file-name in gallery & add unknown-file-type placholder image
ISSUES CLOSED: #1050
2022-12-31 11:29:36 +08:00
陌小路andGitHub 95556498f7 Feature: support multiple config (#1016) 2022-11-23 09:42:40 +08:00
PiEgg 44f5fbbb91 🐛 Fix: nsis script
ISSUES CLOSED: #1019
2022-11-19 21:48:16 +08:00
ChowRexandGitHub 05998bba0d 📝 Docs: update FAQ.md (#1011) 2022-11-15 14:29:07 +08:00
PiEgg b483092c68 🎉 Release: v2.3.1 2022-11-13 17:04:43 +08:00
PiEgg bf75abc2f1 ⬆️ Upgrade(picgo-core): picgo-core to v1.5.0 2022-11-13 17:03:45 +08:00
PiEgg 167e424568 🐛 Fix: upyun options is not required
ISSUES CLOSED: #1002
2022-11-11 17:56:16 +08:00
PiEgg 64e54d0b0b Feature: add $extName for paste template
ISSUES CLOSED: #1000
2022-10-31 19:02:37 +08:00
PiEgg 33bfaa5959 🎉 Release: v2.3.1-beta.8 2022-10-30 16:11:43 +08:00
PiEgg 9317871fdb Feature: add remoteNotice 2022-10-30 16:08:29 +08:00
PiEgg a355bc07f4 📝 Docs: add PicHoro 2022-10-25 09:48:54 +08:00
PiEgg ecd462fbd4 Feature: macOS tray icon more clearer 2022-10-24 17:16:09 +08:00
PiEgg 4de7a1d5f2 🐛 Fix: url encode bug && copy-paste url encode bug
ISSUES CLOSED: #996
2022-10-24 17:14:39 +08:00
PiEgg 404d7667e0 🎉 Release: v2.3.1-beta.7 2022-10-23 15:48:12 +08:00
PiEgg 012a01d5d9 🐛 Fix: some bugs will case picgo-gui-local.log too large
ISSUES CLOSED: #995
2022-10-23 15:47:11 +08:00
PiEgg 9791ff24d8 🐛 Fix: macOS tray icon background color bug
#970
2022-10-21 10:46:54 +08:00
PiEgg 7677f1ea2a 🐛 Fix: change decodeURI -> decodeURIComponent & encode also 2022-10-19 17:40:04 +08:00
PiEgg 3c01861122 🐛 Fix: some case will cause picgo-gui-local.log too large 2022-10-17 17:53:15 +08:00
PiEgg ae87a512a1 ⬆️ Upgrade: picgo to 1.5.0-alpha.10 to support zh-TW 2022-09-25 11:23:05 +08:00
NeiLandGitHub 531bc13cca 🐛 Fix: some texts in zh-TW (#978) 2022-09-22 09:30:51 +08:00
NeiLandGitHub 72371de40b Feature: add zh-TW.yml (#976) 2022-09-21 09:04:26 +08:00
PiEgg d438f829e5 📝 Docs: update readme & FAQ 2022-09-04 15:31:16 +08:00
PiEgg 0ed7755508 🎉 Release: v2.3.1-beta.6 2022-09-04 11:20:39 +08:00
PiEgg 8cfbc696a4 ⬆️ Upgrade: picgo-1.5.0-alpha.9 2022-09-03 20:52:09 +08:00
PiEgg 389bd7096d ⬆️ Upgrade: upgrade picgo version
ISSUES CLOSED: #963, #676
2022-08-27 10:18:15 +08:00
PiEgg e8489182d7 Feature: cli support uploading image with url 2022-08-26 11:04:44 +08:00
PiEgg 7b50ba7ddb 🐛 Fix: windows upload clipboard file with builtin-clipboard not work 2022-08-25 22:46:21 +08:00
PiEgg 20e38f4eb9 🐛 Fix: macos clipboard image can't show on tray page
ISSUES CLOSED: #961
2022-08-25 20:04:50 +08:00
PiEgg b6b2eeae6b 🐛 Fix: showFileExplorer result bug 2022-08-24 10:00:09 +08:00
PiEgg 5f1fb08368 📦 Chore: up issue template 2022-08-21 11:01:04 +08:00
PiEgg d50ad3a592 🔨 Refactor: use picgo-core's picbed list without using builtin picbed list 2022-08-21 10:54:30 +08:00
PiEgg 428ffc7ef1 Feature: finish i18n system 2022-08-20 16:44:55 +08:00
PiEgg 86012c0c7b 📦 Chore: update cos upload url 2022-08-14 23:21:53 +08:00
PiEgg 09ee0947cd 🎉 Release: v2.3.1-beta.5 2022-08-14 23:04:01 +08:00
PiEgg 351fbdaa26 📦 Chore: update manually action 2022-08-14 23:01:50 +08:00
PiEgg c926414839 Feature: add dist upload to cos & update checkupdate logic 2022-08-14 20:46:22 +08:00
PiEgg dc85123bde ⬆️ Upgrade: update electron-builder version 2022-08-14 08:38:07 +08:00
PiEgg 83ab3c6db7 Feature: tray-window add open-setting-window button 2022-08-12 17:06:29 +08:00
PiEgg 87161b3ee0 📦 Chore: update macOS tray icon 2022-08-08 15:03:51 +08:00
PiEgg 4ebdc721e4 Feature: update tray icon in macOS 11 or 12
ISSUES CLOSED: #776
2022-08-07 22:34:26 +08:00
PiEgg dee40a6bcd 🚧 WIP: add new macos tray icon 2022-08-01 21:28:39 +08:00
PiEgg 09e4e82ca9 🐛 Fix: plugin config can't save
ISSUES CLOSED: #943
2022-07-31 18:24:13 +08:00
PiEgg 219b367f7c Feature: add logFileSizeLimit for log file
ISSUES CLOSED: #935, #945
2022-07-31 17:47:15 +08:00
PiEgg 3102d7b1ea 📝 Docs: update cos links 2022-07-17 11:52:14 +08:00
fb69bad2cf Feature: add server response headers for CORS (#939)
Co-authored-by: long <iamlongalong@gmail.com>
2022-07-16 10:36:00 +08:00
PiEgg 0a9e1692ee 📦 Chore: fix version error 2022-06-12 20:36:57 +08:00
PiEgg 1db84a2e14 🐛 Fix: build error 2022-06-12 20:25:03 +08:00
PiEgg e6b9d8853e 📦 Chore: fix github action build scripts 2022-06-12 20:23:02 +08:00
PiEgg c3b0f5b759 🎉 Release: v2.3.1-beta.4 2022-06-12 20:20:46 +08:00
PiEgg d3bb5caa83 🐛 Fix(db): fix some db bugs
#873,#806
2022-06-12 20:20:08 +08:00
PiEgg 8d861be161 🐛 Fix(gallery): can't copy gallery pics link
ISSUES CLOSED: #901
2022-06-12 12:30:28 +08:00
PiEgg a79efbfcc3 📝 Docs: update FAQ 2022-04-05 19:18:22 +08:00
PiEgg 746635ec3f 📝 Docs: update readme & FAQ 2022-04-05 11:22:50 +08:00
PiEgg db6c5b8751 📦 Chore: add issue template 2022-04-05 11:00:00 +08:00
PiEgg 5d0646995e 📝 Docs: fix electron_mirror link error
ISSUES CLOSED: #849
2022-04-04 19:55:02 +08:00
PiEgg b3c2ed9e13 🎉 Release: v2.3.1-beta.3 2022-04-04 19:24:17 +08:00
PiEgg af291e4f11 Feature: add tencent-cos options for url
ISSUES CLOSED: #862, #863, #865, #524, #845, #732
2022-04-04 19:22:30 +08:00
zhullybandPiEgg f1c8507927 Feature: add wayland support for linux 2022-03-14 23:57:37 +08:00
PiEgg 6bcda9ba28 📝 Docs: update readme
ISSUES CLOSED: #849, #850
2022-03-10 22:13:24 +08:00
PiEgg 1936ccfedc Feature: add i18n for en 2022-02-20 21:33:44 +08:00
PiEgg 7f6d58f992 🔨 Refactor: setting-page ui 2022-02-20 17:37:40 +08:00
PiEgg 27628da458 Feature: add upload-clipboard-image from electron' clipboard
ISSUES CLOSED: #822
2022-02-20 12:35:27 +08:00
PiEgg 6bcd019b31 🐛 Fix: fix mini-page can't upload image from dragging browser image
ISSUES CLOSED: #822
2022-02-20 12:33:51 +08:00
Wave-SYJandPiEgg c79a2869c1 🐛 Fix: mini window not always on top after reopen 2022-01-20 21:01:36 +08:00
PiEgg 1212774b3d 🚧 WIP: renderer process i18n 2022-01-19 23:21:17 +08:00
PiEgg be32ed0e52 🚧 WIP: renderer i18n 2022-01-18 23:10:49 +08:00
PiEgg cc73fe7bce 🚧 WIP: renderer process i18n 2022-01-18 16:06:33 +08:00
PiEgg 3a503151a4 🐛 Fix: notification freeze the main-process after uploading with clipboard
ISSUES CLOSED: #824
2022-01-16 20:22:23 +08:00
PiEgg 58f96db967 🚧 WIP: renderer i18n 2022-01-13 23:03:41 +08:00
PiEgg 6b6ae271e8 🐛 Fix: picgo.log path error
ISSUES CLOSED: #819
2022-01-12 14:57:09 +08:00
PiEgg 9d236ba6f4 🚧 WIP: renderer process i18n 2022-01-11 23:57:35 +08:00
PiEgg c19f741243 🚧 WIP: main process i18n 2022-01-11 21:50:29 +08:00
PiEgg c2991dde2f 🚧 WIP: add i18n ing 2022-01-10 10:20:56 +08:00
PiEgg 04701d4202 Feature: click cancel in rename-window will use origin filename now
ISSUES CLOSED: #791
2022-01-09 11:21:04 +08:00
PiEgg bcaf255a9c 📦 Chore: update fix-path
ISSUES CLOSED: #774
2022-01-09 10:11:14 +08:00
PiEgg 43d2a8e5c8 📦 Chore: types change 2022-01-08 15:44:25 +08:00
PiEgg f3ec0cb4be 🎉 Release: v2.3.1-beta.2 2022-01-06 10:42:08 +08:00
PiEgg 5dd6e72b50 🐛 Fix: electron builder actions bug 2022-01-06 00:01:26 +08:00
PiEgg 5cb8151df7 🐛 Fix: linux github actions workflow bug 2022-01-05 23:18:41 +08:00
PiEgg 1848bff091 🎉 Release: v2.3.1-beta.1 2022-01-05 23:13:59 +08:00
PiEgg 56e814a96a 📦 Chore: update ci build scripts 2022-01-05 23:13:24 +08:00
202 changed files with 28241 additions and 12414 deletions
-4
View File
@@ -1,4 +0,0 @@
test/unit/coverage/**
test/unit/*.js
test/e2e/*.js
dist/
-37
View File
@@ -1,37 +0,0 @@
module.exports = {
root: true,
globals: {
__static: 'readonly'
},
env: {
node: true
},
parser: 'vue-eslint-parser',
extends: [
'plugin:vue/essential',
'@vue/standard',
'@vue/typescript'
],
plugins: ['@typescript-eslint'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
indent: 'off',
'no-async-promise-executor': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/indent': ['error', 2]
},
parserOptions: {
parser: '@typescript-eslint/parser'
},
overrides: [
{
files: ['*.ts', '*.vue'],
rules: {
'no-undef': 'off' // https://typescript-eslint.io/docs/linting/troubleshooting/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
}
}
],
ignorePatterns: ['src/**/*.d.ts']
}
+1 -1
View File
@@ -1 +1 @@
custom: ["https://paypal.me/Molunerfinn"]
github: ["Molunerfinn"]
-34
View File
@@ -1,34 +0,0 @@
<!--
PicGo Issue 模板
请依照该模板来提交,否则将会被关闭。
**提问之前请注意你看过 FAQ、配置手册以及那些被关闭的 issues。否则同样的提问也会被关闭!**
-->
**声明:我已经仔细看过 [文档](https://picgo.github.io/PicGo-Doc/)、[FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md)、和搜索过已经关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed) 后依然没有找到答案,所以才发了一个新的 issue。**
## 问题类型
<!-- 你要提交的是 Bug Report 还是 Feature Request-->
## PicGo 的相关信息
<!-- PicGo 的版本、所在平台(Mac 或者 Windows 或者 Linux -->
## 问题重现
<!-- 如果是 Bug Report 请填写本项 -->
<!-- 请附上相关截图 -->
<!-- 请附上 PicGo 的相关报错日志(用文本的形式)否则无法判断原因-->
<!-- 报错日志可以在 PicGo 设置 -> 设置日志文件 -> 点击打开 后找到 -->
## 功能请求
<!-- 如果是 Feature Request 请填写本项 -->
<!-- 详细描述你所预想的功能或者是现有功能的改进。 -->
---
<!--
最后,喜欢 PicGo 的话不妨给它点个 star~
如果可以的话,请我喝杯咖啡?首页有赞助二维码,谢谢你的支持!
-->
+68
View File
@@ -0,0 +1,68 @@
name: "🐛 Bug Report"
description: 提交一个问题 / Report a bug
title: "[Bug]: "
labels: ["bug"]
assignees:
- molunerfinn
body:
- type: markdown
attributes:
value: |+
## PicGo Issue 模板
请依照该模板来提交,否则将会被关闭。
**提问之前请注意你看过 FAQ、文档以及那些被关闭的 issues。否则同样的提问也会被关闭!**
Please submit according to this template, otherwise it will be closed.
**Before asking a question, please note that you have read the FAQ, Doc, and those closed issues. Otherwise the same question will also be closed! **
- type: checkboxes
id: read
attributes:
label: 前置阅读 | Pre-reading
description: 我已经自行查找、阅读以下内容(阅读了请打勾) | I have searched and read the following on my own (Please tick after reading)
options:
- label: "[文档/Doc](https://picgo.github.io/PicGo-Doc/)"
required: true
- label: "[Issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed)"
required: true
- label: "[FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md)"
required: true
- type: input
id: version
attributes:
label: PicGo的版本 | PicGo Version
placeholder: 例如 v2.3.0-beta.1
validations:
required: true
- type: dropdown
id: platform
attributes:
label: 系统信息 | System Information
options:
- Windows
- Mac
- Mac(arm64)
- Linux
- All
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: 问题重现 | Bug reproduce
description: 请复述Bug重现流程,必要时附图 | Please tell me the bug reproduction process, attach pictures if necessary
validations:
required: true
- type: textarea
id: log
attributes:
label: 相关日志 | Logs
description: 请附上 PicGo 的相关报错日志(用文本的形式)。报错日志可以在 PicGo 设置 -> 设置日志文件 -> 点击打开 后找到 | Please attach PicGo's relevant error log (in text form). The error log can be found in PicGo Settings -> Set Log File -> Click to Open
- type: markdown
attributes:
value: |
最后,喜欢 PicGo 的话不妨给它点个 star~
如果可以的话,请我喝杯咖啡?首页有赞助二维码,谢谢你的支持!
Finally, if you like PicGo, give it a star~
Buy me a cup of coffee if you can? There is a sponsorship QR code on the homepage, thank you for your support!
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
@@ -0,0 +1,63 @@
name: "✨ Feature Request"
description: 功能请求 / Feature request
title: "[Feature]: "
labels: ["feature request"]
assignees:
- molunerfinn
body:
- type: markdown
attributes:
value: |+
## PicGo Issue 模板
请依照该模板来提交,否则将会被关闭。
**提问之前请注意你看过 FAQ、文档以及那些被关闭的 issues。否则同样的提问也会被关闭!**
Please submit according to this template, otherwise it will be closed.
**Before asking a question, please note that you have read the FAQ, Doc, and those closed issues. Otherwise the same question will also be closed! **
- type: checkboxes
id: read
attributes:
label: 前置阅读 | Pre-reading
description: 我已经自行查找、阅读以下内容(阅读了请打勾) | I have searched and read the following on my own (Please tick after reading)
options:
- label: "[文档/Doc](https://picgo.github.io/PicGo-Doc/)"
required: true
- label: "[Issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed)"
required: true
- label: "[FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md)"
required: true
- type: input
id: version
attributes:
label: PicGo的版本 | PicGo Version
placeholder: 例如 v2.3.0-beta.1
validations:
required: true
- type: dropdown
id: platform
attributes:
label: 系统信息 | System Information
options:
- Windows
- Mac
- Mac(arm64)
- Linux
- All
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: 功能请求 | Feature request
description: 详细描述你所预想的功能或者是现有功能的改进 | Describe in detail the features you envision or improvements to existing features
validations:
required: true
- type: markdown
attributes:
value: |
最后,喜欢 PicGo 的话不妨给它点个 star~
如果可以的话,请我喝杯咖啡?首页有赞助二维码,谢谢你的支持!
Finally, if you like PicGo, give it a star~
Buy me a cup of coffee if you can? There is a sponsorship QR code on the homepage, thank you for your support!
+119 -39
View File
@@ -1,56 +1,136 @@
# main.yml
name: Build & Release
# Workflow's name
name: Build
# Workflow's trigger
on:
push:
branches:
- master
tags:
- v*
workflow_dispatch:
inputs:
test_upload_dist:
description: "Test upload-dist.js script"
required: true
default: false
type: boolean
env:
NODE_VERSION: 22.x
# Workflow's jobs
jobs:
# job's id
release:
# job's name
name: build and release electron app
# the type of machine to run the job on
# parallel build jobs
build:
name: Build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
# create a build matrix for jobs
strategy:
# if one job fails, do not stop other jobs
fail-fast: false
matrix:
os: [ubuntu-latest, macos-12]
# create steps
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
# step1: check out repository
- name: Check out git repository
uses: actions/checkout@v2
# step2: install node env
- name: Install Node.js
uses: actions/setup-node@v2
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
- name: Install system deps
if: matrix.os == 'ubuntu-latest'
node-version: ${{ env.NODE_VERSION }}
- name: Clean workspace on Windows
if: runner.os == 'Windows'
run: |
sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils
if (Test-Path dist) { Remove-Item -Recurse -Force dist }
if (Test-Path dist_electron) { Remove-Item -Recurse -Force dist_electron }
if (Test-Path node_modules) { Remove-Item -Recurse -Force node_modules }
# step3: yarn
- name: Yarn install
if (Test-Path "$env:LOCALAPPDATA\electron-builder") {
Remove-Item "$env:LOCALAPPDATA\electron-builder" -Recurse -Force -ErrorAction SilentlyContinue
}
if (Test-Path "$env:LOCALAPPDATA\electron") {
Remove-Item "$env:LOCALAPPDATA\electron" -Recurse -Force -ErrorAction SilentlyContinue
}
- name: Clean workspace on macOS & Linux
if: runner.os == 'macOS' || runner.os == 'Linux'
run: |
yarn
yarn global add xvfb-maybe
- name: Build & release app
run: |
yarn build --arm64
yarn release
rm -rf dist dist_electron node_modules ~/.cache/electron-builder ~/.cache/electron
- name: Install dependencies
run: yarn install
- name: Ubuntu Update with sudo
if: runner.os == 'Linux'
run: sudo apt-get update
- name: Build Windows x64 & ARM64 App
if: runner.os == 'Windows'
run: yarn build:win || true
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Build macOS x64 & ARM64 App
if: runner.os == 'macOS'
run: yarn build:mac || true
shell: bash
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Build Linux x64 & ARM64 App
if: runner.os == 'Linux'
run: yarn build:linux || true
shell: bash
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: PicGo-${{ runner.os }}
path: dist/*.*
- name: Upload to release.picgo.app
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.test_upload_dist
run: |
yarn upload-dist
env:
PICGO_ENV_S3_SECRET_ID: ${{ secrets.PICGO_ENV_S3_SECRET_ID }}
PICGO_ENV_S3_SECRET_KEY: ${{ secrets.PICGO_ENV_S3_SECRET_KEY }}
PICGO_ENV_S3_ACCOUNT_ID: ${{ secrets.PICGO_ENV_S3_ACCOUNT_ID }}
release:
name: Publish GitHub Release
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Publish GitHub Dev Release
if: github.event_name == 'workflow_dispatch'
uses: softprops/action-gh-release@v2
continue-on-error: true
with:
token: ${{ secrets.GH_TOKEN }}
tag_name: dev
draft: true
prerelease: false
files: |
!artifacts/**/*-unpacked/**
artifacts/**/*.exe
artifacts/**/*.dmg
artifacts/**/*.zip
artifacts/**/*.AppImage
artifacts/**/*.deb
artifacts/**/*.snap
artifacts/**/*.tar.gz
artifacts/**/*.yml
artifacts/**/*.blockmap
- name: Publish GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
continue-on-error: true
with:
token: ${{ secrets.GH_TOKEN }}
generate_release_notes: true
draft: true
prerelease: false
files: |
!artifacts/**/*-unpacked/**
artifacts/**/*.exe
artifacts/**/*.dmg
artifacts/**/*.zip
artifacts/**/*.AppImage
artifacts/**/*.deb
artifacts/**/*.snap
artifacts/**/*.tar.gz
artifacts/**/*.yml
artifacts/**/*.blockmap
+7
View File
@@ -17,3 +17,10 @@ docs/dist/
.env.*.local
dist_electron/
test.js
.env
scripts/*.yml
#Electron-builder output
/dist_electron
.serena/
dist/*
+1
View File
@@ -0,0 +1 @@
22
+1
View File
@@ -0,0 +1 @@
shamefully-hoist=true
+30 -24
View File
@@ -1,38 +1,44 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"name": "Electron: Main",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/src/main/index.dev.js",
"env": {
"DEBUG_ENV": "debug"
},
"stopOnEntry": false,
"args": [],
"cwd": "${workspaceRoot}",
// this points to the electron task runner
"protocol": "inspector",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"runtimeArgs": [
"--nolazy"
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"preLaunchTask": "electron-debug",
"args": [
"--remote-debugging-port=9223",
"./dist"
],
"sourceMaps": true
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
]
},
{
"name": "Attach",
"type": "node",
"name": "Electron: Renderer",
"type": "chrome",
"request": "attach",
"port": 5858,
"sourceMaps": true,
"restart": true,
"outFiles": [],
"localRoot": "${workspaceRoot}",
"protocol": "inspector",
"remoteRoot": null
"port": 9223,
"urlFilter": "http://localhost:*",
"timeout": 30000,
"webRoot": "${workspaceFolder}/src",
"sourceMapPathOverrides": {
"webpack:///./src/*": "${webRoot}/*"
}
}
],
"compounds": [
{
"name": "Electron: All",
"configurations": [
"Electron: Main",
"Electron: Renderer"
]
}
]
}
+8 -2
View File
@@ -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"
},
}
+27
View File
@@ -0,0 +1,27 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "electron-debug",
"type": "process",
"command": "./node_modules/.bin/vue-cli-service",
"windows": {
"command": "./node_modules/.bin/vue-cli-service.cmd"
},
"isBackground": true,
"args": ["electron:serve", "--debug"],
"problemMatcher": {
"owner": "custom",
"pattern": {
"regexp": ""
},
"background": {
"beginsPattern": "Starting development server\\.\\.\\.",
"endsPattern": "Not launching electron as debug argument was passed\\."
}
}
}
]
}
+28
View File
@@ -0,0 +1,28 @@
# 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
- `pnpm install` — install dependencies; `npm install` is unsupported.
- Always add/remove dependencies with `pnpm` (never edit package.json versions by hand then install).
- `pnpm dev` — electron-vite dev server for main/preload/renderer.
- `pnpm build` — electron-vite build outputs to `dist/main`, `dist/preload`, `dist/renderer`; `pnpm preview` for preview mode.
- Packaging config lives in `electron-builder.yml` (read by electron-builder via package.json `build` field/extraResources); set `ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/` if downloads are slow.
- `pnpm lint` / `pnpm lint:fix` — run or auto-fix ESLint (Standard, TypeScript, Vue rules).
- `pnpm lint:dpdm` — fail fast on circular dependencies in `src/`.
- `pnpm 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. Static assets are served from `public/` and resolved via `getStaticPath`/`getStaticFileUrl` (`src/universal/utils/staticPath.ts`); avoid using `__static` directly.
Static assets are served from `public/`. In the main process use `getStaticPath`/`getStaticFileUrl` (`src/universal/utils/staticPath.ts`). In the renderer, place assets under `public/` and resolve them via `import.meta.env.BASE_URL + filename` (helper: `src/renderer/utils/static.ts`); do not rely on `__static` in renderer code.
- Do not use `as any` under any circumstances; keep typings explicit and safe.
## 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 (`pnpm 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 (`pnpm lint:dpdm` + Commitlint). Stage your changes and run `pnpm 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 `pnpm gen-i18n` so the generated typings stay in sync.
+405 -3
View File
@@ -1,3 +1,408 @@
## :tada: 2.4.1-beta.1 (2025-12-10)
### :bug: Bug Fixes
* **custom:** the issue that x64 macOS app can't be opened ([54d15a6](https://github.com/Molunerfinn/PicGo/commit/54d15a6)), closes [#1363](https://github.com/Molunerfinn/PicGo/issues/1363)
### :package: Chore
* update builder config && add legacy version file upload process ([2cc2983](https://github.com/Molunerfinn/PicGo/commit/2cc2983))
## :tada: 2.4.1-beta.0 (2025-12-09)
### :bug: Bug Fixes
* fix docs link ([d6c0a85](https://github.com/Molunerfinn/PicGo/commit/d6c0a85))
* pic-migrater post handler error ([b421c4b](https://github.com/Molunerfinn/PicGo/commit/b421c4b))
* tray window clipboard image not show bug ([#1362](https://github.com/Molunerfinn/PicGo/issues/1362)) ([56f61d4](https://github.com/Molunerfinn/PicGo/commit/56f61d4))
### :package: Chore
* change release url ([9d4ea82](https://github.com/Molunerfinn/PicGo/commit/9d4ea82))
* disabled build universal installer ([366ac11](https://github.com/Molunerfinn/PicGo/commit/366ac11))
* fix some workflow bug ([db627a4](https://github.com/Molunerfinn/PicGo/commit/db627a4))
* fix upload dist arch bug ([8e94b2a](https://github.com/Molunerfinn/PicGo/commit/8e94b2a))
* upgrade electron version && migrate to electron-vite ([#1361](https://github.com/Molunerfinn/PicGo/issues/1361)) ([070ce2b](https://github.com/Molunerfinn/PicGo/commit/070ce2b))
# :tada: 2.4.0 (2025-11-23)
### :pencil: Documentation
* add 2.4.0 changelog ([cccd295](https://github.com/Molunerfinn/PicGo/commit/cccd295))
* add warp sponsor shoutout ([e0d45fa](https://github.com/Molunerfinn/PicGo/commit/e0d45fa))
* add warp sponsor shoutout ([de441a8](https://github.com/Molunerfinn/PicGo/commit/de441a8))
* update docs ([4a29bf2](https://github.com/Molunerfinn/PicGo/commit/4a29bf2))
* update docs ([24e4a82](https://github.com/Molunerfinn/PicGo/commit/24e4a82))
* update readme & warp sponsor link ([45b3227](https://github.com/Molunerfinn/PicGo/commit/45b3227))
### :package: Chore
* change funding yml ([2450a52](https://github.com/Molunerfinn/PicGo/commit/2450a52))
* update picgo core to 1.5.11 to solve url encode bug ([cfb6146](https://github.com/Molunerfinn/PicGo/commit/cfb6146))
# :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)
### :sparkles: Features
* add picgo toolbox for auto detect & fix problems ([dfbc96f](https://github.com/Molunerfinn/PicGo/commit/dfbc96f))
* add settings.encodeOutputURL options ([f75514d](https://github.com/Molunerfinn/PicGo/commit/f75514d)), closes [#731](https://github.com/Molunerfinn/PicGo/issues/731)
* add showDockIcon option ([46f54e1](https://github.com/Molunerfinn/PicGo/commit/46f54e1)), closes [#1045](https://github.com/Molunerfinn/PicGo/issues/1045)
* add showDockIcon option ([32eb176](https://github.com/Molunerfinn/PicGo/commit/32eb176)), closes [#1045](https://github.com/Molunerfinn/PicGo/issues/1045)
* support dragging any type of file to upload ([520d6d3](https://github.com/Molunerfinn/PicGo/commit/520d6d3)), closes [#1052](https://github.com/Molunerfinn/PicGo/issues/1052)
### :bug: Bug Fixes
* console.log EPIPE error ([7363be7](https://github.com/Molunerfinn/PicGo/commit/7363be7)), closes [#1101](https://github.com/Molunerfinn/PicGo/issues/1101)
* custom url template encode bug ([063962d](https://github.com/Molunerfinn/PicGo/commit/063962d)), closes [#1112](https://github.com/Molunerfinn/PicGo/issues/1112)
* fix copy link encoding bug ([34657ae](https://github.com/Molunerfinn/PicGo/commit/34657ae)), closes [#731](https://github.com/Molunerfinn/PicGo/issues/731)
* i18n bug ([911e34e](https://github.com/Molunerfinn/PicGo/commit/911e34e))
* isDarkMode() error when dragging file to tray icon ([b7d2edb](https://github.com/Molunerfinn/PicGo/commit/b7d2edb)), closes [#1107](https://github.com/Molunerfinn/PicGo/issues/1107)
* typescript nightly build bug ([455cb49](https://github.com/Molunerfinn/PicGo/commit/455cb49)), closes [#1082](https://github.com/Molunerfinn/PicGo/issues/1082)
### :package: Chore
* change version files' upload dest & dist files' upload dest ([4f392f3](https://github.com/Molunerfinn/PicGo/commit/4f392f3))
### :pencil: Documentation
* update FAQ ([6801334](https://github.com/Molunerfinn/PicGo/commit/6801334)), closes [#1067](https://github.com/Molunerfinn/PicGo/issues/1067)
# :tada: 2.4.0-beta.0 (2023-01-05)
### :sparkles: Features
* add file-name in gallery & add unknown-file-type placholder image ([f0787d3](https://github.com/Molunerfinn/PicGo/commit/f0787d3)), closes [#1050](https://github.com/Molunerfinn/PicGo/issues/1050)
* add file-name in tray-page ([c011654](https://github.com/Molunerfinn/PicGo/commit/c011654)), closes [#1054](https://github.com/Molunerfinn/PicGo/issues/1054)
* support multiple config ([#1016](https://github.com/Molunerfinn/PicGo/issues/1016)) ([9555649](https://github.com/Molunerfinn/PicGo/commit/9555649))
### :bug: Bug Fixes
* handleUploaderConfig should be placed in main/utils ([fc897ce](https://github.com/Molunerfinn/PicGo/commit/fc897ce))
* nsis script ([44f5fbb](https://github.com/Molunerfinn/PicGo/commit/44f5fbb)), closes [#1019](https://github.com/Molunerfinn/PicGo/issues/1019)
### :pencil: Documentation
* update FAQ.md ([#1011](https://github.com/Molunerfinn/PicGo/issues/1011)) ([05998bb](https://github.com/Molunerfinn/PicGo/commit/05998bb))
## :tada: 2.3.1 (2022-11-13)
### :sparkles: Features
* add $extName for paste template ([64e54d0](https://github.com/Molunerfinn/PicGo/commit/64e54d0)), closes [#1000](https://github.com/Molunerfinn/PicGo/issues/1000)
### :bug: Bug Fixes
* upyun options is not required ([167e424](https://github.com/Molunerfinn/PicGo/commit/167e424)), closes [#1002](https://github.com/Molunerfinn/PicGo/issues/1002)
## :tada: 2.3.1-beta.8 (2022-10-30)
### :sparkles: Features
* add remoteNotice ([9317871](https://github.com/Molunerfinn/PicGo/commit/9317871))
* macOS tray icon more clearer ([ecd462f](https://github.com/Molunerfinn/PicGo/commit/ecd462f))
### :bug: Bug Fixes
* url encode bug && copy-paste url encode bug ([4de7a1d](https://github.com/Molunerfinn/PicGo/commit/4de7a1d)), closes [#996](https://github.com/Molunerfinn/PicGo/issues/996)
### :pencil: Documentation
* add PicHoro ([a355bc0](https://github.com/Molunerfinn/PicGo/commit/a355bc0))
## :tada: 2.3.1-beta.7 (2022-10-23)
### :sparkles: Features
* add zh-TW.yml ([#976](https://github.com/Molunerfinn/PicGo/issues/976)) ([72371de](https://github.com/Molunerfinn/PicGo/commit/72371de))
### :bug: Bug Fixes
* change decodeURI -> decodeURIComponent & encode also ([7677f1e](https://github.com/Molunerfinn/PicGo/commit/7677f1e))
* macOS tray icon background color bug ([9791ff2](https://github.com/Molunerfinn/PicGo/commit/9791ff2)), closes [#970](https://github.com/Molunerfinn/PicGo/issues/970)
* some bugs will case picgo-gui-local.log too large ([012a01d](https://github.com/Molunerfinn/PicGo/commit/012a01d)), closes [#995](https://github.com/Molunerfinn/PicGo/issues/995)
* some case will cause picgo-gui-local.log too large ([3c01861](https://github.com/Molunerfinn/PicGo/commit/3c01861))
* some texts in zh-TW ([#978](https://github.com/Molunerfinn/PicGo/issues/978)) ([531bc13](https://github.com/Molunerfinn/PicGo/commit/531bc13))
### :pencil: Documentation
* update readme & FAQ ([d438f82](https://github.com/Molunerfinn/PicGo/commit/d438f82))
## :tada: 2.3.1-beta.6 (2022-09-04)
### :sparkles: Features
* cli support uploading image with url ([e848918](https://github.com/Molunerfinn/PicGo/commit/e848918))
* finish i18n system ([428ffc7](https://github.com/Molunerfinn/PicGo/commit/428ffc7))
### :bug: Bug Fixes
* macos clipboard image can't show on tray page ([20e38f4](https://github.com/Molunerfinn/PicGo/commit/20e38f4)), closes [#961](https://github.com/Molunerfinn/PicGo/issues/961)
* showFileExplorer result bug ([b6b2eea](https://github.com/Molunerfinn/PicGo/commit/b6b2eea))
* windows upload clipboard file with builtin-clipboard not work ([7b50ba7](https://github.com/Molunerfinn/PicGo/commit/7b50ba7))
### :package: Chore
* up issue template ([5f1fb08](https://github.com/Molunerfinn/PicGo/commit/5f1fb08))
* update cos upload url ([86012c0](https://github.com/Molunerfinn/PicGo/commit/86012c0))
## :tada: 2.3.1-beta.5 (2022-08-14)
### :sparkles: Features
* add dist upload to cos & update checkupdate logic ([c926414](https://github.com/Molunerfinn/PicGo/commit/c926414))
* add logFileSizeLimit for log file ([219b367](https://github.com/Molunerfinn/PicGo/commit/219b367)), closes [#935](https://github.com/Molunerfinn/PicGo/issues/935) [#945](https://github.com/Molunerfinn/PicGo/issues/945)
* add server response headers for CORS ([#939](https://github.com/Molunerfinn/PicGo/issues/939)) ([fb69bad](https://github.com/Molunerfinn/PicGo/commit/fb69bad))
* tray-window add open-setting-window button ([83ab3c6](https://github.com/Molunerfinn/PicGo/commit/83ab3c6))
* update tray icon in macOS 11 or 12 ([4ebdc72](https://github.com/Molunerfinn/PicGo/commit/4ebdc72)), closes [#776](https://github.com/Molunerfinn/PicGo/issues/776)
### :bug: Bug Fixes
* build error ([1db84a2](https://github.com/Molunerfinn/PicGo/commit/1db84a2))
* plugin config can't save ([09e4e82](https://github.com/Molunerfinn/PicGo/commit/09e4e82)), closes [#943](https://github.com/Molunerfinn/PicGo/issues/943)
### :package: Chore
* fix github action build scripts ([e6b9d88](https://github.com/Molunerfinn/PicGo/commit/e6b9d88))
* fix version error ([0a9e169](https://github.com/Molunerfinn/PicGo/commit/0a9e169))
* update macOS tray icon ([87161b3](https://github.com/Molunerfinn/PicGo/commit/87161b3))
* update manually action ([351fbda](https://github.com/Molunerfinn/PicGo/commit/351fbda))
### :pencil: Documentation
* update cos links ([3102d7b](https://github.com/Molunerfinn/PicGo/commit/3102d7b))
## :tada: 2.3.1-beta.4 (2022-06-12)
### :bug: Bug Fixes
* **db:** fix some db bugs ([d3bb5ca](https://github.com/Molunerfinn/PicGo/commit/d3bb5ca)), closes [#873](https://github.com/Molunerfinn/PicGo/issues/873) [#806](https://github.com/Molunerfinn/PicGo/issues/806)
* **gallery:** can't copy gallery pics link ([8d861be](https://github.com/Molunerfinn/PicGo/commit/8d861be)), closes [#901](https://github.com/Molunerfinn/PicGo/issues/901)
### :pencil: Documentation
* fix electron_mirror link error ([5d06469](https://github.com/Molunerfinn/PicGo/commit/5d06469)), closes [#849](https://github.com/Molunerfinn/PicGo/issues/849)
* update FAQ ([a79efbf](https://github.com/Molunerfinn/PicGo/commit/a79efbf))
* update readme & FAQ ([746635e](https://github.com/Molunerfinn/PicGo/commit/746635e))
### :package: Chore
* add issue template ([db6c5b8](https://github.com/Molunerfinn/PicGo/commit/db6c5b8))
## :tada: 2.3.1-beta.3 (2022-04-04)
### :sparkles: Features
* add i18n for en ([1936ccf](https://github.com/Molunerfinn/PicGo/commit/1936ccf))
* add tencent-cos options for url ([af291e4](https://github.com/Molunerfinn/PicGo/commit/af291e4)), closes [#862](https://github.com/Molunerfinn/PicGo/issues/862) [#863](https://github.com/Molunerfinn/PicGo/issues/863) [#865](https://github.com/Molunerfinn/PicGo/issues/865) [#524](https://github.com/Molunerfinn/PicGo/issues/524) [#845](https://github.com/Molunerfinn/PicGo/issues/845) [#732](https://github.com/Molunerfinn/PicGo/issues/732)
* add upload-clipboard-image from electron' clipboard ([27628da](https://github.com/Molunerfinn/PicGo/commit/27628da)), closes [#822](https://github.com/Molunerfinn/PicGo/issues/822)
* add wayland support for linux ([f1c8507](https://github.com/Molunerfinn/PicGo/commit/f1c8507))
* click cancel in rename-window will use origin filename now ([04701d4](https://github.com/Molunerfinn/PicGo/commit/04701d4)), closes [#791](https://github.com/Molunerfinn/PicGo/issues/791)
### :bug: Bug Fixes
* fix mini-page can't upload image from dragging browser image ([6bcd019](https://github.com/Molunerfinn/PicGo/commit/6bcd019)), closes [#822](https://github.com/Molunerfinn/PicGo/issues/822)
* mini window not always on top after reopen ([c79a286](https://github.com/Molunerfinn/PicGo/commit/c79a286))
* notification freeze the main-process after uploading with clipboard ([3a50315](https://github.com/Molunerfinn/PicGo/commit/3a50315)), closes [#824](https://github.com/Molunerfinn/PicGo/issues/824)
* picgo.log path error ([6b6ae27](https://github.com/Molunerfinn/PicGo/commit/6b6ae27)), closes [#819](https://github.com/Molunerfinn/PicGo/issues/819)
### :pencil: Documentation
* update readme ([6bcda9b](https://github.com/Molunerfinn/PicGo/commit/6bcda9b)), closes [#849](https://github.com/Molunerfinn/PicGo/issues/849) [#850](https://github.com/Molunerfinn/PicGo/issues/850)
### :package: Chore
* types change ([43d2a8e](https://github.com/Molunerfinn/PicGo/commit/43d2a8e))
* update fix-path ([bcaf255](https://github.com/Molunerfinn/PicGo/commit/bcaf255)), closes [#774](https://github.com/Molunerfinn/PicGo/issues/774)
## :tada: 2.3.1-beta.2 (2022-01-06)
### :bug: Bug Fixes
* electron builder actions bug ([5dd6e72](https://github.com/Molunerfinn/PicGo/commit/5dd6e72))
* linux github actions workflow bug ([5cb8151](https://github.com/Molunerfinn/PicGo/commit/5cb8151))
## :tada: 2.3.1-beta.1 (2022-01-05)
### :package: Chore
* update ci build scripts ([56e814a](https://github.com/Molunerfinn/PicGo/commit/56e814a))
## :tada: 2.3.1-beta.0 (2022-01-05)
@@ -367,6 +772,3 @@
### :package: Chore
* add picgo bump-version ([37f1d34](https://github.com/Molunerfinn/PicGo/commit/37f1d34))
+37
View File
@@ -0,0 +1,37 @@
## 贡献指南
### 安装与启动
1. 使用 [yarn](https://yarnpkg.com/) 安装依赖
```bash
yarn install
```
然后通过
```bash
yarn dev
```
启动项目。
2. 只跟 Electron 主进程相关的代码请在 `src/main` 目录下添加。只跟渲染进程相关的代码请在 `src/renderer` 目录下添加。两个进程都能使用的代码请在 `src/universal` 目录下添加。 **注意**:渲染进程没有 `Node.js` 能力,所有渲染进程需要用到 `Node.js` 模块相关的代码请在 `src/main/events/picgoCoreIPC.ts` 下添加事件进行处理。
3. 所有的跨进程事件名请在 `src/universal/events/constants.ts` 里添加。
4. 所有的全局类型定义请在 `src/universal/types/` 里添加,如果是 `enum`,请在 `src/universal/types/enum.ts` 里添加。
### i18n
1.`public/i18n/` 下面创建一种语言的 `yml` 文件,例如 `zh-Hans.yml`。然后参考 `zh-CN.yml` 或者 `en.yml` 编写语言文件。并注意,PicGo 会通过语言文件中的 `LANG_DISPLAY_LABEL` 向用户展示该语言的名称。
2.`src/universal/i18n/index.ts` 里添加一种默认语言。其中 `label` 就是语言文件中 `LANG_DISPLAY_LABEL` 的值,`value` 是语言文件名。
3. 如果是对已有语言文件进行更新,请在更新完,务必运行一遍 `yarn gen-i18n`,确保能生成正确的语言定义文件。
### 提交代码
1. 请检查代码没有多余的注释、`console.log` 等调试代码。
2. 提交代码前,请执行命令 `git add . && yarn cz`,唤起 PicGo 的[代码提交规范工具](https://github.com/PicGo/bump-version)。通过该工具提交代码。
+37
View File
@@ -0,0 +1,37 @@
## Contribution Guidelines
### Installation and startup
1. Use [yarn](https://yarnpkg.com/) to install dependencies
```bash
yarn install
```
then pass
```bash
yarn dev
```
Startup project.
2. Please add code only related to the main process of Electron in the `src/main` directory. Code only related to the rendering process should be added in the `src/renderer` directory. Add code that can be used by both processes in the `src/universal` directory. **Note**: The rendering process does not have the `Node.js` capability. All rendering processes need to use `Node.js modules` related code, please add events under `src/main/events/picgoCoreIPC.ts` for processing.
3. Please add all cross-process event names in `src/universal/events/constants.ts`.
4. Please add all global type definitions in `src/universal/types/`, if it is `enum`, please add it in `src/universal/types/enum.ts`.
### i18n
1. Create a language `yml` file under `public/i18n/`, for example `zh-Hans.yml`. Then refer to `zh-CN.yml` or `en.yml` to write language files. Also note that PicGo will display the name of the language to the user via `LANG_DISPLAY_LABEL` in the language file.
2. Add a default language to `src/universal/i18n/index.ts`. where `label` is the value of `LANG_DISPLAY_LABEL` in the language file, and `value` is the name of the language file.
3. If you are updating an existing language file, be sure to run `yarn gen-i18n` after the update to ensure that the correct language definition file can be generated.
### Submit code
1. Please check that the code has no extra comments, `console.log` and other debugging code.
2. Before submitting the code, please execute the command `git add . && yarn cz` to invoke PicGo's [Code Submission Specification Tool](https://github.com/PicGo/bump-version). Submit code through this tool.
+70 -1
View File
@@ -1,6 +1,6 @@
## 常见问题
> 在使用 PicGo 期间你会遇到很多问题,不过很多问题其实之前就有人提问过,也被解决,所以你可以先看看 [使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B),这份 FAQ,以及那些被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed),应该能找到答案。
> 在使用 PicGo 期间你会遇到很多问题,不过很多问题其实之前就有人提问过,也被解决,所以你可以先看看 [使用文档](https://picgo.github.io/PicGo-Doc/guide/getting-started.html#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B),这份 FAQ,以及那些被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed),应该能找到答案。
## 1. 七牛图床上传图片成功后,相册里无法显示或图片无`http://`前缀
@@ -57,7 +57,76 @@ PicGo 在 Mac 上是一个顶部栏应用,在 dock 栏是不会有图标的。
1. 先自行搜索 error 里的报错信息,往往你能百度或者谷歌出问题原因,不必开 issue。
2. 如果有带有 `401``403``40X` 状态码字样的,不用怀疑,就是你配置写错了,仔细检查配置,看看是否多了空格之类的。
3. 如果带有 `HttpError``RequestError``socket hang up` 等字样的说明这是网络问题,我无法帮你解决网络问题,请检查你自己的网络,是否有代理,DNS 设置是否正常等。
3. 通常网络问题引起的上传失败都是因为代理设置不当导致的。如果开启了系统代理,建议同时也在 PicGo 的代理设置中设置对应的HTTP代理。参考 [#912](https://github.com/Molunerfinn/PicGo/issues/912)
## 10. macOS版本安装完之后没有主界面
请找到PicGo在顶部栏的图标,然后右键(触摸板双指点按,或者鼠标右键),即可找到「打开详细窗口」的菜单。
## 11. 相册突然无法显示图片 或者 上传后相册不更新 或者 使用Typora+PicGo上传图片成功但是没有写回Typora
这个原因可能是相册存储文件损坏导致的。可以找到 PicGo 配置文件所在路径下的 `picgo.db` ,将其删掉(删掉前建议备份一遍),再重启 PicGo 试试。
注意同时看看日志文件里有没有什么error,必要时可以提issue。2.3.0以上的版本已经解决因为 `picgo.db` 损坏导致的上述问题,建议更新版本。
## 12. Gitee相关问题
如果在使用 Gitee 图床的时候遇到上传的问题,由于 PicGo 并没有官方提供 Gitee 上传服务,无法帮你解决,请去你所使用的 Gitee 插件仓库发相关的issue。
## 13. macOS系统安装完PicGo显示「文件已损坏」或者安装完打开没有反应
因为 PicGo 没有签名,所以会被 macOS 的安全检查所拦下。
1. 安装后打开遇到「文件已损坏」的情况,请按如下方式操作:
信任开发者,会要求输入密码:
```
sudo spctl --master-disable
```
然后放行 PicGo :
```
xattr -cr /Applications/PicGo.app
```
然后就能正常打开。
如果提示以下内容
```sh
option -r not recognized
usage: xattr [-slz] file [file ...]
xattr -p [-slz] attr_name file [file ...]
xattr -w [-sz] attr_name attr_value file [file ...]
xattr -d [-s] attr_name file [file ...]
xattr -c [-s] file [file ...]
The first form lists the names of all xattrs on the given file(s).
The second form (-p) prints the value of the xattr attr_name.
The third form (-w) sets the value of the xattr attr_name to attr_value.
The fourth form (-d) deletes the xattr attr_name.
The fifth form (-c) deletes (clears) all xattrs.
options:
-h: print this help
-s: act on symbolic links themselves rather than their targets
-l: print long format (attr_name: attr_value)
-z: compress or decompress (if compressed) attribute value in zip format
```
执行命令
```
xattr -c /Applications/PicGo.app/*
```
如果上述命令依然没有效果,可以尝试下面的命令:
```
sudo xattr -d com.apple.quarantine /Applications/PicGo.app/
```
2. 如果安装打开后没有反应,请按下方顺序排查:
1. macOS安装好之后,PicGo 是不会弹出主窗口的,因为 PicGo 在 macOS 系统里设计是个顶部栏应用。注意看你顶部栏的图标,如果有 PicGo 的图标,说明安装成功了,点击图标即可打开顶部栏窗口。参考上述[第八点](#8-mac-上无法打开-picgo-的主窗口界面)。
2. 如果你是 M1 的系统,此前装过 PicGo 的 x64 版本,但是后来更新了 arm64 的版本发现打开后没反应,请重启电脑即可。
+35 -35
View File
@@ -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>
@@ -6,7 +20,7 @@
<img src="https://img.shields.io/badge/code%20style-standard-green.svg?style=flat-square" alt="">
</a>
<a href="https://github.com/Molunerfinn/PicGo/actions">
<img src="https://github.com/Molunerfinn/PicGo/workflows/Build/badge.svg" alt="">
<img src="https://github.com/Molunerfinn/PicGo/actions/workflows/main.yml/badge.svg" alt="">
</a>
<a href="https://github.com/Molunerfinn/PicGo/releases">
<img src="https://img.shields.io/github/downloads/Molunerfinn/PicGo/total.svg?style=flat-square" alt="">
@@ -17,9 +31,6 @@
<a href="https://github.com/PicGo/bump-version">
<img src="https://img.shields.io/badge/picgo-convention-blue.svg?style=flat-square" alt="">
</a>
<a href="https://gitter.im/picgo-all/PicGo?utm_source=share-link&utm_medium=link&utm_campaign=share-link">
<img src="https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-66ae93.svg?style=flat-square" alt="">
</a>
</div>
## 应用概述
@@ -51,33 +62,20 @@ PicGo 本体支持如下图床:
- 支持通过发送 HTTP 请求调用 PicGo 上传(v2.2.0+)
- 更多功能等你自己去发现,同时也会不断开发新功能
- 开发进度可以查看 [Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度
- 欢迎加入 [官方讨论区](https://github.com/Molunerfinn/PicGo/discussions) 与我交流
<!-- - 欢迎加入 [官方讨论区](https://github.com/Molunerfinn/PicGo/discussions) 与我交流 -->
**如果第一次使用,请参考应用 [使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html)。遇到问题了还可以看看 [FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md) 以及被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed)。**
**如果第一次使用,请参考应用 [使用文档](https://picgo.github.io/PicGo-Doc/guide/getting-started.html)。遇到问题了还可以看看 [FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md) 以及被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed)。**
## 下载安装
点击此处下载 [应用](https://github.com/Molunerfinn/PicGo/releases)。
### Windows
Windows 用户请下载最新版本的 `exe` 文件。
**还可以使用 [Scoop](https://scoop.sh/) 来安装 PicGo: `scoop bucket add helbing https://github.com/helbing/scoop-bucket` & `scoop install picgo`。 感谢 @helbing 的贡献!**
**还可以使用 [Chocolatey](https://chocolatey.org/) 来安装 PicGo: `choco install picgo`。 感谢 @iYato 的贡献!**
### macOS
macOS 用户请下载最新版本的 `dmg` 文件。
**还可以使用 [Homebrew](https://brew.sh/) 来安装 PicGo: `brew install picgo --cask`。感谢 @womeimingzi11 的贡献!**
### Linux
Linux 用户请下载 `AppImage` 文件。
**如果你是 Arch 类的 Linux 用户,可以直接通过 `yay -S picgo-appimage` 来安装 PicGo (若是没有则先安装 yay ,是 aur 仓库助手)。感谢 @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 的贡献 |
## 应用截图
@@ -97,6 +95,7 @@ Linux 用户请下载 `AppImage` 文件。
2. `git clone https://github.com/Molunerfinn/PicGo.git` 并进入项目。
3. `yarn` 下载依赖。注意如果你没有 `yarn`,请去 [官网](https://classic.yarnpkg.com/en/docs/install) 下载安装后再使用。 **用 `npm install` 将导致未知错误!**
4. Mac 需要有 Xcode 环境,Windows 需要有 VS 环境。
5. 如果需要贡献代码,可以参考[贡献指南](./CONTRIBUTING.md)。
### 开发模式
@@ -104,21 +103,21 @@ Linux 用户请下载 `AppImage` 文件。
```bash
ctrl+c # 退出开发模式
npm run electron:serve # 重新进入开发模式
npm run dev # 重新进入开发模式
```
**注:Windows 开发模式运行之后会在底部任务栏的右下角应用区出现 PicGo 的应用图标。**
### 生产模式
如果你需要自行构建,可以 `npm run electron:build` 开始进行构建。构建成功后,会在 `dist_electron` 目录里出现构建成功的相应安装文件。
如果你需要自行构建,可以 `npm run build` 开始进行构建。构建成功后,会在 `dist` 目录里出现构建成功的相应安装文件。
**注意**:如果你的网络环境不太好,可能会出现 `electron-builder` 下载 `electron` 二进制文件失败的情况。这个时候需要在 `npm run electron:build` 之前指定一下 `electron` 的源为国内源:
**注意**:如果你的网络环境不太好,可能会出现 `electron-builder` 下载 `electron` 二进制文件失败的情况。这个时候需要在 build 之前指定一下 `electron` 的源为国内源:
```bash
export ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/"
# 在 Windows 上,则可以使用 set ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/ (无需引号)
npm run electron:build
export ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"
# 在 Windows 上,则可以使用 set ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ (无需引号)
npm run build
```
只需第一次构建的时候指定一下国内源即可。后续构建不需要特地指定。二进制文件下载在 `~/.electron/` 目录下。如果想要更新 `electron` 构建版本,可以删除 `~/.electron/` 目录,然后重新运行上一步,让 `electron-builder `去下载最新的 `electron` 二进制文件。
@@ -126,7 +125,8 @@ npm run electron:build
## 其他相关
- [vs-picgo](https://github.com/PicGo/vs-picgo)PicGo 的 VS Code 版。
- [flutter-picgo](https://github.com/PicGo/flutter-picgo)PicGo 的手机版(支持 Android 和 iOS )。
- [flutter-picgo](https://github.com/PicGo/flutter-picgo)PicGo 的手机版 App(支持 Android 和 iOS )。
- [PicHoro](https://github.com/Kuingsmile/PicHoro):另一款支持 PicGo 配置的手机版 App(暂时只支持 Android)。
## 赞助
@@ -144,4 +144,4 @@ npm run electron:build
[MIT](http://opensource.org/licenses/MIT)
Copyright (c) 2017 - 2019 Molunerfinn
Copyright (c) 2017 - Now Molunerfinn
-32
View File
@@ -1,32 +0,0 @@
# Commented sections below can be used to run tests on the CI server
# https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing
version: 0.1.{build}
branches:
only:
- master
image: Visual Studio 2017
platform:
- x64
cache:
- '%APPDATA%\npm-cache'
- '%USERPROFILE%\.electron'
- '%USERPROFILE%\AppData\Local\Yarn\cache'
init:
- git config --global core.autocrlf input
install:
- ps: Install-Product node 12 x64
- git reset --hard HEAD
- yarn
- node --version
build_script:
#- yarn test
- yarn build --win --ia32
- yarn release
test: false
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

+131
View File
@@ -0,0 +1,131 @@
# PicGo 2.4.0 Changelog
## Features
- 新增 相册页新增文件名展示,参考 #1050
- 新增 相册中和顶部栏窗口中无法展示的图片或者 url 将会展示默认图片,参考#1050
![image](https://user-images.githubusercontent.com/12621342/210806238-e77bc3b1-5e32-4c9f-af5c-278c5706450d.png)
- 新增 macOS 顶部栏窗口新增文件名展示,参考 #1054
- 新增 同种类型图床支持多份配置,上传时可以指定某一份配置进行上传。感谢 @STDSuperman ,参考 #1016
![](https://user-images.githubusercontent.com/44311619/203093104-9537e08a-2ef0-450d-a59d-c470dbcdd6c8.png)
- 新增 选择图床的菜单可以支持选择该图床类型某一项具体配置
![image](https://user-images.githubusercontent.com/12621342/210804413-4f78804f-a451-4ca5-93a3-63d461261b18.png)
- 新增 显示 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 的贡献!
![image](https://github.com/Molunerfinn/PicGo/assets/12621342/6e623751-d330-47f8-89bc-986031452914)
- 新增 `tips` 配置渲染的支持,可以动态渲染 Uploader config 的 tips。支持 markdown 格式。参考 [tcyun uploader config](https://github.com/PicGo/PicGo-Core/blob/dev/src/plugins/uploader/tcyun.ts#L280)
![image](https://github.com/Molunerfinn/PicGo/assets/12621342/2f9b8734-717a-4f49-b397-1ddb9437bc3c)
- 新增 上传界面展示当前图床使用的配置名
![image](https://github.com/Molunerfinn/PicGo/assets/12621342/52c58fff-4810-4b91-92d7-5ade512eaca8)
- 新增 相册页工具栏,目前内置 `批量修改图片 URL HOST 的功能` 。参考 #875
注意:需要先选中指定的图片,然后会根据已选中的图片进行修改,你可以通过图床筛选功能只筛选出需要修改的图片。
例如,你有一批图片都是 `https://www.a.com/...` 打头的 URL,你想把 `www.a.com` 批量修改成 `www.b.com` ,就可以用这个功能
![image](https://github.com/Molunerfinn/PicGo/assets/12621342/ee314dfc-7699-4ceb-8638-cafe7948bd5a)
- 新增 `启动模式`,可以设置启动的时候是否要打开窗口。全平台支持 `静默启动`(默认值) & `打开主窗口`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).
![image](https://user-images.githubusercontent.com/12621342/210806238-e77bc3b1-5e32-4c9f-af5c-278c5706450d.png)
- Add filename display in the macOS tray window (#1054).
- Allow multiple configs per uploader type and choose one for upload (thanks @STDSuperman, #1016).
![](https://user-images.githubusercontent.com/44311619/203093104-9537e08a-2ef0-450d-a59d-c470dbcdd6c8.png)
- Allow the uploader selection menu to pick a specific config entry for that uploader type.
![image](https://user-images.githubusercontent.com/12621342/210804413-4f78804f-a451-4ca5-93a3-63d461261b18.png)
- 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).
![image](https://github.com/Molunerfinn/PicGo/assets/12621342/6e623751-d330-47f8-89bc-986031452914)
- Add Markdown-rendered tips for uploader config (see tcyun uploader config).
![image](https://github.com/Molunerfinn/PicGo/assets/12621342/2f9b8734-717a-4f49-b397-1ddb9437bc3c)
- Show the active uploader config name in the upload screen.
![image](https://github.com/Molunerfinn/PicGo/assets/12621342/52c58fff-4810-4b91-92d7-5ade512eaca8)
- 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/...`.
![image](https://github.com/Molunerfinn/PicGo/assets/12621342/ee314dfc-7699-4ceb-8638-cafe7948bd5a)
- 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).
+48
View File
@@ -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 `----------`.
+3 -3
View File
@@ -7,13 +7,13 @@
small(v-if="version") {{ version }}
h2.desc 图片上传+管理新体验
button.download(@click="goLink('https://github.com/Molunerfinn/picgo/releases')") 免费下载
button.download(@click="goLink('https://picgo.github.io/PicGo-Doc/zh/guide/')") 查看文档
button.download(@click="goLink('https://picgo.github.io/PicGo-Doc/guide/')") 查看文档
h3.desc
| 基于#[a(href="https://github.com/SimulatedGREG/electron-vue" target="_blank") electron-vue]开发
h3.desc
| 支持macOS,Windows,Linux
h3.desc
| 支持#[a(href="https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E6%8F%92%E4%BB%B6%E8%AE%BE%E7%BD%AE%EF%BC%88v2-0%EF%BC%89" target="_blank") 插件系统]让PicGo更强大
| 支持#[a(href="https://picgo.github.io/PicGo-Doc/guide/config.html#%E6%8F%92%E4%BB%B6%E8%AE%BE%E7%BD%AE%EF%BC%88v2-0%EF%BC%89" target="_blank") 插件系统]让PicGo更强大
#container.container-fluid
.row.ex-width
img.gallery.col-xs-10.col-xs-offset-1.col-md-offset-2.col-md-8(src="https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/first.png")
@@ -30,7 +30,7 @@
</template>
<script>
export default {
name: '',
name: 'HomePage',
data () {
return {
version: '',
+107
View File
@@ -0,0 +1,107 @@
import type { Configuration } from 'electron-builder'
const config: Configuration = {
appId: 'com.molunerfinn.picgo',
productName: 'PicGo',
// publish: [
// {
// provider: 'github',
// owner: 'Molunerfinn',
// repo: 'PicGo',
// releaseType: 'draft'
// }
// ],
// temporarily disable auto update feature
publish: [],
files: [
'dist_electron/**/*',
'public/**/*',
'package.json',
'!node_modules/@babel/**/*',
"!**/node_modules/typescript{,/**}"
],
extraResources: [
{
from: 'public',
to: 'public'
}
],
dmg: {
contents: [
{
x: 410,
y: 150,
type: 'link',
path: '/Applications'
},
{
x: 130,
y: 150,
type: 'file'
}
]
},
mac: {
icon: 'build/icons/512x512.png',
extendInfo: {
LSUIElement: 0
},
target: [
{
target: 'dmg',
arch: ['x64', 'arm64']
}
],
artifactName: 'PicGo-${version}-${arch}.${ext}'
},
win: {
icon: 'build/icons/icon.ico',
artifactName: 'PicGo-${version}-${arch}.exe',
target: [
{
target: 'nsis',
arch: ['x64']
},
{
target: 'nsis',
arch: ['ia32']
},
{
target: 'nsis',
arch: ['arm64']
}
]
},
nsis: {
shortcutName: 'PicGo',
oneClick: false,
allowToChangeInstallationDirectory: true,
buildUniversalInstaller: false,
include: 'build/installer.nsh'
},
linux: {
executableName: 'PicGo',
icon: 'build/icons/512x512.png',
artifactName: 'PicGo-${version}-${arch}.${ext}',
target: [
{
target: 'AppImage',
arch: ['x64', 'arm64']
},
{
target: 'deb',
arch: ['x64', 'arm64']
},
{
target: 'snap',
arch: ['x64']
}
],
maintainer: 'Molunerfinn',
category: 'Utility',
publish: []
}
}
export default config
+58
View File
@@ -0,0 +1,58 @@
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
// temp for webUtils
import electronRenderer from '@molunerfinn/vite-plugin-electron-renderer'
import { resolve } from 'path'
const alias = {
'@': resolve(__dirname, 'src/renderer'),
'~': resolve(__dirname, 'src'),
'#': resolve(__dirname, 'src/universal'),
root: resolve(__dirname, '.'),
apis: resolve(__dirname, 'src/main/apis'),
'@core': resolve(__dirname, 'src/main/apis/core')
}
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()],
resolve: { alias },
build: {
outDir: 'dist_electron/main',
rollupOptions: {
input: {
index: resolve(__dirname, 'src/background.ts')
}
}
}
},
preload: {
plugins: [externalizeDepsPlugin()],
resolve: { alias },
build: {
outDir: 'dist_electron/preload',
rollupOptions: {
input: {
index: resolve(__dirname, 'src/preload/index.ts')
}
}
}
},
renderer: {
root: 'src/renderer',
publicDir: resolve(__dirname, 'src/renderer/public'),
resolve: { alias },
plugins: [vue(), electronRenderer()],
build: {
outDir: 'dist_electron/renderer',
rollupOptions: {
input: {
index: resolve(__dirname, 'src/renderer/index.html')
}
}
},
server: {
port: 5173
}
}
})
+126
View File
@@ -0,0 +1,126 @@
const path = require('node:path')
const eslintJs = require('@eslint/js')
const tsPlugin = require('@typescript-eslint/eslint-plugin')
const tsParser = require('@typescript-eslint/parser')
const importPlugin = require('eslint-plugin-import')
const promisePlugin = require('eslint-plugin-promise')
const vuePlugin = require('eslint-plugin-vue')
const isProduction = process.env.NODE_ENV === 'production'
const vueConfigs = vuePlugin.configs['flat/recommended'].map(config => ({
...config,
languageOptions: {
...(config.languageOptions || {}),
parserOptions: {
...(config.languageOptions?.parserOptions || {}),
parser: tsParser,
ecmaVersion: 'latest',
sourceType: 'module',
extraFileExtensions: ['.vue']
}
}
}))
const tsConfigs = tsPlugin.configs['flat/recommended'].map(config => ({
...config,
files: config.files || ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
languageOptions: {
...(config.languageOptions || {}),
parser: tsParser,
parserOptions: {
...(config.languageOptions?.parserOptions || {}),
ecmaVersion: 'latest',
sourceType: 'module',
extraFileExtensions: ['.vue'],
project: path.join(__dirname, 'tsconfig.json'),
tsconfigRootDir: __dirname
}
},
rules: {
...(config.rules || {}),
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrors: 'none'
}],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
'@typescript-eslint/no-empty-object-type': 'off'
}
}))
module.exports = [
{
ignores: [
'dist/**',
'dist_electron/**',
'build/**',
'test/unit/coverage/**',
'test/unit/*.js',
'test/e2e/*.js',
'node_modules/**'
]
},
{
name: 'eslint/base',
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
__static: 'readonly'
}
},
plugins: {
import: importPlugin,
promise: promisePlugin
},
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.d.ts', '.vue']
}
}
},
rules: {
...eslintJs.configs.recommended.rules,
...importPlugin.configs.recommended.rules,
...promisePlugin.configs.recommended.rules,
'import/named': 'off',
'import/no-named-as-default-member': 'off',
'import/no-unresolved': 'off',
'promise/catch-or-return': 'off',
'promise/always-return': 'off',
'no-console': 'off',
'no-debugger': isProduction ? 'error' : 'off',
'no-async-promise-executor': 'off',
'no-empty': ['error', { allowEmptyCatch: true }],
'no-unused-vars': 'off'
}
},
...vueConfigs,
{
files: ['*.vue', '**/*.vue'],
rules: {
'vue/no-v-html': 'off',
'vue/attribute-hyphenation': 'off'
}
},
...tsConfigs,
{
files: ['**/*.{ts,tsx,vue}'],
rules: {
'no-undef': 'off'
}
},
{
files: ['**/*.d.ts'],
rules: {
'no-var': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-explicit-any': 'off'
}
}
]
+103 -75
View File
@@ -1,25 +1,110 @@
{
"name": "picgo",
"version": "2.3.1-beta.0",
"version": "2.4.1-beta.1",
"private": true,
"main": "dist_electron/main/index.js",
"description": "A powerful & simple image uploader for creators.",
"author": {
"name": "Molunerfinn",
"email": "marksz@teamsz.xyz"
},
"scripts": {
"dev": "vue-cli-service electron:serve",
"build": "vue-cli-service electron:build",
"lint": "vue-cli-service lint",
"build": "electron-vite build && electron-builder --config electron-builder.config.ts",
"build:win": "npm run build && electron-builder --config electron-builder.config.ts --win",
"build:mac": "npm run build && electron-builder --config electron-builder.config.ts --mac",
"build:linux": "npm run build && electron-builder --config electron-builder.config.ts --linux",
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.vue src/",
"tsc": "tsc --noEmit",
"bump": "bump-version",
"cz": "git-cz",
"dev": "electron-vite dev",
"preview": "electron-vite preview",
"gen-i18n": "node ./scripts/gen-i18n-types.js",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"cz": "git-cz",
"bump": "bump-version",
"release": "vue-cli-service electron:build --publish always"
"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"
},
"main": "background.js",
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"@picgo/i18n": "^1.0.0",
"@picgo/store": "^2.1.0",
"axios": "^0.19.0",
"clip-filepaths": "^0.3.0",
"compare-versions": "^4.1.3",
"core-js": "^3.27.1",
"dayjs": "^1.11.19",
"element-plus": "^2.3.7",
"epipebomb": "^1.0.0",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
"keycode": "^2.2.0",
"lodash": "^4.17.21",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"marked": "^7.0.4",
"mitt": "^3.0.1",
"multer": "^1.4.5-lts.1",
"picgo": "^1.6.0",
"qrcode.vue": "^3.3.3",
"semver": "^7.7.3",
"shell-path": "2.1.0",
"tunnel": "^0.0.6",
"uuid": "^9.0.0",
"vue": "^3.3.4",
"vue-router": "^4.2.2",
"vue3-lazyload": "^0.3.6",
"vue3-photo-preview": "^0.3.0",
"write-file-atomic": "^4.0.1"
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.276.0",
"@aws-sdk/lib-storage": "^3.276.0",
"@eslint/js": "^9.39.1",
"@molunerfinn/vite-plugin-electron-renderer": "^0.14.7",
"@picgo/bump-version": "^1.1.2",
"@types/electron-devtools-installer": "^2.2.0",
"@types/fs-extra": "^9.0.13",
"@types/inquirer": "^6.5.0",
"@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.17.21",
"@types/lowdb": "^1.0.9",
"@types/multer": "^1.4.12",
"@types/node": "^20",
"@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": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0",
"@vitejs/plugin-vue": "^6.0.2",
"autoprefixer": "^10.4.14",
"conventional-changelog": "^3.1.18",
"cz-customizable": "^6.2.0",
"dotenv": "^16.0.1",
"dpdm": "^3.13.1",
"electron": "^38",
"electron-builder": "26.1.0",
"electron-devtools-installer": "^3.2.0",
"electron-vite": "^4.0.1",
"eslint": "^9.39.1",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-vue": "^10.6.2",
"husky": "^3.1.0",
"postcss": "^8.4.23",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"tailwindcss": "^3.3.2",
"typescript": "^5.9.3",
"vite": "^7.2.6"
},
"commitlint": {
"extends": [
"./node_modules/@picgo/bump-version/commitlint-picgo"
]
},
"config": {
"commitizen": {
@@ -29,66 +114,9 @@
"config": "./node_modules/@picgo/bump-version/.cz-config.js"
}
},
"commitlint": {
"extends": [
"./node_modules/@picgo/bump-version/commitlint-picgo"
]
},
"dependencies": {
"@picgo/store": "^1.0.3",
"axios": "^0.19.0",
"core-js": "^3.3.2",
"element-ui": "^2.13.0",
"fix-path": "^2.1.0",
"fs-extra": "^10.0.0",
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"picgo": "^1.4.24",
"qrcode.vue": "^1.7.0",
"uuidv4": "^6.2.11",
"vue": "^2.6.10",
"vue-gallery": "^2.0.1",
"vue-lazyload": "^1.2.6",
"vue-router": "^3.1.3"
},
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
"@picgo/bump-version": "^1.1.2",
"@types/fs-extra": "^9.0.13",
"@types/inquirer": "^6.5.0",
"@types/lowdb": "^1.0.9",
"@types/node": "^16.10.2",
"@types/request-promise-native": "^1.0.17",
"@types/semver": "^7.3.8",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-eslint": "^4.0.0",
"@vue/cli-plugin-router": "^4.0.0",
"@vue/cli-plugin-typescript": "^4.5.13",
"@vue/cli-service": "^4.0.0",
"@vue/eslint-config-standard": "^6.1.0",
"@vue/eslint-config-typescript": "^7.0.0",
"conventional-changelog": "^3.1.18",
"cz-customizable": "^6.2.0",
"electron": "^16.0.6",
"electron-devtools-installer": "^3.2.0",
"eslint": "^7.32.0",
"eslint-config-standard": ">=16.0.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.0.0",
"husky": "^3.1.0",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"typescript": "^4.4.3",
"vue-cli-plugin-electron-builder": "^2.1.1",
"vue-property-decorator": "^8.3.0",
"vue-template-compiler": "^2.6.10"
},
"resolutions": {
"@types/node": "^16.10.2"
"husky": {
"hooks": {
"commit-msg": "npm run lint:dpdm && commitlint -E HUSKY_GIT_PARAMS"
}
}
}
+11845
View File
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
onlyBuiltDependencies:
- core-js
- ejs
- electron
- electron-winstaller
- esbuild
- husky
- vue-demi
# for multi-arch builds, include both x64 and arm64 versions of electron
# will be deprecated in future(use different arch machine to build different arch binaries)
supportedArchitectures:
os:
- current
cpu:
- x64
- arm64
+2 -1
View File
@@ -1,5 +1,6 @@
module.exports = {
plugins: {
autoprefixer: {}
autoprefixer: {},
tailwindcss: {}
}
}
+285
View File
@@ -0,0 +1,285 @@
LANG_DISPLAY_LABEL: "English"
ABOUT: About
OPEN_MAIN_WINDOW: Open Main Window
CHOOSE_DEFAULT_PICBED: Choose Default Picbed
OPEN_UPDATE_HELPER: Open Update Helper
PRIVACY_AGREEMENT: Privacy Agreement
RELOAD_APP: Reload App
UPLOAD_FAILED: Upload Failed
UPLOAD_SUCCEED: Upload Succeed
UPLOAD_PROGRESS: Upload Progress
OPERATION_FAILED: Operation Failed
OPERATION_SUCCEED: Operation Succeed
UPLOADING: Uploading
QUICK_UPLOAD: Quick Upload
UPLOAD_BY_CLIPBOARD: Upload by Clipboard
HIDE_WINDOW: Hide Window
SPONSOR_PICGO: Sponsor PicGo
SHOW_PICBED_QRCODE: Show Picbed Qrcode
PICBED_QRCODE: Picbed Qrcode
ENABLE: Enable
DISABLE: Disable
CONFIG_THING: Config ${c}
FIND_NEW_VERSION: Find New Version
NO_MORE_NOTICE: No More Notice
SHOW_DEVTOOLS: Show Devtools
CURRENT_PICBED: Current Picbed
OPEN_TOOLBOX: Open Toolbox
# ---renderer i18n begin---
CHOOSE_YOUR_DEFAULT_PICBED: "Choose ${d} as your default picbed:"
UPLOAD_AREA: Upload Area
GALLERY: Gallery
PICBEDS_SETTINGS: Picbeds Settings
PICGO_SETTINGS: PicGo Settings
PLUGIN_SETTINGS: Plugins Settings
PICGO_SPONSOR_TEXT: PicGo is a free software, if you like it, please don't forget to buy me a cup of coffee.
ALIPAY: Alipay
WECHATPAY: Wechat Pay
CHOOSE_PICBED: Choose Picbed
COPY_PICBED_CONFIG: Copy Picbed Config
COPY_PICBED_CONFIG_SUCCEED: Copy Picbed Config Succeed
INPUT: Input
CANCEL: Cancel
CONFIRM: Confirm
CHOOSE_SHOWED_PICBED: Choose Showed Picbed
CHOOSE_PASTE_FORMAT: Choose Paste Format
SEARCH: Search
COPY: Copy
DELETE: Delete
SELECT_ALL: Select All
CHANGE_IMAGE_URL: Change Image URL
CHANGE_IMAGE_URL_SUCCEED: Change Image URL Succeed
COPY_LINK_SUCCEED: Copy Link Succeed
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
SETTINGS: Settings
SETTINGS_OPEN_CONFIG_FILE: Open Config File
SETTINGS_CLICK_TO_OPEN: Click to Open
SETTINGS_SET_LOG_FILE: Set Log File
SETTINGS_CLICK_TO_SET: Click to Set
SETTINGS_CLICK_TO_CHECK: Click to Check
SETTINGS_SET_SHORTCUT: Set Shortcut
SETTINGS_CUSTOM_LINK_FORMAT: Custom Link Format
SETTINGS_SET_PROXY_AND_MIRROR: Set Proxy and Mirror
SETTINGS_SET_SERVER: Set Server
SETTINGS_CHECK_UPDATE: Check Update
SETTINGS_OPEN_UPDATE_HELPER: Open Update Helper
SETTINGS_OPEN: Open
SETTINGS_CLOSE: Close
SETTINGS_ACCEPT_BETA_UPDATE: Accept Beta Update
SETTINGS_LAUNCH_ON_BOOT: Launch On Boot
SETTINGS_RENAME_BEFORE_UPLOAD: Rename Before Upload
SETTINGS_TIMESTAMP_RENAME: Timestamp Rename
SETTINGS_OPEN_UPLOAD_TIPS: Open Upload Tips
SETTINGS_MINI_WINDOW_ON_TOP: Mini Window On Top
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_UPLOAD_PROXY: Upload Proxy
SETTINGS_PLUGIN_INSTALL_PROXY: Proxy for Plugin Install
SETTINGS_PLUGIN_INSTALL_MIRROR: Mirror for Plugin Install
SETTINGS_CURRENT_VERSION: Current Version
SETTINGS_NEWEST_VERSION: Newest Version
SETTINGS_GETING: Getting...
SETTINGS_TIPS_HAS_NEW_VERSION: PicGo has a new version, please click confirm to open download page
SETTINGS_LOG_FILE: Log File
SETTINGS_LOG_LEVEL: Log Level
SETTINGS_LOG_FILE_SIZE: Log File Size
SETTINGS_SET_PICGO_SERVER: Set PicGo Server
SETTINGS_TIPS_SERVER_NOTICE: If you don't know what is the server's function, please read the document, or don't modify the configuration.
SETTINGS_ENABLE_SERVER: Enable Server
SETTINGS_SET_SERVER_HOST: Set Server Host
SETTINGS_SET_SERVER_PORT: Set Server Port
SETTINGS_TIP_PLACEHOLDER_HOST: Default:127.0.0.1
SETTINGS_TIP_PLACEHOLDER_PORT: Default:36677
SETTINGS_LOG_LEVEL_ALL: All
SETTINGS_LOG_LEVEL_SUCCESS: Success
SETTINGS_LOG_LEVEL_ERROR: Error
SETTINGS_LOG_LEVEL_INFO: Info
SETTINGS_LOG_LEVEL_WARN: Warn
SETTINGS_LOG_LEVEL_NONE: None
SETTINGS_RESULT: Result
SETTINGS_DEFAULT_PICBED: Default Picbed
SETTINGS_SET_DEFAULT_PICBED: Set Default Picbed
SETTINGS_NOT_CONFIG_OPTIONS: Not Config Options
SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD: Use Builtin Clipboard to Upload
SETTINGS_CHOOSE_LANGUAGE: Choose Language
UPLOADER_CONFIG_NAME: Configuration Name
BUILTIN_CLIPBOARD_TIPS: Use builtin clipboard function to upload instead of using scripts
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
SHORTCUT_NAME: Shortcut Name
SHORTCUT_BIND: Shortcut Binding
SHORTCUT_STATUS: Status
SHORTCUT_ENABLED: Enabled
SHORTCUT_DISABLED: Disabled
SHORTCUT_SOURCE: Source
SHORTCUT_HANDLE: Handle
SHORTCUT_ENABLE: Enable
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
ALREADY_UPLOAD: Already Upload
# upload-page
PICTURE_UPLOAD: Picture Upload
DRAG_FILE_TO_HERE: Drag file to here, or
CLICK_TO_UPLOAD: click to upload
LINK_FORMAT: Link Format
CUSTOM: Custom
CLIPBOARD_PICTURE: Clipboard
TIPS_DRAG_VALID_PICTURE_OR_URL: Drag valid picture or url to here
TIPS_INPUT_URL: Input URL
TIPS_HTTP_PREFIX: http:// or https://
TIPS_INPUT_VALID_URL: Input valid URL
# plugins
PLUGIN_SEARCH_PLACEHOLDER: Search picgo plugins on npm, or click the button to view the awesome plugins list
PLUGIN_INSTALL: Install
PLUGIN_INSTALLING: Installing...
PLUGIN_INSTALLED: Installed
PLUGIN_DOING_SOMETHING: Doing...
PLUGIN_LIST: Plugin List
PLUGIN_IMPORT_LOCAL: Import Local Plugins
# tips
TIPS_REMOVE_LINK: This operation will remove the picture from the album, continue?
TIPS_WILL_REMOVE_CHOOSED_IMAGES: This operation will remove the picture from the album, continue?
TIPS_MUST_CONTAINS_URL: Must contains $url or $fileName or $extName
TIPS_NETWORK_ERROR: Network Error
TIPS_NEED_RELOAD: Need Reload App
TIPS_PLEASE_CHOOSE_LOG_LEVEL: Please choose log level
TIPS_SET_SUCCEED: Set successfully
TIPS_PLUGIN_NOT_GUI_IMPLEMENT: This plugin is not optimized for the GUI, continue?
TIPS_CLICK_NOTIFICATION_TO_RELOAD: Click notification to reload app
TIPS_GET_PLUGIN_LIST_FAILED: Get plugin list failed
# ---renderer i18n end---
# plugins
PLUGIN_INSTALL_SUCCEED: Plugin install succeed
PLUGIN_INSTALL_FAILED: Plugin install failed
PLUGIN_UNINSTALL_SUCCEED: Plugin uninstall succeed
PLUGIN_UNINSTALL_FAILED: Plugin uninstall failed
PLUGIN_UPDATE_SUCCEED: Plugin update succeed
PLUGIN_UPDATE_FAILED: Plugin update failed
PLUGIN_IMPORT_SUCCEED: Plugin import succeed
PLUGIN_IMPORT_FAILED: Plugin import failed
ENABLE_PLUGIN: Enable Plugin
DISABLE_PLUGIN: Disable Plugin
UNINSTALL_PLUGIN: Uninstall Plugin
UPDATE_PLUGIN: Update Plugin
# toolbox
TOOLBOX: Toolbox
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_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
TOOLBOX_CANT_AUTO_FIX: Unable to automatically repair, please repair the following problems yourself
TOOLBOX_START_SCAN: Start scanning
TOOLBOX_RE_SCAN: Re scanning
TOOLBOX_START_FIX: Start fixing
TOOLBOX_SUCCESS_TIPS: Congratulations, no problems were found
TOOLBOX_CHECK_CONFIG_FILE_PATH_TIPS: "The configuration file path is: ${path}"
TOOLBOX_CHECK_CONFIG_FILE_BROKEN_TIPS: The configuration file is damaged
TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS: "The album file path is: ${path}"
TOOLBOX_CHECK_GALLERY_FILE_BROKEN_TIPS: The album file is damaged
TOOLBOX_CHECK_PROXY_SUCCESS_TIPS: Proxy settings normal
TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS: No proxy settings
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT: Proxy settings incorrect
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING: Proxy settings unavailable
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_TIPS: "The temporary folder path for clipboard pictures is: ${path}"
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS: "The temporary folder for clipboard pictures does not exist: ${path}"
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: "Please create the folder yourself: ${path}"
# tips
TIPS_NOTICE: Tips
TIPS_WARNING: Warning
TIPS_ERROR: Error
TIPS_INSTALL_NODE_AND_RELOAD_PICGO: Please install Node.js and restart PicGo to continue
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: Plugin is trying to remove some images from the album gallery, continue?
TIPS_PLUGIN_OVERWRITE_GALLERY: Plugin is trying to overwrite the album gallery, continue?
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_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
TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED: Custom link style modified successfully
TIPS_FIND_NEW_VERSION: Find new version ${v}update many new features, do you want to download the latest version?
# privacy
PRIVACY: >
This software respects and protects the personal privacy of all users who use the service. In order to provide you with more accurate and better services, this software will use and collect some of your behavioral information in accordance with the provisions of this Privacy Policy. When you agree to the software service use agreement, you are deemed to have agreed to the entire content of this privacy policy. This privacy policy is an integral part of the software service use agreement, and it will not be used if you do not agree.
This Agreement will be updated periodically.
1. Scope of application
a) When you use this software, this software will record some information about your operation behavior of this software, including but not limited to the time-consuming, type, quantity and other information of your use of this software to upload files.
2. Use of Information
a) After obtaining your usage data, the software will upload it to the data analysis server so as to provide you with better services after analyzing the data.
3. Information disclosure
a) This software will not disclose your information to untrusted third parties.
b) In accordance with the relevant provisions of the law, or the requirements of administrative or judicial institutions, disclose to third parties or administrative or judicial institutions;
c) If you violate relevant Chinese laws, regulations or relevant rules, you need to disclose it to a third party;
4. Information Security
a) This software does not collect your personal information, key information and other private information, and the collected information is only used for improving the software, optimizing the experience, and understanding the daily activities of the software.
PRIVACY_TIPS: Please agree the privacy policy to upload
QUIT: Quit
+285
View File
@@ -0,0 +1,285 @@
LANG_DISPLAY_LABEL: 中文
ABOUT: 关于
OPEN_MAIN_WINDOW: 打开主窗口
CHOOSE_DEFAULT_PICBED: 选择默认图床
OPEN_UPDATE_HELPER: 打开更新助手
PRIVACY_AGREEMENT: 隐私协议
RELOAD_APP: 重启应用
UPLOAD_SUCCEED: 上传成功
UPLOAD_FAILED: 上传失败
UPLOAD_PROGRESS: 上传进度
OPERATION_SUCCEED: 操作成功
OPERATION_FAILED: 操作失败
UPLOADING: 正在上传
QUICK_UPLOAD: 快捷上传
UPLOAD_BY_CLIPBOARD: 剪贴板图片上传
HIDE_WINDOW: 隐藏窗口
SPONSOR_PICGO: 赞助 PicGo
SHOW_PICBED_QRCODE: 生成图床配置二维码
PICBED_QRCODE: 图床配置二维码
ENABLE: 启用
DISABLE: 禁用
CONFIG_THING: 配置${c}
FIND_NEW_VERSION: 发现新版本
NO_MORE_NOTICE: 以后不再提醒
SHOW_DEVTOOLS: 打开开发者工具
CURRENT_PICBED: 当前图床
OPEN_TOOLBOX: 打开修复工具箱
# ---renderer i18n begin---
CHOOSE_YOUR_DEFAULT_PICBED: 选择 ${d} 作为你默认图床:
UPLOAD_AREA: 上传区
GALLERY: 相册
PICBEDS_SETTINGS: 图床设置
PICGO_SETTINGS: PicGo设置
PLUGIN_SETTINGS: 插件设置
PICGO_SPONSOR_TEXT: PicGo是免费开源的软件,如果你喜欢它,对你有帮助,不妨请我喝杯咖啡?
ALIPAY: 支付宝
WECHATPAY: 微信支付
CHOOSE_PICBED: 选择图床
COPY_PICBED_CONFIG: 复制图床配置
COPY_PICBED_CONFIG_SUCCEED: 复制图床配置成功
INPUT: 输入框
CANCEL: 取消
CONFIRM: 确定
CHOOSE_SHOWED_PICBED: 请选择显示的图床
CHOOSE_PASTE_FORMAT: 请选择粘贴的格式
SEARCH: 搜索
COPY: 复制
DELETE: 删除
SELECT_ALL: 全选
CHANGE_IMAGE_URL: 修改图片URL
CHANGE_IMAGE_URL_SUCCEED: 修改图片URL成功
COPY_LINK_SUCCEED: 复制链接成功
BATCH_COPY_LINK_SUCCEED: 批量复制链接成功
FILE_RENAME: 文件改名
COPY_FILE_PATH: 复制文件路径
OPEN_FILE_PATH: 打开文件路径
SUCCESS: 成功
FAILED: 失败
# settings
SETTINGS: 设置
SETTINGS_OPEN_CONFIG_FILE: 打开配置文件
SETTINGS_CLICK_TO_OPEN: 点击打开
SETTINGS_SET_LOG_FILE: 设置日志文件
SETTINGS_CLICK_TO_SET: 点击设置
SETTINGS_CLICK_TO_CHECK: 点击检查
SETTINGS_SET_SHORTCUT: 设置快捷键
SETTINGS_CUSTOM_LINK_FORMAT: 自定义链接格式
SETTINGS_SET_PROXY_AND_MIRROR: 设置代理和镜像地址
SETTINGS_SET_SERVER: 设置Server
SETTINGS_CHECK_UPDATE: 检查更新
SETTINGS_OPEN_UPDATE_HELPER: 打开更新助手
SETTINGS_OPEN:
SETTINGS_CLOSE:
SETTINGS_ACCEPT_BETA_UPDATE: 接受Beta版本更新
SETTINGS_LAUNCH_ON_BOOT: 开机自启
SETTINGS_RENAME_BEFORE_UPLOAD: 上传前重命名
SETTINGS_TIMESTAMP_RENAME: 时间戳重命名
SETTINGS_OPEN_UPLOAD_TIPS: 开启上传提示
SETTINGS_MINI_WINDOW_ON_TOP: Mini窗口置顶
SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD: 上传后自动复制URL
SETTINGS_TIPS_PLACEHOLDER_URL: 用占位符 $url 来表示url的位置
SETTINGS_TIPS_PLACEHOLDER_FILENAME: 用占位符 $fileName 来表示文件名的位置
SETTINGS_TIPS_PLACEHOLDER_EXTNAME: 用占位符 $extName 来表示文件格式的位置
SETTINGS_TIPS_SUCH_AS:
SETTINGS_UPLOAD_PROXY: 上传代理
SETTINGS_PLUGIN_INSTALL_PROXY: 插件安装代理
SETTINGS_PLUGIN_INSTALL_MIRROR: 插件安装镜像
SETTINGS_CURRENT_VERSION: 当前版本
SETTINGS_NEWEST_VERSION: 最新版本
SETTINGS_GETING: 正在获取中
SETTINGS_TIPS_HAS_NEW_VERSION: PicGo更新啦,请点击确定打开下载页面
SETTINGS_LOG_FILE: 日志文件
SETTINGS_LOG_LEVEL: 日志记录等级
SETTINGS_LOG_FILE_SIZE: 日志文件大小
SETTINGS_SET_PICGO_SERVER: 设置PicGo-Server
SETTINGS_TIPS_SERVER_NOTICE: 如果你不知道Server的作用,请阅读文档,或者不用修改配置。
SETTINGS_ENABLE_SERVER: 是否开启Server
SETTINGS_SET_SERVER_HOST: 设置监听地址
SETTINGS_SET_SERVER_PORT: 设置监听端口
SETTINGS_TIP_PLACEHOLDER_HOST: 推荐默认地址:127.0.0.1
SETTINGS_TIP_PLACEHOLDER_PORT: 推荐默认端口:36677
SETTINGS_LOG_LEVEL_ALL: 全部-All
SETTINGS_LOG_LEVEL_SUCCESS: 成功-Success
SETTINGS_LOG_LEVEL_ERROR: 错误-Error
SETTINGS_LOG_LEVEL_INFO: 普通-Info
SETTINGS_LOG_LEVEL_WARN: 提醒-Warn
SETTINGS_LOG_LEVEL_NONE: 不记录日志-None
SETTINGS_RESULT: 设置结果
SETTINGS_DEFAULT_PICBED: 设置默认图床
SETTINGS_SET_DEFAULT_PICBED: 设为默认图床
SETTINGS_NOT_CONFIG_OPTIONS: 暂无配置项
SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD: 使用内置剪贴板上传
SETTINGS_CHOOSE_LANGUAGE: 选择语言
BUILTIN_CLIPBOARD_TIPS: 使用内置剪贴板函数而不是调用脚本获取剪贴板图片
UPLOADER_CONFIG_NAME: 图床配置名
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
SHORTCUT_NAME: 快捷键名称
SHORTCUT_BIND: 快捷键绑定
SHORTCUT_STATUS: 状态
SHORTCUT_ENABLED: 已启用
SHORTCUT_DISABLED: 已禁用
SHORTCUT_SOURCE: 来源
SHORTCUT_HANDLE: 操作
SHORTCUT_ENABLE: 启用
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: 等待上传
ALREADY_UPLOAD: 已上传
# upload-page
PICTURE_UPLOAD: 图片上传
DRAG_FILE_TO_HERE: 将文件拖拽到此处,或
CLICK_TO_UPLOAD: 点击上传
LINK_FORMAT: 链接格式
CUSTOM: 自定义
CLIPBOARD_PICTURE: 剪贴板图片
TIPS_DRAG_VALID_PICTURE_OR_URL: 请拖入合法的图片文件或者图片URL地址
TIPS_INPUT_URL: 请输入URL
TIPS_HTTP_PREFIX: http://或者https://开头
TIPS_INPUT_VALID_URL: 请输入合法的URL
# plugins
PLUGIN_SEARCH_PLACEHOLDER: 搜索npm上的PicGo插件,或者点击上方按钮查看优秀插件列表
PLUGIN_INSTALL: 安装
PLUGIN_INSTALLING: 安装中
PLUGIN_INSTALLED: 已安装
PLUGIN_DOING_SOMETHING: 进行中
PLUGIN_LIST: 插件列表
PLUGIN_IMPORT_LOCAL: 导入本地插件
# tips
TIPS_REMOVE_LINK: 此操作将把该图片移出相册, 是否继续?
TIPS_WILL_REMOVE_CHOOSED_IMAGES: 将在相册中移除刚才选中的 ${m} 张图片,是否继续?
TIPS_MUST_CONTAINS_URL: 必须含有$url 或 $fileName 或 $extName
TIPS_NETWORK_ERROR: 网络错误暂时无法获取
TIPS_NEED_RELOAD: 需要重启生效
TIPS_PLEASE_CHOOSE_LOG_LEVEL: 请选择日志记录等级
TIPS_SET_SUCCEED: 设置成功
TIPS_PLUGIN_NOT_GUI_IMPLEMENT: 该插件未对可视化界面进行优化, 是否继续安装?
TIPS_CLICK_NOTIFICATION_TO_RELOAD: 请点击此通知重启应用以生效
TIPS_GET_PLUGIN_LIST_FAILED: 获取插件列表失败
# ---renderer i18n end---
# plugins
PLUGIN_INSTALL_SUCCEED: 插件安装成功
PLUGIN_INSTALL_FAILED: 插件安装失败
PLUGIN_UNINSTALL_SUCCEED: 插件卸载成功
PLUGIN_UNINSTALL_FAILED: 插件卸载失败
PLUGIN_UPDATE_SUCCEED: 插件更新成功
PLUGIN_UPDATE_FAILED: 插件更新失败
PLUGIN_IMPORT_SUCCEED: 插件导入成功
PLUGIN_IMPORT_FAILED: 插件导入失败
ENABLE_PLUGIN: 启用插件
DISABLE_PLUGIN: 禁用插件
UNINSTALL_PLUGIN: 卸载插件
UPDATE_PLUGIN: 更新插件
# toolbox
TOOLBOX: 工具箱
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_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_GALLERY_FILE_BROKEN_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_NOT_EXIST_TIPS: 剪贴板图片临时文件夹不存在:${path}
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: 请自行创建文件夹:${path}
# tips
TIPS_NOTICE: 注意
TIPS_WARNING: 警告
TIPS_ERROR: 发生错误
TIPS_INSTALL_NODE_AND_RELOAD_PICGO: 请安装Node.js并重启PicGo再继续操作
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: 有插件正在试图删除一些相册图片,是否继续
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_CUSTOM_CONFIG_FILE_PATH_ERROR: 自定义文件解析出错,请检查路径内容是否正确
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷键已经修改成功
TIPS_SHORTCUT_MODIFIED_CONFLICT: 快捷键冲突,请重新设置
TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED: 自定义链接格式已经修改成功
TIPS_FIND_NEW_VERSION: 发现新版本${v},更新了很多功能,是否去下载最新的版本?
# privacy
PRIVACY: >
本软件尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更优质的服务,本软件会按照本隐私权政策的规定使用和收集您的一些行为信息。您在同意本软件服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本软件服务使用协议不可分割的一部分,如果不同意将无法使用。本协议会定期更新。
1.适用范围
a)在您使用本软件时,本软件会记录的您对本软件的一些操作行为信息,包括但不限于您使用本软件进行文件上传的耗时、类型、数量等信息。
2.信息的使用
a)在获得您的使用数据之后,本软件会将其上传至数据分析服务器,以便分析数据后,提供给您更好的服务。
3.信息披露
a)本软件不会将您的信息披露给不受信任的第三方。
b)根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露;
c)如您出现违反中国有关法律、法规或者相关规则的情况,需要向第三方披露;
4.信息安全
a)本软件不会收集您的个人信息、密钥信息等隐私信息,所收集的信息仅仅作为改善软件、优化体验、了解软件日活等用途。
PRIVACY_TIPS: 请同意隐私协议,否则无法上传。
QUIT: 退出
+285
View File
@@ -0,0 +1,285 @@
LANG_DISPLAY_LABEL: 繁體中文
ABOUT: 關於
OPEN_MAIN_WINDOW: 打開主視窗
CHOOSE_DEFAULT_PICBED: 選擇預設圖床
OPEN_UPDATE_HELPER: 開啟更新助手
PRIVACY_AGREEMENT: 隱私協議
RELOAD_APP: 重啟程式
UPLOAD_SUCCEED: 上傳成功
UPLOAD_FAILED: 上傳失敗
UPLOAD_PROGRESS: 上傳進度
OPERATION_SUCCEED: 操作成功
OPERATION_FAILED: 操作失敗
UPLOADING: 正在上傳
QUICK_UPLOAD: 快速上傳
UPLOAD_BY_CLIPBOARD: 剪貼簿圖片上傳
HIDE_WINDOW: 隱藏視窗
SPONSOR_PICGO: 贊助 PicGo
SHOW_PICBED_QRCODE: 產生圖床配置 QRCODE
PICBED_QRCODE: 圖床配置 QRCODE
ENABLE: 啟用
DISABLE: 禁用
CONFIG_THING: 設定${c}
FIND_NEW_VERSION: 發現新版本
NO_MORE_NOTICE: 以後不再提醒
SHOW_DEVTOOLS: 開啟開發者工具
CURRENT_PICBED: 當前圖床
OPEN_TOOLBOX: 開啟修復工具箱
# ---renderer i18n begin---
CHOOSE_YOUR_DEFAULT_PICBED: 選擇 ${d} 作為你的預設圖床:
UPLOAD_AREA: 上傳區
GALLERY: 相簿
PICBEDS_SETTINGS: 圖床設定
PICGO_SETTINGS: PicGo設定
PLUGIN_SETTINGS: 插件設定
PICGO_SPONSOR_TEXT: PicGo是開放原始碼的軟體,如果你喜歡它,對你有幫助,不妨請我喝杯咖啡?
ALIPAY: 支付寶
WECHATPAY: 微信支付
CHOOSE_PICBED: 選擇圖床
COPY_PICBED_CONFIG: 複製圖床設定
COPY_PICBED_CONFIG_SUCCEED: 複製圖床設定成功
INPUT: 輸入框
CANCEL: 取消
CONFIRM: 確定
CHOOSE_SHOWED_PICBED: 請選擇顯示的圖床
CHOOSE_PASTE_FORMAT: 請選擇貼上的格式
SEARCH: 搜尋
COPY: 複製
DELETE: 刪除
SELECT_ALL: 全選
CHANGE_IMAGE_URL: 修改圖片URL
CHANGE_IMAGE_URL_SUCCEED: 修改圖片URL成功
COPY_LINK_SUCCEED: 複製連結成功
BATCH_COPY_LINK_SUCCEED: 批量複製連結成功
FILE_RENAME: 檔案改名
COPY_FILE_PATH: 複製檔案路徑
OPEN_FILE_PATH: 打開檔案路徑
SUCCESS: 成功
FAILED: 失敗
# settings
SETTINGS: 設定
SETTINGS_OPEN_CONFIG_FILE: 打開設定檔案
SETTINGS_CLICK_TO_OPEN: 點擊打開
SETTINGS_SET_LOG_FILE: 設定記錄檔案
SETTINGS_CLICK_TO_SET: 點擊設定
SETTINGS_CLICK_TO_CHECK: 點擊檢查
SETTINGS_SET_SHORTCUT: 設定快捷鍵
SETTINGS_CUSTOM_LINK_FORMAT: 自訂連結格式
SETTINGS_SET_PROXY_AND_MIRROR: 設定PROXY和鏡像地址
SETTINGS_SET_SERVER: 設定Server
SETTINGS_CHECK_UPDATE: 檢查更新
SETTINGS_OPEN_UPDATE_HELPER: 打開更新助手
SETTINGS_OPEN:
SETTINGS_CLOSE:
SETTINGS_ACCEPT_BETA_UPDATE: 接受Beta版本更新
SETTINGS_LAUNCH_ON_BOOT: 開機時啟動
SETTINGS_RENAME_BEFORE_UPLOAD: 上傳前重新命名
SETTINGS_TIMESTAMP_RENAME: 以時間戳命名
SETTINGS_OPEN_UPLOAD_TIPS: 開啟上傳提示
SETTINGS_MINI_WINDOW_ON_TOP: Mini視窗置頂
SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD: 上傳後自動複製URL
SETTINGS_TIPS_PLACEHOLDER_URL: 用佔位符 $url 來表示URL的位置
SETTINGS_TIPS_PLACEHOLDER_FILENAME: 用佔位符 $fileName 來表示檔案名稱的位置
SETTINGS_TIPS_PLACEHOLDER_EXTNAME: 用佔位符 $extName 來表示檔案格式的位置
SETTINGS_TIPS_SUCH_AS:
SETTINGS_UPLOAD_PROXY: 上傳PROXY
SETTINGS_PLUGIN_INSTALL_PROXY: 插件安裝PROXY
SETTINGS_PLUGIN_INSTALL_MIRROR: 插件安裝鏡像
SETTINGS_CURRENT_VERSION: 當前版本
SETTINGS_NEWEST_VERSION: 最新版本
SETTINGS_GETING: 正在取得中
SETTINGS_TIPS_HAS_NEW_VERSION: PicGo更新啦,請點擊確定開啟下載頁面
SETTINGS_LOG_FILE: 記錄檔案
SETTINGS_LOG_LEVEL: 記錄等级
SETTINGS_LOG_FILE_SIZE: 記錄檔案大小
SETTINGS_SET_PICGO_SERVER: 設定PicGo-Server
SETTINGS_TIPS_SERVER_NOTICE: 如果你不知道Server的作用,請閱讀文檔,或者不用修改設定。
SETTINGS_ENABLE_SERVER: 是否開啟Server
SETTINGS_SET_SERVER_HOST: 設定監聽地址
SETTINGS_SET_SERVER_PORT: 設定監聽端口
SETTINGS_TIP_PLACEHOLDER_HOST: 推薦預設地址:127.0.0.1
SETTINGS_TIP_PLACEHOLDER_PORT: 推薦預設端口:36677
SETTINGS_LOG_LEVEL_ALL: 全部-All
SETTINGS_LOG_LEVEL_SUCCESS: 成功-Success
SETTINGS_LOG_LEVEL_ERROR: 錯誤-Error
SETTINGS_LOG_LEVEL_INFO: 普通-Info
SETTINGS_LOG_LEVEL_WARN: 提醒-Warn
SETTINGS_LOG_LEVEL_NONE: 不記錄-None
SETTINGS_RESULT: 設定結果
SETTINGS_DEFAULT_PICBED: 設定預設圖床
SETTINGS_SET_DEFAULT_PICBED: 設為預設圖床
SETTINGS_NOT_CONFIG_OPTIONS: 暫無設定選項
SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD: 使用內建剪貼簿上傳
SETTINGS_CHOOSE_LANGUAGE: 選擇語言
BUILTIN_CLIPBOARD_TIPS: 使用內建剪貼簿函數而不是調用腳本取得剪貼簿內的照片
UPLOADER_CONFIG_NAME: 圖床配置名
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
SHORTCUT_NAME: 快捷鍵名稱
SHORTCUT_BIND: 快捷鍵綁定
SHORTCUT_STATUS: 狀態
SHORTCUT_ENABLED: 已啟用
SHORTCUT_DISABLED: 已禁用
SHORTCUT_SOURCE: 來源
SHORTCUT_HANDLE: 操作
SHORTCUT_ENABLE: 啟用
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: 等待上傳
ALREADY_UPLOAD: 已上傳
# upload-page
PICTURE_UPLOAD: 圖片上傳
DRAG_FILE_TO_HERE: 將檔案拖曳到此處,或
CLICK_TO_UPLOAD: 點擊上傳
LINK_FORMAT: 連結格式
CUSTOM: 自訂
CLIPBOARD_PICTURE: 剪貼簿圖片
TIPS_DRAG_VALID_PICTURE_OR_URL: 請拖入合法的圖片檔案或者圖片URL地址
TIPS_INPUT_URL: 請輸入URL
TIPS_HTTP_PREFIX: http://或者https://開頭
TIPS_INPUT_VALID_URL: 請輸入合法的URL
# plugins
PLUGIN_SEARCH_PLACEHOLDER: 搜尋npm上的PicGo插件,或者點擊上方按鈕查看優秀插件列表
PLUGIN_INSTALL: 安裝
PLUGIN_INSTALLING: 安裝中
PLUGIN_INSTALLED: 已安裝
PLUGIN_DOING_SOMETHING: 進行中
PLUGIN_LIST: 插件列表
PLUGIN_IMPORT_LOCAL: 導入本地插件
# tips
TIPS_REMOVE_LINK: 此操作將在相簿中移除該圖片,是否繼續?
TIPS_WILL_REMOVE_CHOOSED_IMAGES: 將在相簿中移除剛才選中的 ${m} 張圖片,是否繼續?
TIPS_MUST_CONTAINS_URL: 必須含有$url 或 $fileName 或 $extName
TIPS_NETWORK_ERROR: 網路錯誤,暫時無法取得
TIPS_NEED_RELOAD: 需要重新啟動生效
TIPS_PLEASE_CHOOSE_LOG_LEVEL: 請選擇記錄等級
TIPS_SET_SUCCEED: 設定成功
TIPS_PLUGIN_NOT_GUI_IMPLEMENT: 該插件未對GUI進行優化,是否繼續安裝?
TIPS_CLICK_NOTIFICATION_TO_RELOAD: 請點擊此通知重新啟動程式以生效
TIPS_GET_PLUGIN_LIST_FAILED: 取得插件列表失敗
# ---renderer i18n end---
# plugins
PLUGIN_INSTALL_SUCCEED: 插件安裝成功
PLUGIN_INSTALL_FAILED: 插件安裝失敗
PLUGIN_UNINSTALL_SUCCEED: 插件卸載成功
PLUGIN_UNINSTALL_FAILED: 插件卸載失敗
PLUGIN_UPDATE_SUCCEED: 插件更新成功
PLUGIN_UPDATE_FAILED: 插件更新失敗
PLUGIN_IMPORT_SUCCEED: 插件導入成功
PLUGIN_IMPORT_FAILED: 插件導入失敗
ENABLE_PLUGIN: 啟用插件
DISABLE_PLUGIN: 禁用插件
UNINSTALL_PLUGIN: 卸載插件
UPDATE_PLUGIN: 更新插件
# toolbox
TOOLBOX: 工具箱
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_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_GALLERY_FILE_BROKEN_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_NOT_EXIST_TIPS: 剪貼板圖片臨時文件夾不存在:${path}
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: 請自行創建文件夾:${path}
# tips
TIPS_NOTICE: 注意
TIPS_WARNING: 警告
TIPS_ERROR: 發生錯誤
TIPS_INSTALL_NODE_AND_RELOAD_PICGO: 請安裝Node.js並重新啟動PicGo再繼續操作
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: 有插件正在試圖刪除一些相簿圖片,是否繼續?
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_CUSTOM_CONFIG_FILE_PATH_ERROR: 自訂設定檔案解析出錯,請檢查路徑內容是否正確
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷鍵已經修改成功
TIPS_SHORTCUT_MODIFIED_CONFLICT: 快捷鍵衝突,請重新設定
TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED: 自訂連結格式已經修改成功
TIPS_FIND_NEW_VERSION: 發現新版本${v},更新了很多功能,是否去下載最新的版本?
# privacy
PRIVACY: >
本軟體尊重並保護所有使用服務用戶的個人隱私權。為了給您提供更準確、更優質的服務,本軟體會按照本隱私權政策的規定使用和收集您的一些行為信息。您在同意本軟體服務使用協議之時,即視為您已經同意本隱私權政策全部內容。本隱私權政策屬於本軟體服務使用協議不可分割的一部分,如果不同意將無法使用。本協議會定期更新。
1.適用範圍
a)在您使用本軟體時,本軟體會記錄的您對本軟體的一些操作行為信息,包括但不限於您使用本軟體進行文件上傳的耗時、類型、數量等信息。
2.信息的使用
a)在獲得您的使用數據之後,本軟體會將其上傳至數據分析服務器,以便分析數據後,提供給您更好的服務。
3.信息披露
a)本軟體不會將您的信息披露給不受信任的第三方。
b)根據法律的有關規定,或者行政或司法機構的要求,向第三方或者行政、司法機構披露;
c)如您出現違反中國有關法律、法規或者相關規則的情況,需要向第三方披露;
4.信息安全
a)本軟體不會收集您的個人信息、密鑰信息等隱私信息,所收集的信息僅僅作為改善軟體、優化體驗、了解軟體日活等用途。
PRIVACY_TIPS: 請同意隱私協議,否則無法上傳。
QUIT: 退出
-1
View File
@@ -6,7 +6,6 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>PicGo</title>
</head>
<body>
<noscript>
+29 -13
View File
@@ -1,18 +1,34 @@
#!/bin/sh
# require xclip(see http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script/677212#677212)
command -v xclip >/dev/null 2>&1 || { echo >&1 "no xclip"; exit 1; }
# write image in clipboard to file (see http://unix.stackexchange.com/questions/145131/copy-image-from-clipboard-to-file)
filePath=`xclip -selection clipboard -o 2>/dev/null | grep ^file:// | cut -c8-`
if [ ! -n "$filePath" ] ;then
if
xclip -selection clipboard -target image/png -o >/dev/null 2>&1
then
xclip -selection clipboard -target image/png -o >$1 2>/dev/null
echo $1
if [ "$XDG_SESSION_TYPE" = "x11" ]; then
# require xclip(see http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script/677212#677212)
command -v xclip >/dev/null 2>&1 || { echo >&1 "no xclip"; exit 1; }
# write image in clipboard to file (see http://unix.stackexchange.com/questions/145131/copy-image-from-clipboard-to-file)
filePath=`xclip -selection clipboard -o 2>/dev/null | grep ^file:// | cut -c8-`
if [ ! -n "$filePath" ] ;then
if
xclip -selection clipboard -target image/png -o >/dev/null 2>&1
then
xclip -selection clipboard -target image/png -o >$1 2>/dev/null
echo $1
else
echo "no image"
fi
else
echo "no image"
echo $filePath
fi
elif [ "$XDG_SESSION_TYPE" = "wayland" ]; then
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 "no image"
exit 1
fi
else
echo $filePath
# 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
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+66
View File
@@ -0,0 +1,66 @@
// different platform has different format
// macos (dmg, x64 + arm64)
const darwin = [{
appNameWithPrefix: 'PicGo',
ext: 'dmg',
arch: 'arm64',
'version-file': 'latest-mac.yml'
}, {
appNameWithPrefix: 'PicGo',
ext: 'dmg',
arch: 'x64',
'version-file': 'latest-mac.yml'
}]
// linux (AppImage, deb, snap)
const linux = [{
appNameWithPrefix: 'PicGo',
ext: 'AppImage',
arch: 'arm64',
'version-file': 'latest-linux-arm64.yml'
}, {
appNameWithPrefix: 'PicGo',
ext: 'AppImage',
arch: 'x86_64',
'version-file': 'latest-linux.yml'
}, {
appNameWithPrefix: 'PicGo',
ext: 'deb',
arch: 'arm64',
'version-file': 'latest-linux-arm64.yml'
}, {
appNameWithPrefix: 'PicGo',
ext: 'deb',
arch: 'amd64',
'version-file': 'latest-linux.yml'
}, {
appNameWithPrefix: 'PicGo',
ext: 'snap',
arch: 'amd64',
'version-file': 'latest-linux.yml'
}]
// windows (nsis, x64 + ia32 + arm64)
const win32 = [{
appNameWithPrefix: 'PicGo',
ext: 'exe',
arch: 'ia32',
'version-file': 'latest.yml'
}, {
appNameWithPrefix: 'PicGo',
ext: 'exe',
arch: 'x64',
'version-file': 'latest.yml'
}, {
appNameWithPrefix: 'PicGo',
ext: 'exe',
arch: 'arm64',
'version-file': 'latest.yml'
}]
module.exports = {
darwin,
linux,
win32
}
+39
View File
@@ -0,0 +1,39 @@
const pkg = require('../package.json')
const version = pkg.version
const generateURL = (arch, ext, prefix = 'PicGo-') => `https://release.picgo.app/${version}/${prefix}${version}${arch}${ext}`
const windows = [
{ label: '32 bit', arch: '-ia32', ext: '.exe' },
{ label: '64 bit', arch: '-x64', ext: '.exe' },
{ label: 'ARM64', arch: '-arm64', ext: '.exe' }
]
const macos = [
{ label: 'Intel', arch: '-x64', ext: '.dmg' },
{ label: 'Apple Silicon', arch: '-arm64', ext: '.dmg' }
]
const linux = {
AppImage: [
{ label: '64 bit', arch: '-x64', ext: '.AppImage' },
{ label: 'ARM64', arch: '-arm64', ext: '.AppImage' }
],
Deb: [
{ label: '64 bit', arch: '-x64', ext: '.deb' },
{ label: 'ARM64', arch: '-arm64', ext: '.deb' }
],
Snap: [
{ label: '64 bit', arch: '-x64', ext: '.snap' }
]
}
const renderLine = (items) => items.map(({ label, arch, ext }) => `[${label}](${generateURL(arch, ext)})`).join(' | ')
const sections = [
`### Windows\n- ${renderLine(windows)}`,
`### macOS\n- ${renderLine(macos)}`,
`### Linux\n- AppImage: ${renderLine(linux.AppImage)}\n- Deb: ${renderLine(linux.Deb)}\n- Snap: ${renderLine(linux.Snap)}`
]
console.log(sections.join('\n\n'))
+22
View File
@@ -0,0 +1,22 @@
const yaml = require('js-yaml')
const path = require('path')
const fs = require('fs')
const languageFileName = 'zh-CN.yml' // use zh-CN for type is OK
const i18nFolder = path.join(__dirname, '../public/i18n')
const typeFolder = path.join(__dirname, '../src/universal/types')
const languageFile = path.join(i18nFolder, languageFileName)
const langFile = fs.readFileSync(languageFile, 'utf8')
const obj = yaml.load(langFile)
const keys = Object.keys(obj)
const types =
`interface ILocales {
${keys.map(key => `${key}: string`).join('\n ')}
}
type ILocalesKey = keyof ILocales
`
fs.writeFileSync(path.join(typeFolder, 'i18n.d.ts'), types)
+208
View File
@@ -0,0 +1,208 @@
// upload dist bundled-app to r2
// upload version file to cos
require('dotenv').config()
// const crypto = require('crypto')
// const axios = require('axios').default
const fs = require('fs')
const pkg = require('../package.json')
const configList = require('./config')
const mime = require('mime-types')
const path = require('path')
const distPath = path.join(__dirname, '../dist')
const S3Client = require('@aws-sdk/client-s3').S3Client
const Upload = require('@aws-sdk/lib-storage').Upload
// const BUCKET = 'picgo-1251750343'
// const COS_SECRET_ID = process.env.PICGO_ENV_COS_SECRET_ID
// const COS_SECRET_KEY = process.env.PICGO_ENV_COS_SECRET_KEY
const S3_BUCKET = 'release'
const S3_LEGACY_BUCKET = 'picgo'
// const AREA = 'ap-chengdu'
const VERSION = pkg.version
const FILE_PATH = `${VERSION}/`
const S3_SECRET_ID = process.env.PICGO_ENV_S3_SECRET_ID
const S3_SECRET_KEY = process.env.PICGO_ENV_S3_SECRET_KEY
const S3_ACCOUNT_ID = process.env.PICGO_ENV_S3_ACCOUNT_ID
const S3_LEGACY_SECRET_ID = process.env.PICGO_ENV_S3_LEGACY_SECRET_ID
const S3_LEGACY_SECRET_KEY = process.env.PICGO_ENV_S3_LEGACY_SECRET_KEY
const S3_LEGACY_ACCOUNT_ID = process.env.PICGO_ENV_S3_LEGACY_ACCOUNT_ID
const S3Options = {
credentials: {
accessKeyId: S3_SECRET_ID,
secretAccessKey: S3_SECRET_KEY
},
endpoint: `https://${S3_ACCOUNT_ID}.r2.cloudflarestorage.com`,
sslEnabled: true,
region: 'auto'
}
// for legacy release file fetch
const S3LegacyOptions = {
credentials: {
accessKeyId: S3_LEGACY_SECRET_ID,
secretAccessKey: S3_LEGACY_SECRET_KEY
},
endpoint: `https://${S3_LEGACY_ACCOUNT_ID}.r2.cloudflarestorage.com`,
sslEnabled: true,
region: 'auto'
}
// https://cloud.tencent.com/document/product/436/7778#signature
// /**
// * @param {string} fileName
// * @returns
// */
// const generateSignature = (fileName, folder = FILE_PATH) => {
// const secretKey = COS_SECRET_ID
// // const area = AREA
// const bucket = BUCKET
// const path = folder
// const today = Math.floor(new Date().getTime() / 1000)
// const tomorrow = today + 86400
// const signTime = `${today};${tomorrow}`
// const signKey = crypto.createHmac('sha1', secretKey).update(signTime).digest('hex')
// const httpString = `put\n/${path}${fileName}\n\nhost=${bucket}.cos.accelerate.myqcloud.com\n`
// const sha1edHttpString = crypto.createHash('sha1').update(httpString).digest('hex')
// const stringToSign = `sha1\n${signTime}\n${sha1edHttpString}\n`
// const signature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex')
// return {
// signature,
// signTime
// }
// }
// /**
// *
// * @param {string} fileName
// * @param {Buffer} fileBuffer
// * @param {{ signature: string, signTime: string }} signature
// * @returns
// */
// const getReqOptions = (fileName, fileBuffer, signature, folder = FILE_PATH) => {
// return {
// method: 'PUT',
// url: `http://${BUCKET}.cos.accelerate.myqcloud.com/${encodeURI(folder)}${encodeURI(fileName)}`,
// headers: {
// Host: `${BUCKET}.cos.accelerate.myqcloud.com`,
// Authorization: `q-sign-algorithm=sha1&q-ak=${COS_SECRET_KEY}&q-sign-time=${signature.signTime}&q-key-time=${signature.signTime}&q-header-list=host&q-url-param-list=&q-signature=${signature.signature}`,
// contentType: mime.lookup(fileName),
// useAgent: `PicGo;${pkg.version};null;null`
// },
// maxContentLength: Infinity,
// maxBodyLength: Infinity,
// data: fileBuffer,
// resolveWithFullResponse: true
// }
// }
/**
* a backup for version file
*/
// const uploadVersionFile = async () => {
// try {
// const platform = process.platform
// if (configList[platform]) {
// let versionFileHasUploaded = false
// for (const [, config] of configList[platform].entries()) {
// const versionFilePath = path.join(distPath, config['version-file'])
// let versionFileName = config['version-file']
// if (VERSION.toLocaleLowerCase().includes('beta')) {
// versionFileName = versionFileName.replace('.yml', '.beta.yml')
// }
// // upload version file
// if (!versionFileHasUploaded) {
// const signature = generateSignature(versionFileName, '')
// const reqOptions = getReqOptions(versionFileName, fs.readFileSync(versionFilePath), signature, '')
// console.log('[PicGo Version File] Uploading...', versionFileName)
// await axios.request(reqOptions)
// versionFileHasUploaded = true
// }
// }
// } else {
// console.warn('platform not supported!', platform)
// }
// } catch (e) {
// console.error(e)
// }
// }
const uploadDist = async () => {
try {
const platform = process.platform
if (configList[platform]) {
const uploadedVersionFiles = new Set()
for (const [index, config] of configList[platform].entries()) {
const fileName = `${config.appNameWithPrefix}-${VERSION}-${config.arch}.${config.ext}`
const filePath = path.join(distPath, fileName)
const versionFilePath = path.join(distPath, config['version-file'])
let versionFileName = config['version-file']
if (VERSION.toLocaleLowerCase().includes('beta')) {
versionFileName = versionFileName.replace('.yml', '.beta.yml')
}
console.log('[PicGo Dist] Preparing to upload', fileName)
const client = new S3Client(S3Options)
if (fs.existsSync(filePath)) {
const uploadDistToS3 = new Upload({
client,
params: {
Bucket: S3_BUCKET,
Key: `${FILE_PATH}${fileName}`,
Body: fs.createReadStream(filePath),
ContentType: 'application/octet-stream'
}
})
// upload dist file
console.log('[PicGo Dist] Uploading...', fileName, `${index + 1}/${configList[platform].length}`)
uploadDistToS3.on('httpUploadProgress', progress => {
console.log(`[PicGo Dist] Uploading... ${progress.loaded}/${progress.total}`)
})
await uploadDistToS3.done()
} else {
console.warn('[PicGo Dist] File not found:', fileName)
}
// upload version file
if (!uploadedVersionFiles.has(versionFileName) && fs.existsSync(versionFilePath)) {
const uploadVersionFileToS3 = new Upload({
client,
params: {
Bucket: S3_BUCKET,
Key: `${versionFileName}`,
Body: fs.createReadStream(versionFilePath),
ContentType: mime.lookup(versionFileName)
}
})
// upload to legacy bucket as well
// will be deprecated in 2.5.0
const legacyClient = new S3Client(S3LegacyOptions)
const uploadVersionFileToLegacyS3 = new Upload({
client: legacyClient,
params: {
Bucket: S3_LEGACY_BUCKET,
Key: `${versionFileName}`,
Body: fs.createReadStream(versionFilePath),
ContentType: mime.lookup(versionFileName)
}
})
console.log('[PicGo Version File] Uploading...', versionFileName)
await uploadVersionFileToS3.done()
await uploadVersionFileToLegacyS3.done()
uploadedVersionFiles.add(versionFileName)
console.log('[PicGo Version File] Upload successfully')
}
}
} else {
console.warn('platform not supported!', platform)
}
} catch (e) {
console.error(e)
}
}
const main = async () => {
await uploadDist()
}
main()
+2
View File
@@ -1,5 +1,7 @@
import { initStaticPath } from '~/main/utils/env'
import { bootstrap } from '~/main/lifeCycle'
initStaticPath()
bootstrap.launchApp()
/**
-39
View File
@@ -1,39 +0,0 @@
import Vue from 'vue'
import App from './renderer/App.vue'
import router from './renderer/router'
import ElementUI from 'element-ui'
import { webFrame } from 'electron'
import 'element-ui/lib/theme-chalk/index.css'
import VueLazyLoad from 'vue-lazyload'
import axios from 'axios'
import mainMixin from './renderer/utils/mainMixin'
import bus from '@/utils/bus'
import { initTalkingData } from './renderer/utils/analytics'
import db from './renderer/utils/db'
webFrame.setVisualZoomLevelLimits(1, 1)
Vue.config.productionTip = false
Vue.prototype.$builtInPicBed = [
'smms',
'imgur',
'qiniu',
'tcyun',
'upyun',
'aliyun',
'github'
]
Vue.prototype.$$db = db
Vue.prototype.$http = axios
Vue.prototype.$bus = bus
Vue.use(ElementUI)
Vue.use(VueLazyLoad)
Vue.mixin(mainMixin)
new Vue({
router,
render: h => h(App)
}).$mount('#app')
initTalkingData()
+199
View File
@@ -0,0 +1,199 @@
// get notice from remote
// such as some notices for users; some updates for users
import fs from 'fs-extra'
import { app, clipboard, dialog, shell } from 'electron'
import { IRemoteNoticeActionType, IRemoteNoticeTriggerCount, IRemoteNoticeTriggerHook } from '#/types/enum'
import { lte, gte } from 'semver'
import path from 'path'
import axios from 'axios'
import windowManager from '../window/windowManager'
import { showNotification } from '~/main/utils/common'
import { isDev } from '~/universal/utils/common'
// for test
const REMOTE_NOTICE_URL = isDev ? 'http://localhost:8181/remote-notice.json' : 'https://picgo-1251750343.cos.accelerate.myqcloud.com/remote-notice.yml'
const REMOTE_NOTICE_LOCAL_STORAGE_FILE = 'picgo-remote-notice.json'
const STORE_PATH = app.getPath('userData')
const REMOTE_NOTICE_LOCAL_STORAGE_PATH = path.join(STORE_PATH, REMOTE_NOTICE_LOCAL_STORAGE_FILE)
class RemoteNoticeHandler {
private remoteNotice: IRemoteNotice | null = null
private remoteNoticeLocalCountStorage: IRemoteNoticeLocalCountStorage | null = null
async init () {
this.remoteNotice = await this.getRemoteNoticeInfo()
this.initLocalCountStorage()
}
private initLocalCountStorage () {
const localCountStorage = {}
if (!fs.existsSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH)) {
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({}))
}
try {
const localCountStorage: IRemoteNoticeLocalCountStorage = fs.readJSONSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, 'utf8')
this.remoteNoticeLocalCountStorage = localCountStorage
} catch (e) {
console.log(e)
this.remoteNoticeLocalCountStorage = localCountStorage
}
}
private saveLocalCountStorage (newData?: IRemoteNoticeLocalCountStorage) {
if (newData) {
this.remoteNoticeLocalCountStorage = newData
}
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify(this.remoteNoticeLocalCountStorage))
}
private async getRemoteNoticeInfo (): Promise<IRemoteNotice | null> {
try {
const noticeInfo = await axios({
method: 'get',
url: REMOTE_NOTICE_URL,
responseType: 'json'
}).then(res => res.data) as IRemoteNotice
return noticeInfo
} catch {
return null
}
}
/**
* if the notice is not shown or is always shown, then show the notice
* @param action
*/
private checkActionCount (action: IRemoteNoticeAction) {
try {
if (!this.remoteNoticeLocalCountStorage) {
return true
}
const actionCount = this.remoteNoticeLocalCountStorage[action.id]
if (actionCount === undefined) {
if (action.triggerCount === IRemoteNoticeTriggerCount.ALWAYS) {
this.remoteNoticeLocalCountStorage[action.id] = 1 // if always, count number
} else {
this.remoteNoticeLocalCountStorage[action.id] = true
}
return true
} else {
// here is the count of action
// if not always show, then can't show
if (action.triggerCount !== IRemoteNoticeTriggerCount.ALWAYS) {
return false
} else {
const preCount = this.remoteNoticeLocalCountStorage[action.id]
if (typeof preCount !== 'number') {
this.remoteNoticeLocalCountStorage[action.id] = true
return true
} else {
this.remoteNoticeLocalCountStorage[action.id] = preCount + 1
}
return true
}
}
} finally {
this.saveLocalCountStorage()
}
}
private async doActions (actions: IRemoteNoticeAction[]) {
for (const action of actions) {
if (this.checkActionCount(action)) {
switch (action.type) {
case IRemoteNoticeActionType.SHOW_DIALOG: {
// SHOW DIALOG
const currentWindow = windowManager.getAvailableWindow()
dialog.showOpenDialog(currentWindow, action.data?.options)
break
}
case IRemoteNoticeActionType.SHOW_NOTICE:
showNotification({
title: action.data?.title || '',
body: action.data?.content || '',
clickToCopy: !!action.data?.copyToClipboard,
copyContent: action.data?.copyToClipboard || '',
clickFn () {
if (action.data?.url) {
shell.openExternal(action.data.url)
}
}
})
break
case IRemoteNoticeActionType.OPEN_URL:
// OPEN URL
shell.openExternal(action.data?.url || '')
break
case IRemoteNoticeActionType.COMMON:
// DO COMMON CASE
if (action.data?.copyToClipboard) {
clipboard.writeText(action.data.copyToClipboard)
}
if (action.data?.url) {
shell.openExternal(action.data.url)
}
break
case IRemoteNoticeActionType.SHOW_MESSAGE_BOX: {
const currentWindow = windowManager.getAvailableWindow()
dialog.showMessageBox(currentWindow, {
title: action.data?.title || '',
message: action.data?.content || '',
type: 'info',
buttons: action.data?.buttons?.map(item => item.label) || ['Yes']
}).then(res => {
const button = action.data?.buttons?.[res.response]
if (button?.type === 'cancel') {
// do nothing
} else {
if (button?.action) {
this.doActions([button?.action])
}
}
})
break
}
}
}
}
}
triggerHook (hook: IRemoteNoticeTriggerHook) {
if (!this.remoteNotice || !this.remoteNotice.list) {
return
}
const actions = this.remoteNotice.list
.filter(item => {
if (item.versionMatch) {
switch (item.versionMatch) {
case 'exact':
return item.versions.includes(app.getVersion())
case 'gte':
return item.versions.some(version => {
// appVersion >= version
return gte(app.getVersion(), version)
})
case 'lte':
return item.versions.some(version => {
// appVersion <= version
return lte(app.getVersion(), version)
})
}
}
return item.versions.includes(app.getVersion())
})
.map(item => item.actions)
.reduce((pre, cur) => pre.concat(cur), [])
.filter(item => item.hooks.includes(hook))
this.doActions(actions)
}
}
const remoteNoticeHandler = new RemoteNoticeHandler()
export {
remoteNoticeHandler
}
+6
View File
@@ -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
}
+19 -5
View File
@@ -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}]`)
}
}
@@ -177,7 +191,7 @@ class ShortKeyHandler {
keyList.forEach(item => {
globalShortcut.unregister(item.key)
shortKeyService.unregisterCommand(item.command)
picgo.unsetConfig('settings.shortKey', item.command)
db.unset('settings.shortKey', item.command)
})
}
}
+117 -78
View File
@@ -4,44 +4,32 @@ import {
Tray,
dialog,
clipboard,
systemPreferences,
Notification
Notification,
nativeTheme
} from 'electron'
import uploader from 'apis/app/uploader'
import getPicBeds from '~/main/utils/getPicBeds'
import db, { GalleryDB } from '~/main/apis/core/datastore'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import picgo from '@core/picgo'
import { IWindowList } from '#/types/enum'
import pasteTemplate from '~/main/utils/pasteTemplate'
import pkg from 'root/package.json'
import { handleCopyUrl } from '~/main/utils/common'
import { ensureFilePath, getClipboardFilePathList, handleCopyUrl } from '~/main/utils/common'
import { privacyManager } from '~/main/utils/privacyManager'
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'
import { getStaticPath } from '#/utils/staticPath'
let contextMenu: Menu | null
let menu: Menu | null
let tray: Tray | null
// need to build new menu
export function createContextMenu () {
const picBeds = getPicBeds()
if (process.platform === 'darwin' || process.platform === 'win32') {
const submenu = picBeds.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: db.get('picBed.current') === item.type,
click () {
picgo.saveConfig({
'picBed.current': item.type,
'picBed.uploader': item.type
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
}
}
})
const submenu = buildPicBedListMenu()
contextMenu = Menu.buildFromTemplate([
{
label: '关于',
label: T('ABOUT'),
click () {
dialog.showMessageBox({
title: 'PicGo',
@@ -51,7 +39,7 @@ export function createContextMenu () {
}
},
{
label: '打开详细窗口',
label: T('OPEN_MAIN_WINDOW'),
click () {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
settingWindow!.show()
@@ -62,14 +50,14 @@ export function createContextMenu () {
}
},
{
label: '选择默认图床',
label: T('CHOOSE_DEFAULT_PICBED'),
type: 'submenu',
// @ts-ignore
submenu
},
// @ts-ignore
{
label: '打开更新助手',
label: T('OPEN_UPDATE_HELPER'),
type: 'checkbox',
checked: db.get('settings.showUpdateTip'),
click () {
@@ -78,13 +66,13 @@ export function createContextMenu () {
}
},
{
label: '隐私协议',
label: T('PRIVACY_AGREEMENT'),
click () {
privacyManager.show(false)
}
},
{
label: '重启应用',
label: T('RELOAD_APP'),
click () {
app.relaunch()
app.exit(0)
@@ -93,7 +81,7 @@ export function createContextMenu () {
// @ts-ignore
{
role: 'quit',
label: '退出'
label: T('QUIT')
}
])
} else if (process.platform === 'linux') {
@@ -106,7 +94,7 @@ export function createContextMenu () {
contextMenu = Menu.buildFromTemplate([
{
label: '打开详细窗口',
label: T('OPEN_MAIN_WINDOW'),
click () {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
settingWindow!.show()
@@ -118,7 +106,7 @@ export function createContextMenu () {
},
// @ts-ignore
{
label: '打开更新助手',
label: T('OPEN_UPDATE_HELPER'),
type: 'checkbox',
checked: db.get('settings.showUpdateTip'),
click () {
@@ -127,7 +115,7 @@ export function createContextMenu () {
}
},
{
label: '关于应用',
label: T('ABOUT'),
click () {
dialog.showMessageBox({
title: 'PicGo',
@@ -140,14 +128,26 @@ export function createContextMenu () {
// @ts-ignore
{
role: 'quit',
label: '退出'
label: T('QUIT')
}
])
}
return contextMenu!
}
const getTrayIcon = () => {
if (process.platform === 'darwin') {
const isMacOSGreaterThan11 = isMacOSVersionGreaterThanOrEqualTo('11')
return isMacOSGreaterThan11
? getStaticPath('menubar-newdarwinTemplate.png')
: getStaticPath('menubar.png')
} else {
return getStaticPath('menubar-nodarwin.png')
}
}
export function createTray () {
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
const menubarPic = getTrayIcon()
tray = new Tray(menubarPic)
// click事件在Mac和Windows上可以触发(在Ubuntu上无法触发,Unity不支持)
if (process.platform === 'darwin' || process.platform === 'win32') {
@@ -156,25 +156,37 @@ 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') {
toggleWindow(bounds)
setTimeout(() => {
const img = clipboard.readImage()
setTimeout(async () => {
const obj: ImgInfo[] = []
if (!img.isEmpty()) {
// 从剪贴板来的图片默认转为png
// @ts-ignore
const imgUrl = 'data:image/png;base64,' + Buffer.from(img.toPNG(), 'binary').toString('base64')
obj.push({
width: img.getSize().width,
height: img.getSize().height,
imgUrl
})
const imgPathList = getClipboardFilePathList()
if (imgPathList.length > 0) {
for (const imgPath of imgPathList) {
const decodePath = ensureFilePath(imgPath)
obj.push({
imgUrl: decodePath
})
}
} else {
const img = clipboard.readImage()
if (!img.isEmpty()) {
const imgUrl = img.toDataURL()
obj.push({
width: img.getSize().width,
height: img.getSize().height,
imgUrl
})
}
}
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)) {
@@ -190,20 +202,21 @@ export function createTray () {
})
tray.on('drag-enter', () => {
if (systemPreferences.isDarkMode()) {
tray!.setImage(`${__static}/upload-dark.png`)
if (nativeTheme.shouldUseDarkColors) {
tray!.setImage(getStaticPath('upload-dark.png'))
} else {
tray!.setImage(`${__static}/upload.png`)
tray!.setImage(getStaticPath('upload.png'))
}
})
tray.on('drag-end', () => {
tray!.setImage(`${__static}/menubar.png`)
const menubarPic = getTrayIcon()
tray!.setImage(menubarPic)
})
// drop-files only be supported in macOS
// so the tray window must be available
tray.on('drop-files', async (event: Event, files: string[]) => {
tray.on('drop-files', async (event, files: string[]) => {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
const imgs = await uploader
@@ -212,11 +225,13 @@ 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: '上传成功',
body: imgs[i].imgUrl!,
icon: files[i]
title: T('UPLOAD_SUCCEED'),
body: imgs[i].imgUrl!
// icon: files[i]
})
setTimeout(() => {
notification.show()
@@ -228,35 +243,59 @@ 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 (isMacOS) {
if (db.get('settings.showDockIcon') !== false) {
app.dock?.show()
app.dock?.setMenu(createContextMenu())
} else {
app.dock?.hide()
}
}
}
export function createMenu () {
if (menu) {
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)
+32 -18
View File
@@ -3,38 +3,52 @@ import {
WebContents
} from 'electron'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
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'
export const uploadClipboardFiles = async (): Promise<string> => {
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[]> => {
const useBuiltinClipboard = !!db.get('settings.useBuiltinClipboard')
const win = windowManager.getAvailableWindow()
const img = await uploader.setWebContents(win!.webContents).upload()
if (useBuiltinClipboard) {
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard()
}
return await uploader.setWebContents(win!.webContents).upload()
}
export const uploadClipboardFiles = async (): Promise<string> => {
logger.info('upload clipboard file')
const img = await handleClipboardUploading()
if (img !== false) {
if (img.length > 0) {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl!,
icon: img[0].imgUrl
title: T('UPLOAD_SUCCEED'),
body: img[0].imgUrl!
// icon: img[0].imgUrl
})
notification.show()
setTimeout(() => {
notification.show()
}, 100)
await GalleryDB.getInstance().insert(img[0])
// trayWindow just be created in mac/windows, not in linux
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: '上传不成功',
body: '你剪贴板最新的一条记录不是图片哦'
title: T('UPLOAD_FAILED'),
body: T('TIPS_UPLOAD_NOT_PICTURES')
})
notification.show()
return ''
@@ -54,21 +68,21 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl!,
icon: files[i].path
title: T('UPLOAD_SUCCEED'),
body: imgs[i].imgUrl!
// icon: files[i].path
})
setTimeout(() => {
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 {
+67 -23
View File
@@ -2,37 +2,38 @@ import {
Notification,
BrowserWindow,
ipcMain,
WebContents
WebContents,
clipboard
} from 'electron'
import dayjs from 'dayjs'
import picgo from '@core/picgo'
import db from '~/main/apis/core/datastore'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import { IWindowList } from '#/types/enum'
import util from 'util'
import { IPicGo } from 'picgo/dist/src/types'
import { showNotification, calcDurationRange } from '~/main/utils/common'
import { TALKING_DATA_EVENT } from '~/universal/events/constants'
import { IPicGo } from 'picgo'
import { showNotification, calcDurationRange, getClipboardFilePathList } from '~/main/utils/common'
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME, TALKING_DATA_EVENT } from '~/universal/events/constants'
import logger from '@core/picgo/logger'
const waitForShow = (webcontent: WebContents) => {
return new Promise<void>((resolve) => {
webcontent.on('did-finish-load', () => {
resolve()
})
})
}
import { T } from '~/main/i18n'
import fse from 'fs-extra'
import path from 'path'
import { privacyManager } from '~/main/utils/privacyManager'
import writeFile from 'write-file-atomic'
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static'
import { cleanupFormUploaderFiles } from '~/main/utils/cleanupFormUploaderFiles'
import { IpcMainEvent } from 'electron/main'
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
return new Promise((resolve) => {
const windowId = window.id
ipcMain.once(`rename${id}`, (evt: Event, newName: string) => {
ipcMain.once(`${RENAME_FILE_NAME}${id}`, (evt: IpcMainEvent, newName: string) => {
resolve(newName)
window.close()
})
window.on('close', () => {
resolve(null)
ipcMain.removeAllListeners(`rename${id}`)
ipcMain.removeAllListeners(`${RENAME_FILE_NAME}${id}`)
windowManager.deleteById(windowId)
})
})
@@ -60,19 +61,19 @@ class Uploader {
}
init () {
picgo.on('notification', message => {
picgo.on('notification', (message: Electron.NotificationConstructorOptions | undefined) => {
const notification = new Notification(message)
notification.show()
})
picgo.on('uploadProgress', progress => {
picgo.on('uploadProgress', (progress: any) => {
this.webContents?.send('uploadProgress', progress)
})
picgo.on('beforeTransform', () => {
if (db.get('settings.uploadNotification')) {
const notification = new Notification({
title: '上传进度',
body: '正在上传'
title: T('UPLOAD_PROGRESS'),
body: T('UPLOADING')
})
notification.show()
}
@@ -86,14 +87,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', 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
@@ -108,8 +114,43 @@ class Uploader {
return this
}
/**
* use electron's clipboard image to upload
*/
async uploadWithBuildInClipboard (): Promise<ImgInfo[]|false> {
let filePath = ''
try {
const imgPath = getClipboardFilePathList()
if (!imgPath.length) {
const nativeImage = clipboard.readImage()
if (nativeImage.isEmpty()) {
return false
}
const buffer = nativeImage.toPNG()
const baseDir = picgo.baseDir
const fileName = `${dayjs().format('YYYYMMDDHHmmssSSS')}.png`
filePath = path.join(baseDir, CLIPBOARD_IMAGE_FOLDER, fileName)
await writeFile(filePath, buffer)
return await this.upload([filePath])
} else {
return await this.upload(imgPath)
}
} catch (e: any) {
logger.error(e)
return false
} finally {
if (filePath) {
fse.unlink(filePath)
}
}
}
async upload (img?: IUploadOption): Promise<ImgInfo[]|false> {
try {
const privacyCheckRes = await privacyManager.check()
if (!privacyCheckRes) {
throw Error(T('PRIVACY_TIPS'))
}
const startTime = Date.now()
const output = await picgo.upload(img)
if (Array.isArray(output) && output.some((item: ImgInfo) => item.imgUrl)) {
@@ -129,12 +170,15 @@ class Uploader {
logger.error(e)
setTimeout(() => {
showNotification({
title: '上传失败',
title: T('UPLOAD_FAILED'),
body: util.format(e.stack),
clickToCopy: true
})
}, 500)
return false
} finally {
ipcMain.removeAllListeners(GET_RENAME_FILE_NAME)
cleanupFormUploaderFiles(img)
}
}
}
+6 -19
View File
@@ -1,24 +1,11 @@
export enum IWindowList {
SETTING_WINDOW = 'SETTING_WINDOW',
TRAY_WINDOW = 'TRAY_WINDOW',
MINI_WINDOW = 'MINI_WINDOW',
RENAME_WINDOW = 'RENAME_WINDOW'
}
import { buildRendererUrl } from '~/main/utils/env'
const isDevelopment = process.env.NODE_ENV !== 'production'
export const TRAY_WINDOW_URL = buildRendererUrl()
export const TRAY_WINDOW_URL = isDevelopment
? (process.env.WEBPACK_DEV_SERVER_URL as string)
: 'picgo://./index.html'
export const SETTING_WINDOW_URL = buildRendererUrl('main-page/upload')
export const SETTING_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#main-page/upload`
: 'picgo://./index.html#main-page/upload'
export const MINI_WINDOW_URL = buildRendererUrl('mini-page')
export const MINI_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page`
: 'picgo://./index.html#mini-page'
export const RENAME_WINDOW_URL = buildRendererUrl('rename-page')
export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page`
: 'picgo://./index.html#rename-page'
export const TOOLBOX_WINDOW_URL = buildRendererUrl('toolbox-page')
+101 -33
View File
@@ -1,19 +1,56 @@
// import path from 'path'
import {
IWindowList,
SETTING_WINDOW_URL,
TRAY_WINDOW_URL,
MINI_WINDOW_URL,
RENAME_WINDOW_URL
RENAME_WINDOW_URL,
TOOLBOX_WINDOW_URL
} from './constants'
import { IWindowListItem } from '#/types/electron'
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 { T } from '~/main/i18n'
import { isLinux } from '~/universal/utils/common'
import { getStaticPath } from '#/utils/staticPath'
// import { URLSearchParams } from 'url'
const windowList = new Map<IWindowList, IWindowListItem>()
const defaultWebPreferences = {
// preload: path.join(__dirname, '../preload/index.js'),
nodeIntegration: true,
contextIsolation: false,
nodeIntegrationInWorker: true,
backgroundThrottling: false
}
const handleWindowParams = (windowURL: string) => {
// const [baseURL, hash = ''] = windowURL.split('#')
// const search = new URLSearchParams()
// const lang = i18n.getLanguage()
// search.append('lang', lang)
// return `${baseURL}?${search.toString()}#${hash}`
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,
@@ -28,15 +65,13 @@ windowList.set(IWindowList.TRAY_WINDOW, {
transparent: true,
vibrancy: 'ultra-dark',
webPreferences: {
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true,
backgroundThrottling: false
...defaultWebPreferences,
webSecurity: false
}
}
},
callback (window) {
window.loadURL(TRAY_WINDOW_URL)
window.loadURL(handleWindowParams(TRAY_WINDOW_URL))
window.on('blur', () => {
window.hide()
})
@@ -47,6 +82,7 @@ windowList.set(IWindowList.SETTING_WINDOW, {
isValid: true,
multiple: false,
options () {
const showDockIcon = db.get('settings.showDockIcon') !== false
const options: IBrowserWindowOptions = {
height: 450,
width: 800,
@@ -56,28 +92,24 @@ windowList.set(IWindowList.SETTING_WINDOW, {
fullscreenable: false,
resizable: false,
title: 'PicGo',
vibrancy: 'ultra-dark',
transparent: true,
skipTaskbar: !showDockIcon,
titleBarStyle: 'hidden',
webPreferences: {
backgroundThrottling: false,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true,
...defaultWebPreferences,
webSecurity: false
}
}
if (process.platform !== 'darwin') {
options.show = false
options.frame = false
options.backgroundColor = '#3f3c37'
options.transparent = false
options.icon = `${__static}/logo.png`
options.icon = getStaticPath('logo.png')
options.skipTaskbar = false
}
return options
},
callback (window, windowManager) {
window.loadURL(SETTING_WINDOW_URL)
window.loadURL(handleWindowParams(SETTING_WINDOW_URL))
window.on('closed', () => {
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
if (process.platform === 'linux') {
@@ -98,28 +130,25 @@ windowList.set(IWindowList.MINI_WINDOW, {
const obj: IBrowserWindowOptions = {
height: 64,
width: 64,
show: process.platform === 'linux',
show: isLinux,
frame: false,
fullscreenable: false,
skipTaskbar: true,
resizable: false,
transparent: process.platform !== 'linux',
icon: `${__static}/logo.png`,
icon: getStaticPath('logo.png'),
webPreferences: {
backgroundThrottling: false,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true
...defaultWebPreferences
}
}
if (db.get('settings.miniWindowOntop')) {
if (db.get('settings.miniWindowOnTop')) {
obj.alwaysOnTop = true
}
return obj
},
callback (window) {
window.loadURL(MINI_WINDOW_URL)
window.loadURL(handleWindowParams(MINI_WINDOW_URL))
}
})
@@ -133,24 +162,19 @@ windowList.set(IWindowList.RENAME_WINDOW, {
show: true,
fullscreenable: false,
resizable: false,
vibrancy: 'ultra-dark',
backgroundColor: 'rgba(26,40,42,0.9)',
webPreferences: {
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true,
backgroundThrottling: false
...defaultWebPreferences
}
}
if (process.platform !== 'darwin') {
options.show = true
options.backgroundColor = '#3f3c37'
options.autoHideMenuBar = true
options.transparent = false
}
return options
},
async callback (window, windowManager) {
window.loadURL(RENAME_WINDOW_URL)
window.loadURL(handleWindowParams(RENAME_WINDOW_URL))
const currentWindow = windowManager.getAvailableWindow()
if (currentWindow && currentWindow.isVisible()) {
// bounds: { x: 821, y: 75, width: 800, height: 450 }
@@ -168,4 +192,48 @@ windowList.set(IWindowList.RENAME_WINDOW, {
}
})
windowList.set(IWindowList.TOOLBOX_WINDOW, {
isValid: true,
multiple: false,
options () {
const options: IBrowserWindowOptions = {
height: 450,
width: 800,
show: false,
frame: true,
center: true,
fullscreenable: false,
resizable: false,
backgroundColor: 'rgba(26,40,42,0.9)',
title: `PicGo-${T('TOOLBOX')}`,
icon: getStaticPath('logo.png'),
webPreferences: {
...defaultWebPreferences,
webSecurity: false
}
}
if (process.platform !== 'darwin') {
options.autoHideMenuBar = true
}
return options
},
async callback (window, windowManager) {
window.loadURL(TOOLBOX_WINDOW_URL)
const currentWindow = windowManager.getAvailableWindow()
if (currentWindow && currentWindow.isVisible()) {
// bounds: { x: 821, y: 75, width: 800, height: 450 }
const bounds = currentWindow.getBounds()
const positionX = bounds.x + bounds.width / 2 - 400
let positionY
// if is the settingWindow
if (bounds.height > 400) {
positionY = bounds.y + bounds.height / 2 - 225
} else { // if is the miniWindow
positionY = bounds.y + bounds.height / 2
}
window.setPosition(positionX, positionY, false)
}
}
})
export default windowList
+5 -4
View File
@@ -1,11 +1,8 @@
import {
BrowserWindow
} from 'electron'
import { IWindowManager, IWindowListItem } from '#/types/electron'
import windowList from './windowList'
import {
IWindowList
} from './constants'
import { IWindowList } from '#/types/enum'
class WindowManager implements IWindowManager {
private windowMap: Map<IWindowList | string, BrowserWindow> = new Map()
@@ -26,6 +23,10 @@ class WindowManager implements IWindowManager {
this.windowIdMap.set(window.id, name)
}
windowConfig.callback(window, this)
// https://github.com/electron/electron/issues/1594
window.on('page-title-updated', (evt) => {
evt.preventDefault()
})
window.on('close', () => {
this.deleteById(id)
})
+4
View File
@@ -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`
+25 -11
View File
@@ -1,8 +1,11 @@
import fs from 'fs-extra'
import writeFile from 'write-file-atomic'
import path from 'path'
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')
@@ -11,8 +14,8 @@ let _configFilePath = ''
let hasCheckPath = false
const errorMsg = {
broken: 'PicGo 配置文件损坏,已经恢复为默认配置',
brokenButBackup: 'PicGo 配置文件损坏,已经恢复为备份配置'
broken: T('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT'),
brokenButBackup: T('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP')
}
/** ensure notification list */
@@ -36,7 +39,7 @@ function dbChecker () {
}
let configFile: string = '{}'
const optionsTpl = {
title: '注意',
title: T('TIPS_NOTICE'),
body: ''
}
// config save bak
@@ -49,9 +52,11 @@ function dbChecker () {
try {
configFile = fs.readFileSync(configFileBackupPath, { encoding: 'utf-8' })
JSON.parse(configFile)
fs.writeFileSync(configFilePath, configFile, { encoding: 'utf-8' })
writeFile.sync(configFilePath, configFile, { encoding: 'utf-8' })
const stats = fs.statSync(configFileBackupPath)
optionsTpl.body = `${errorMsg.brokenButBackup}\n备份文件版本:${dayjs(stats.mtime).format('YYYY-MM-DD HH:mm:ss')}`
optionsTpl.body = `${errorMsg.brokenButBackup}\n${T('TIPS_PICGO_BACKUP_FILE_VERSION', {
v: dayjs(stats.mtime).format('YYYY-MM-DD HH:mm:ss')
})}`
global.notificationList?.push(optionsTpl)
return
} catch (e) {
@@ -64,7 +69,7 @@ function dbChecker () {
global.notificationList?.push(optionsTpl)
return
}
fs.writeFileSync(configFileBackupPath, configFile, { encoding: 'utf-8' })
writeFile.sync(configFileBackupPath, configFile, { encoding: 'utf-8' })
}
}
@@ -94,18 +99,17 @@ function dbPathChecker (): string {
}
return _configFilePath
} catch (e) {
const picgoLogPath = path.join(defaultConfigPath, 'picgo.log')
const picgoLogPath = path.join(STORE_PATH, 'picgo-gui-local.log')
const logger = getLogger(picgoLogPath)
if (!hasCheckPath) {
const optionsTpl = {
title: '注意',
body: '自定义文件解析出错,请检查路径内容是否正确'
title: T('TIPS_NOTICE'),
body: T('TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR')
}
global.notificationList?.push(optionsTpl)
hasCheckPath = true
}
logger('error', e)
console.error(e)
_configFilePath = defaultConfigPath
return _configFilePath
}
@@ -128,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
}
+29 -40
View File
@@ -1,10 +1,7 @@
import Datastore from 'lowdb'
// @ts-ignore
import LodashId from 'lodash-id'
import FileSync from 'lowdb/adapters/FileSync'
import fs from 'fs-extra'
import { dbPathChecker, dbPathDir, getGalleryDBPath } from './dbChecker'
import { DBStore } from '@picgo/store'
import { DBStore, JSONStore } from '@picgo/store'
import { T } from '~/main/i18n'
const STORE_PATH = dbPathDir()
@@ -12,70 +9,60 @@ if (!fs.pathExistsSync(STORE_PATH)) {
fs.mkdirpSync(STORE_PATH)
}
const CONFIG_PATH: string = dbPathChecker()
const DB_PATH: string = getGalleryDBPath().dbPath
export const DB_PATH: string = getGalleryDBPath().dbPath
// TODO: use JSONStore with @picgo/store
class ConfigStore {
private db: Datastore.LowdbSync<Datastore.AdapterSync>
private db: JSONStore
constructor () {
const adapter = new FileSync(CONFIG_PATH)
this.db = new JSONStore(CONFIG_PATH)
this.db = Datastore(adapter)
this.db._.mixin(LodashId)
if (!this.db.has('picBed').value()) {
if (!this.db.has('picBed')) {
this.db.set('picBed', {
current: 'smms', // deprecated
uploader: 'smms',
smms: {
token: ''
}
}).write()
})
}
if (!this.db.has('settings.shortKey').value()) {
if (!this.db.has('settings.shortKey')) {
this.db.set('settings.shortKey[picgo:upload]', {
enable: true,
key: 'CommandOrControl+Shift+P',
name: 'upload',
label: '快捷上传'
}).write()
label: T('QUICK_UPLOAD')
})
}
this.read()
}
flush () {
this.db = new JSONStore(CONFIG_PATH)
}
read () {
return this.db.read()
this.db.read()
return this.db
}
get (key = '') {
return this.read().get(key).value()
get (key = ''): any {
if (key === '') {
return this.db.read()
}
return this.db.get(key)
}
set (key: string, value: any) {
return this.read().set(key, value).write()
set (key: string, value: any): void {
return this.db.set(key, value)
}
has (key: string) {
return this.read().has(key).value()
}
insert (key: string, value: any): void {
// @ts-ignore
return this.read().get(key).insert(value).write()
return this.db.has(key)
}
unset (key: string, value: any): boolean {
return this.read().get(key).unset(value).value()
}
getById (key: string, id: string) {
// @ts-ignore
return this.read().get(key).getById(id).value()
}
removeById (key: string, id: string) {
// @ts-ignore
return this.read().get(key).removeById(id).write()
return this.db.unset(key, value)
}
getConfigPath () {
@@ -83,7 +70,9 @@ class ConfigStore {
}
}
export default new ConfigStore()
const db = new ConfigStore()
export default db
// v2.3.0 add gallery db
class GalleryDB {
+18 -5
View File
@@ -1,9 +1,8 @@
import PicGoCore from '~/universal/types/picgo'
import { dbChecker, dbPathChecker } from 'apis/core/datastore/dbChecker'
import pkg from 'root/package.json'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo') as typeof PicGoCore
import { PicGo } from 'picgo'
import db from 'apis/core/datastore'
import debounce from 'lodash/debounce'
const CONFIG_PATH = dbPathChecker()
@@ -18,4 +17,18 @@ picgo.saveConfig({
global.PICGO_GUI_VERSION = pkg.version
picgo.GUI_VERSION = global.PICGO_GUI_VERSION
export default picgo! as PicGoCore
const originPicGoSaveConfig = picgo.saveConfig.bind(picgo)
function flushDB () {
db.flush()
}
const debounced = debounce(flushDB, 1000)
picgo.saveConfig = (config: IStringKeyMap) => {
originPicGoSaveConfig(config)
// flush electron's db
debounced()
}
export default picgo
+79 -15
View File
@@ -2,28 +2,92 @@ import fs from 'fs-extra'
import dayjs from 'dayjs'
import util from 'util'
const checkLogFileIsLarge = (logPath: string): {
isLarge: boolean
logFileSize?: number
logFileSizeLimit?: number
} => {
try {
if (fs.existsSync(logPath)) {
const logFileSize = fs.statSync(logPath).size
const logFileSizeLimit = 10 * 1024 * 1024 // 10 MB default
return {
isLarge: logFileSize > logFileSizeLimit,
logFileSize,
logFileSizeLimit
}
}
return {
isLarge: false
}
} catch (e) {
// why throw error???
console.log(e)
return {
isLarge: true
}
}
}
const recreateLogFile = (logPath: string): void => {
try {
if (fs.existsSync(logPath)) {
fs.unlinkSync(logPath)
fs.createFileSync(logPath)
}
} catch (e) {
// do nothing
}
}
/**
* for local log before picgo inited
*/
const getLogger = (logPath: string) => {
if (!fs.existsSync(logPath)) {
fs.ensureFileSync(logPath)
let hasUncathcedError = false
try {
if (!fs.existsSync(logPath)) {
fs.ensureFileSync(logPath)
}
if (checkLogFileIsLarge(logPath).isLarge) {
recreateLogFile(logPath)
}
} catch (e) {
console.log(e)
hasUncathcedError = true
}
return (type: string, ...msg: any[]) => {
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicGo ${type.toUpperCase()}] `
msg.forEach((item: ILogArgvTypeWithError) => {
if (typeof item === 'object' && type === 'error') {
log += `\n------Error Stack Begin------\n${util.format(item.stack)}\n-------Error Stack End------- `
} else {
if (typeof item === 'object') {
item = JSON.stringify(item)
}
log += `${item} `
if (hasUncathcedError) {
if (checkLogFileIsLarge(logPath).isLarge) {
recreateLogFile(logPath)
}
})
log += '\n'
// A synchronized approach to avoid log msg sequence errors
fs.appendFileSync(logPath, log)
return
}
try {
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicGo ${type.toUpperCase()}] `
msg.forEach((item: ILogArgvTypeWithError) => {
if (typeof item === 'object' && type === 'error') {
log += `\n------Error Stack Begin------\n${util.format(item.stack)}\n-------Error Stack End------- `
} else {
if (typeof item === 'object') {
item = JSON.stringify(item)
}
log += `${item} `
}
})
log += '\n'
console.log(log)
// A synchronized approach to avoid log msg sequence errors
if (checkLogFileIsLarge(logPath).isLarge) {
recreateLogFile(logPath)
}
if (!hasUncathcedError) {
fs.appendFileSync(logPath, log)
}
} catch (e) {
console.log(e)
hasUncathcedError = true
}
}
}
+49 -13
View File
@@ -17,14 +17,15 @@ import {
SHOW_INPUT_BOX
} 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 {
private static instance: GuiApi
private windowId: number = -1
private settingWindowId: number = -1
private constructor () {
console.log('init guiapi')
console.log('init gui api')
}
public static getInstance (): GuiApi {
@@ -48,7 +49,7 @@ class GuiApi implements IGuiApi {
})
}
private getWebcontentsByWindowId (id: number) {
private getWebContentsByWindowId (id: number) {
return BrowserWindow.fromId(id)?.webContents
}
@@ -57,9 +58,9 @@ 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) => {
ipcMain.once(SHOW_INPUT_BOX, (event, value: string) => {
resolve(value)
})
})
@@ -68,12 +69,12 @@ class GuiApi implements IGuiApi {
async showFileExplorer (options: IShowFileExplorerOption = {}) {
this.windowId = await getWindowId()
const res = await dialog.showOpenDialog(BrowserWindow.fromId(this.windowId)!, options)
return res.filePaths?.[0]
return res.filePaths || []
}
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'
@@ -81,9 +82,9 @@ class GuiApi implements IGuiApi {
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl as string,
icon: imgs[i].imgUrl
title: T('UPLOAD_SUCCEED'),
body: imgs[i].imgUrl as string
// icon: imgs[i].imgUrl
})
setTimeout(() => {
notification.show()
@@ -92,7 +93,7 @@ class GuiApi implements IGuiApi {
}
handleCopyUrl(pasteText.join('\n'))
webContents?.send('uploadFiles', imgs)
webContents?.send('updateGallery')
webContents?.send(IRPCActionType.UPDATE_GALLERY)
return imgs
}
return []
@@ -129,6 +130,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, value: T | false) => {
resolve(value)
})
})
}
/**
* get picgo config/data path
*/
@@ -145,14 +160,35 @@ class GuiApi implements IGuiApi {
get galleryDB (): DBStore {
return new Proxy<DBStore>(GalleryDB.getInstance(), {
get (target, prop: keyof DBStore) {
if (prop === 'overwrite') {
return new Proxy(GalleryDB.getInstance().overwrite, {
apply (target, ctx, args) {
return new Promise((resolve) => {
const guiApi = GuiApi.getInstance()
guiApi.showMessageBox({
title: T('TIPS_WARNING'),
message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'),
type: 'info',
buttons: ['Yes', 'No']
}).then(res => {
if (res.result === 0) {
resolve(Reflect.apply(target, ctx, args))
} else {
resolve(undefined)
}
})
})
}
})
}
if (prop === 'removeById') {
return new Proxy(GalleryDB.getInstance().removeById, {
apply (target, ctx, args) {
return new Promise((resolve) => {
const guiApi = GuiApi.getInstance()
guiApi.showMessageBox({
title: '警告',
message: '有插件正在试图删除一些相册图片,是否继续',
title: T('TIPS_WARNING'),
message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'),
type: 'info',
buttons: ['Yes', 'No']
}).then(res => {
+1 -2
View File
@@ -6,7 +6,7 @@ import {
import {
createMenu
} from 'apis/app/system'
import { IWindowList } from 'apis/app/window/constants'
import { IWindowList } from '#/types/enum'
import windowManager from 'apis/app/window/windowManager'
import {
UPLOAD_WITH_FILES,
@@ -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,
+34 -28
View File
@@ -7,7 +7,7 @@ import {
BrowserWindow
} from 'electron'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
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'
@@ -25,9 +25,9 @@ import {
SHOW_UPLOAD_PAGE_MENU,
OPEN_USER_STORE_FILE,
OPEN_URL,
RELOAD_APP,
SHOW_PLUGIN_PAGE_MENU,
SET_MINI_WINDOW_POS
SET_MINI_WINDOW_POS,
GET_PICBEDS
} from '#/events/constants'
import {
uploadClipboardFiles,
@@ -35,8 +35,9 @@ import {
} from '~/main/apis/app/uploader/apis'
import picgoCoreIPC from './picgoCoreIPC'
import { handleCopyUrl } from '~/main/utils/common'
import { buildMainPageMenu, buildMiniPageMenu, buildPluginPageMenu, buildUploadPageMenu } from './remotes/menu'
import { buildMainPageMenu, buildMiniPageMenu, buildPluginPageMenu, buildPicBedListMenu } from './remotes/menu'
import path from 'path'
import { T } from '~/main/i18n'
const STORE_PATH = app.getPath('userData')
@@ -46,27 +47,29 @@ export default {
// from macOS tray
ipcMain.on('uploadClipboardFiles', async () => {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
const img = await uploader.setWebContents(trayWindow.webContents).upload()
// macOS use builtin clipboard is OK
const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard()
if (img !== false) {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl!,
title: T('UPLOAD_SUCCEED'),
body: img[0].imgUrl!
// icon: file[0]
icon: img[0].imgUrl
// icon: img[0].imgUrl
})
notification.show()
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')
})
ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
console.log('handle')
uploadClipboardFiles()
})
@@ -79,14 +82,14 @@ export default {
evt.sender.send('updateShortKeyResponse', result)
if (result) {
const notification = new Notification({
title: '操作成功',
body: '你的快捷键已经修改成功'
title: T('OPERATION_SUCCEED'),
body: T('TIPS_SHORTCUT_MODIFIED_SUCCEED')
})
notification.show()
} else {
const notification = new Notification({
title: '操作失败',
body: '快捷键冲突,请重新设置'
title: T('OPERATION_FAILED'),
body: T('TIPS_SHORTCUT_MODIFIED_CONFLICT')
})
notification.show()
}
@@ -96,14 +99,14 @@ export default {
const result = shortKeyHandler.bindOrUnbindShortKey(item, from)
if (result) {
const notification = new Notification({
title: '操作成功',
body: '你的快捷键已经修改成功'
title: T('OPERATION_SUCCEED'),
body: T('TIPS_SHORTCUT_MODIFIED_SUCCEED')
})
notification.show()
} else {
const notification = new Notification({
title: '操作失败',
body: '快捷键冲突,请重新设置'
title: T('OPERATION_FAILED'),
body: T('TIPS_SHORTCUT_MODIFIED_CONFLICT')
})
notification.show()
}
@@ -111,8 +114,8 @@ export default {
ipcMain.on('updateCustomLink', () => {
const notification = new Notification({
title: '操作成功',
body: '你的自定义链接格式已经修改成功'
title: T('OPERATION_SUCCEED'),
body: T('TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED')
})
notification.show()
})
@@ -133,6 +136,11 @@ export default {
ipcMain.on('openMiniWindow', () => {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
if (db.get('settings.miniWindowOnTop')) {
miniWindow.setAlwaysOnTop(true)
}
miniWindow.show()
miniWindow.focus()
settingWindow.hide()
@@ -145,9 +153,9 @@ export default {
}
})
ipcMain.on('getPicBeds', (evt: IpcMainEvent) => {
ipcMain.on(GET_PICBEDS, (evt: IpcMainEvent) => {
const picBeds = getPicBeds()
evt.sender.send('getPicBeds', picBeds)
evt.sender.send(GET_PICBEDS, picBeds)
evt.returnValue = picBeds
})
@@ -159,7 +167,9 @@ export default {
server.restart()
})
ipcMain.on(OPEN_DEVTOOLS, (event: IpcMainEvent) => {
event.sender.openDevTools()
event.sender.openDevTools({
mode: 'detach'
})
})
// menu & window methods
ipcMain.on(SHOW_MINI_PAGE_MENU, () => {
@@ -171,14 +181,14 @@ export default {
})
ipcMain.on(SHOW_MAIN_PAGE_MENU, () => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const menu = buildMainPageMenu()
const menu = buildMainPageMenu(window)
menu.popup({
window
})
})
ipcMain.on(SHOW_UPLOAD_PAGE_MENU, () => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const menu = buildUploadPageMenu()
const menu = buildPicBedListMenu()
menu.popup({
window
})
@@ -209,10 +219,6 @@ export default {
ipcMain.on(OPEN_URL, (evt: IpcMainEvent, url: string) => {
shell.openExternal(url)
})
ipcMain.on(RELOAD_APP, () => {
app.relaunch()
app.exit(0)
})
ipcMain.on(SET_MINI_WINDOW_POS, (evt: IpcMainEvent, pos: IMiniWindowPos) => {
const window = BrowserWindow.getFocusedWindow()
window?.setBounds(pos)
+95 -29
View File
@@ -7,14 +7,13 @@ import {
ipcMain,
clipboard
} from 'electron'
import PicGoCore from '~/universal/types/picgo'
import { IPasteStyle, IPicGoHelperType } from '#/types/enum'
import fs from 'fs-extra'
import { IPasteStyle, IPicGoHelperType, IWindowList } from '#/types/enum'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import picgo from '@core/picgo'
import { handleStreamlinePluginName } from '~/universal/utils/common'
import { IGuiMenuItem } from 'picgo/dist/src/types'
import { handleStreamlinePluginName, simpleClone } from '~/universal/utils/common'
import { IGuiMenuItem, PicGo as PicGoCore } from 'picgo'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import { showNotification } from '~/main/utils/common'
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
import {
@@ -27,18 +26,21 @@ import {
PICGO_GET_BY_ID_DB,
PICGO_REMOVE_BY_ID_DB,
PICGO_OPEN_FILE,
PASTE_TEXT
PASTE_TEXT,
OPEN_WINDOW,
GET_LANGUAGE_LIST,
SET_CURRENT_LANGUAGE,
GET_CURRENT_LANGUAGE,
GET_PICBED_CONFIG
} from '#/events/constants'
import { GalleryDB } from 'apis/core/datastore'
import { IObject, IFilter } from '@picgo/store/dist/types'
import pasteTemplate from '../utils/pasteTemplate'
import { i18nManager, T } from '~/main/i18n'
import { rpcServer } from './rpc'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
// const PluginHandler = requireFunc('picgo/dist/lib/PluginHandler').default
const STORE_PATH = path.dirname(dbPathChecker())
// const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
interface GuiMenuItem {
label: string
@@ -79,12 +81,14 @@ const getPluginList = (): IPicGoPlugin[] => {
for (const i in pluginList) {
const plugin = picgo.pluginLoader.getPlugin(pluginList[i])!
const pluginPath = path.join(STORE_PATH, `/node_modules/${pluginList[i]}`)
const pluginPKG = requireFunc(path.join(pluginPath, 'package.json'))
const pluginPKG = fs.readJSONSync(path.join(pluginPath, 'package.json'), 'utf-8')
const uploaderName = plugin.uploader || ''
const transformerName = plugin.transformer || ''
let menu: IGuiMenuItem[] = []
let menu: Omit<IGuiMenuItem, 'handle'>[] = []
if (plugin.guiMenu) {
menu = plugin.guiMenu(picgo)
menu = plugin.guiMenu(picgo).map(item => ({
label: item.label
}))
}
let gui = false
if (pluginPKG.keywords && pluginPKG.keywords.length > 0) {
@@ -127,8 +131,19 @@ const getPluginList = (): IPicGoPlugin[] => {
const handleGetPluginList = () => {
ipcMain.on('getPluginList', (event: IpcMainEvent) => {
const list = getPluginList()
event.sender.send('pluginList', list)
try {
const list = simpleClone(getPluginList())
// here can just send JS Object not function
// or will cause [Failed to serialize arguments] error
event.sender.send('pluginList', list)
} catch (e: any) {
event.sender.send('pluginList', [])
showNotification({
title: T('TIPS_GET_PLUGIN_LIST_FAILED'),
body: e.message
})
picgo.log.error(e)
}
})
}
@@ -145,7 +160,7 @@ const handlePluginInstall = () => {
shortKeyHandler.registerPluginShortKey(res.body[0])
} else {
showNotification({
title: '插件安装失败',
title: T('PLUGIN_INSTALL_FAILED'),
body: res.body as string
})
}
@@ -163,7 +178,7 @@ const handlePluginUninstall = async (fullName: string) => {
shortKeyHandler.unregisterPluginShortKey(res.body[0])
} else {
showNotification({
title: '插件卸载失败',
title: T('PLUGIN_UNINSTALL_FAILED'),
body: res.body as string
})
}
@@ -179,7 +194,7 @@ const handlePluginUpdate = async (fullName: string) => {
window.webContents.send('updateSuccess', res.body[0])
} else {
showNotification({
title: '插件更新失败',
title: T('PLUGIN_UPDATE_FAILED'),
body: res.body as string
})
}
@@ -191,8 +206,8 @@ const handleNPMError = (): IDispose => {
const handler = (msg: string) => {
if (msg === 'NPM is not installed') {
dialog.showMessageBox({
title: '发生错误',
message: '请安装Node.js并重启PicGo再继续操作',
title: T('TIPS_ERROR'),
message: T('TIPS_INSTALL_NODE_AND_RELOAD_PICGO'),
buttons: ['Yes']
}).then((res) => {
if (res.response === 0) {
@@ -206,13 +221,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 = handleConfigWithFunction(picgo.helper.uploader.get(type)!.config(picgo))
event.sender.send('getPicBedConfig', config, name)
const _config = picgo.helper.uploader.get(type)!.config!(picgo)
const config = handleConfigWithFunction(_config)
event.sender.send(GET_PICBED_CONFIG, config, name)
} else {
event.sender.send('getPicBedConfig', [], name)
event.sender.send(GET_PICBED_CONFIG, [], name)
}
})
}
@@ -263,15 +279,23 @@ const handleImportLocalPlugin = () => {
if (filePaths.length > 0) {
const res = await picgo.pluginHandler.install(filePaths)
if (res.success) {
const list = getPluginList()
event.sender.send('pluginList', list)
try {
const list = simpleClone(getPluginList())
event.sender.send('pluginList', list)
} catch (e: any) {
event.sender.send('pluginList', [])
showNotification({
title: T('TIPS_GET_PLUGIN_LIST_FAILED'),
body: e.message
})
}
showNotification({
title: '导入插件成功',
title: T('PLUGIN_IMPORT_SUCCEED'),
body: ''
})
} else {
showNotification({
title: '导入插件失败',
title: T('PLUGIN_IMPORT_FAILED'),
body: res.body as string
})
}
@@ -317,7 +341,7 @@ const handlePicGoGalleryDB = () => {
event.sender.send(PICGO_REMOVE_BY_ID_DB, res, callbackId)
})
ipcMain.handle(PASTE_TEXT, async (item: ImgInfo, copy = true) => {
ipcMain.handle(PASTE_TEXT, async (_, item: ImgInfo, copy = true) => {
const pasteStyle = picgo.getConfig<IPasteStyle>('settings.pasteStyle') || IPasteStyle.MARKDOWN
const customLink = picgo.getConfig<string>('settings.customLink')
const txt = pasteTemplate(pasteStyle, item, customLink)
@@ -335,6 +359,45 @@ const handleOpenFile = () => {
})
}
const handleOpenWindow = () => {
ipcMain.on(OPEN_WINDOW, (event: IpcMainEvent, windowName: IWindowList) => {
const window = windowManager.get(windowName)
if (window) {
window.show()
}
})
}
const handleI18n = () => {
ipcMain.on(GET_LANGUAGE_LIST, (event: IpcMainEvent) => {
event.sender.send(GET_LANGUAGE_LIST, i18nManager.languageList)
})
ipcMain.on(SET_CURRENT_LANGUAGE, (event: IpcMainEvent, language: string) => {
i18nManager.setCurrentLanguage(language)
const { lang, locales } = i18nManager.getCurrentLocales()
picgo.i18n.setLanguage(lang)
if (process.platform === 'darwin') {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
trayWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales)
}
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
settingWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales)
if (windowManager.has(IWindowList.MINI_WINDOW)) {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)
miniWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales)
}
// event.sender.send(SET_CURRENT_LANGUAGE, lang, locales)
})
ipcMain.on(GET_CURRENT_LANGUAGE, (event: IpcMainEvent) => {
const { lang, locales } = i18nManager.getCurrentLocales()
event.sender.send(GET_CURRENT_LANGUAGE, lang, locales)
})
}
const handleRPCActions = () => {
rpcServer.start()
}
export default {
listen () {
handleGetPluginList()
@@ -347,6 +410,9 @@ export default {
handlePicGoGalleryDB()
handleImportLocalPlugin()
handleOpenFile()
handleOpenWindow()
handleI18n()
handleRPCActions()
},
// TODO: separate to single file
handlePluginUninstall,
+88 -54
View File
@@ -1,5 +1,5 @@
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import { IWindowList } from '#/types/enum'
import { Menu, BrowserWindow, app, dialog } from 'electron'
import getPicBeds from '~/main/utils/getPicBeds'
import picgo from '@core/picgo'
@@ -9,9 +9,11 @@ import {
import { privacyManager } from '~/main/utils/privacyManager'
import pkg from 'root/package.json'
import GuiApi from 'apis/gui'
import PicGoCore from '~/universal/types/picgo'
import { PICGO_CONFIG_PLUGIN, PICGO_HANDLE_PLUGIN_ING, PICGO_TOGGLE_PLUGIN } from '~/universal/events/constants'
import { PICGO_CONFIG_PLUGIN, PICGO_HANDLE_PLUGIN_DONE, PICGO_HANDLE_PLUGIN_ING, PICGO_TOGGLE_PLUGIN, SHOW_MAIN_PAGE_DONATION, SHOW_MAIN_PAGE_QRCODE } from '~/universal/events/constants'
import picgoCoreIPC from '~/main/events/picgoCoreIPC'
import { PicGo as PicGoCore } from 'picgo'
import { T } from '~/main/i18n'
import { changeCurrentUploader } from '~/main/utils/handleUploaderConfig'
interface GuiMenuItem {
label: string
@@ -19,27 +21,10 @@ interface GuiMenuItem {
}
const buildMiniPageMenu = () => {
const picBeds = getPicBeds()
const current = picgo.getConfig('picBed.uploader')
const submenu = picBeds.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: current === item.type,
click () {
picgo.saveConfig({
'picBed.current': item.type,
'picBed.uploader': item.type
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
}
}
})
const submenu = buildPicBedListMenu()
const template = [
{
label: '打开详细窗口',
label: T('OPEN_MAIN_WINDOW'),
click () {
windowManager.get(IWindowList.SETTING_WINDOW)!.show()
if (windowManager.has(IWindowList.MINI_WINDOW)) {
@@ -48,30 +33,30 @@ const buildMiniPageMenu = () => {
}
},
{
label: '选择默认图床',
label: T('CHOOSE_DEFAULT_PICBED'),
type: 'submenu',
submenu
},
{
label: '剪贴板图片上传',
label: T('UPLOAD_BY_CLIPBOARD'),
click () {
uploadClipboardFiles()
}
},
{
label: '隐藏窗口',
label: T('HIDE_WINDOW'),
click () {
BrowserWindow.getFocusedWindow()!.hide()
}
},
{
label: '隐私协议',
label: T('PRIVACY_AGREEMENT'),
click () {
privacyManager.show(false)
}
},
{
label: '重启应用',
label: T('RELOAD_APP'),
click () {
app.relaunch()
app.exit(0)
@@ -79,17 +64,17 @@ const buildMiniPageMenu = () => {
},
{
role: 'quit',
label: '退出'
label: T('QUIT')
}
]
// @ts-ignore
return Menu.buildFromTemplate(template)
}
const buildMainPageMenu = () => {
const buildMainPageMenu = (win: BrowserWindow) => {
const template = [
{
label: '关于',
label: T('ABOUT'),
click () {
dialog.showMessageBox({
title: 'PicGo',
@@ -99,20 +84,34 @@ const buildMainPageMenu = () => {
}
},
{
label: '赞助PicGo',
label: T('SPONSOR_PICGO'),
click () {
// TODO: show donation
win?.webContents?.send(SHOW_MAIN_PAGE_DONATION)
}
},
{
label: '生成图床配置二维码',
label: T('SHOW_PICBED_QRCODE'),
click () {
// TODO: qrcode
// _this.qrcodeVisible = true
win?.webContents?.send(SHOW_MAIN_PAGE_QRCODE)
}
},
{
label: '隐私协议',
label: T('OPEN_TOOLBOX'),
click () {
const window = windowManager.create(IWindowList.TOOLBOX_WINDOW)
window?.show()
}
},
{
label: T('SHOW_DEVTOOLS'),
click () {
win?.webContents?.openDevTools({
mode: 'detach'
})
}
},
{
label: T('PRIVACY_AGREEMENT'),
click () {
privacyManager.show(false)
}
@@ -122,26 +121,58 @@ const buildMainPageMenu = () => {
return Menu.buildFromTemplate(template)
}
const buildUploadPageMenu = () => {
const buildPicBedListMenu = () => {
const picBeds = getPicBeds()
const currentPicBed = picgo.getConfig('picBed.uploader')
const submenu = picBeds.filter(item => item.visible).map(item => {
const currentPicBedName = picBeds.find(item => item.type === currentPicBed)?.name
const picBedConfigList = picgo.getConfig<IUploaderConfig>('uploader')
const currentPicBedMenuItem = [{
label: `${T('CURRENT_PICBED')} - ${currentPicBedName}`,
enabled: false
}, {
type: 'separator'
}]
let submenu = picBeds.filter(item => item.visible).map(item => {
const configList = picBedConfigList?.[item.type]?.configList
const defaultId = picBedConfigList?.[item.type]?.defaultId
const hasSubmenu = !!configList
return {
label: item.name,
type: 'radio',
checked: currentPicBed === item.type,
click () {
picgo.saveConfig({
'picBed.current': item.type,
'picBed.uploader': item.type
type: !hasSubmenu ? 'checkbox' : undefined,
checked: !hasSubmenu ? (currentPicBed === item.type) : undefined,
submenu: hasSubmenu
? configList.map((config) => {
return {
label: config._configName || 'Default',
// if only one config, use checkbox, or radio will checked as default
// see: https://github.com/electron/electron/issues/21292
type: 'checkbox',
checked: config._id === defaultId && (item.type === currentPicBed),
click: function () {
changeCurrentUploader(item.type, config, config._id)
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
}
}
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
: undefined,
click: !hasSubmenu
? function () {
picgo.saveConfig({
'picBed.current': item.type,
'picBed.uploader': item.type
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
}
}
: undefined
}
})
// @ts-ignore
submenu = currentPicBedMenuItem.concat(submenu)
// @ts-ignore
return Menu.buildFromTemplate(submenu)
}
@@ -169,7 +200,7 @@ const handleRestoreState = (item: string, name: string): void => {
const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const menu = [{
label: '启用插件',
label: T('ENABLE_PLUGIN'),
enabled: !plugin.enabled,
click () {
picgo.saveConfig({
@@ -179,7 +210,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, true)
}
}, {
label: '禁用插件',
label: T('DISABLE_PLUGIN'),
enabled: plugin.enabled,
click () {
picgo.saveConfig({
@@ -188,6 +219,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, false)
window.webContents.send(PICGO_HANDLE_PLUGIN_DONE, plugin.fullName)
if (plugin.config.transformer.name) {
handleRestoreState('transformer', plugin.config.transformer.name)
}
@@ -196,14 +228,14 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
}
}
}, {
label: '卸载插件',
label: T('UNINSTALL_PLUGIN'),
click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
picgoCoreIPC.handlePluginUninstall(plugin.fullName)
}
}, {
label: '更新插件',
label: T('UPDATE_PLUGIN'),
click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
@@ -213,7 +245,9 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
for (const i in plugin.config) {
if (plugin.config[i].config.length > 0) {
const obj = {
label: `配置${i} - ${plugin.config[i].fullName || plugin.config[i].name}`,
label: T('CONFIG_THING', {
c: `${i} - ${plugin.config[i].fullName || plugin.config[i].name}`
}),
click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const currentType = i
@@ -231,7 +265,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const currentTransformer = picgo.getConfig<string>('picBed.transformer') || 'path'
const pluginTransformer = plugin.config.transformer.name
const obj = {
label: `${currentTransformer === pluginTransformer ? '禁用' : '启用'}transformer - ${plugin.config.transformer.name}`,
label: `${currentTransformer === pluginTransformer ? T('DISABLE') : T('ENABLE')}transformer - ${plugin.config.transformer.name}`,
click () {
const transformer = plugin.config.transformer.name
const currentTransformer = picgo.getConfig<string>('picBed.transformer') || 'path'
@@ -281,6 +315,6 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
export {
buildMiniPageMenu,
buildMainPageMenu,
buildUploadPageMenu,
buildPicBedListMenu,
buildPluginPageMenu
}
+61
View File
@@ -0,0 +1,61 @@
import { ipcMain, IpcMainEvent } from 'electron'
import { IRPCActionType } from '~/universal/types/enum'
import { RPC_ACTIONS } from '#/events/constants'
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()
private rpcEventHandler = async (event: IpcMainEvent, action: IRPCActionType, args: any[], callbackId: string) => {
try {
const handler = this.routes.get(action)
if (!handler) {
return this.sendBack(event, action, null, callbackId)
}
const res = await handler?.(args, event)
this.sendBack(event, action, res, callbackId)
} catch (e) {
this.sendBack(event, action, null, callbackId)
}
}
/**
* if sendback data is null, then it means that the action is not supported or error occurs
* if there is no callbackId, then do not send back
*/
private sendBack (event: IpcMainEvent, action: IRPCActionType, data: any, callbackId: string) {
if (callbackId) {
event.sender.send(RPC_ACTIONS, data, action, callbackId)
}
}
start () {
ipcMain.on(RPC_ACTIONS, this.rpcEventHandler)
}
use (routes: IRPCRoutes) {
for (const [action, handler] of routes) {
this.routes.set(action, handler)
}
}
stop () {
ipcMain.off(RPC_ACTIONS, this.rpcEventHandler)
}
}
const rpcServer = new RPCServer()
rpcServer.use(configRouter.routes())
rpcServer.use(versionRouter.routes())
rpcServer.use(toolboxRouter.routes())
rpcServer.use(systemRouter.routes())
rpcServer.use(galleryToolboxRouter.routes())
export {
rpcServer
}
+13
View File
@@ -0,0 +1,13 @@
import { IRPCActionType } from '~/universal/types/enum'
export class RPCRouter implements IRPCRouter {
private routeMap: IRPCRoutes = new Map()
add = <T>(action: IRPCActionType, handler: IRPCHandler<T>) => {
this.routeMap.set(action, handler)
return this
}
routes () {
return this.routeMap
}
}
+31
View File
@@ -0,0 +1,31 @@
import { IRPCActionType } from '~/universal/types/enum'
import { RPCRouter } from '../router'
import { deleteUploaderConfig, getUploaderConfigList, selectUploaderConfig, updateUploaderConfig } from '~/main/utils/handleUploaderConfig'
const configRouter = new RPCRouter()
configRouter
.add(IRPCActionType.GET_PICBED_CONFIG_LIST, async (args) => {
const [type] = args as IGetUploaderConfigListArgs
const config = getUploaderConfigList(type)
return config
})
.add(IRPCActionType.DELETE_PICBED_CONFIG, async (args) => {
const [type, id] = args as IDeleteUploaderConfigArgs
const config = deleteUploaderConfig(type, id)
return config
})
.add(IRPCActionType.SELECT_UPLOADER, async (args) => {
const [type, id] = args as ISelectUploaderConfigArgs
selectUploaderConfig(type, id)
return true
})
.add(IRPCActionType.UPDATE_UPLOADER_CONFIG, async (args) => {
const [type, id, config] = args as IUpdateUploaderConfigArgs
updateUploaderConfig(type, id, config)
return true
})
export {
configRouter
}
@@ -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
}
+36
View File
@@ -0,0 +1,36 @@
import { IRPCActionType, IWindowList } from '~/universal/types/enum'
import { RPCRouter } from '../router'
import { app, clipboard, shell } from 'electron'
import windowManager from '~/main/apis/app/window/windowManager'
const systemRouter = new RPCRouter()
systemRouter
.add(IRPCActionType.RELOAD_APP, async () => {
app.relaunch()
app.exit(0)
})
.add(IRPCActionType.OPEN_FILE, async (args) => {
const [filePath] = args as IOpenFileArgs
shell.openPath(filePath)
})
.add(IRPCActionType.COPY_TEXT, async (args) => {
const [text] = args as ICopyTextArgs
return clipboard.writeText(text)
})
.add(IRPCActionType.SHOW_DOCK_ICON, async (args) => {
const [visible] = args as IShowDockIconArgs
app.dock?.[visible ? 'show' : 'hide']()
const win = windowManager.get(IWindowList.SETTING_WINDOW)
if (!visible) {
win?.show()
win?.focus()
win?.setSkipTaskbar(true)
} else {
win?.setSkipTaskbar(false)
}
})
export {
systemRouter
}
@@ -0,0 +1,77 @@
import fs from 'fs-extra'
import path from 'path'
import { dbPathChecker, defaultConfigPath } from '~/main/apis/core/datastore/dbChecker'
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static'
import { sendToolboxResWithType } from './utils'
import { T } from '~/main/i18n'
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD)
const defaultClipboardImagePath = path.join(defaultConfigPath, CLIPBOARD_IMAGE_FOLDER)
export const checkClipboardUploadMap: IToolboxCheckerMap<
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async (event) => {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
const configFilePath = dbPathChecker()
if (fs.existsSync(configFilePath)) {
const dirPath = path.dirname(configFilePath)
const clipboardImagePath = path.join(dirPath, CLIPBOARD_IMAGE_FOLDER)
if (fs.existsSync(clipboardImagePath)) {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_TIPS', {
path: clipboardImagePath
}),
value: clipboardImagePath
})
} else {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS', {
path: clipboardImagePath
}),
value: path.dirname(clipboardImagePath)
})
}
} else {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS', {
path: defaultClipboardImagePath
}),
value: path.dirname(defaultClipboardImagePath)
})
}
}
}
export const fixClipboardUploadMap: IToolboxFixMap<
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async () => {
const configFilePath = dbPathChecker()
const dirPath = path.dirname(configFilePath)
const clipboardImagePath = path.join(dirPath, CLIPBOARD_IMAGE_FOLDER)
try {
fs.mkdirsSync(clipboardImagePath)
return {
type: IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD,
status: IToolboxItemCheckStatus.SUCCESS
}
} catch (e) {
return {
type: IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD,
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS', {
path: clipboardImagePath
}),
value: path.dirname(clipboardImagePath)
}
}
}
}
@@ -0,0 +1,87 @@
import fs from 'fs-extra'
import { IpcMainEvent } from 'electron'
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
import { sendToolboxResWithType } from './utils'
import { dbPathChecker } from '~/main/apis/core/datastore/dbChecker'
import { GalleryDB, DB_PATH } from '~/main/apis/core/datastore'
import path from 'path'
import { T } from '~/main/i18n'
export const checkFileMap: IToolboxCheckerMap<
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
> = {
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async (event: IpcMainEvent) => {
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_CONFIG_FILE_BROKEN)
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
const configFilePath = dbPathChecker()
try {
if (fs.existsSync(configFilePath)) {
await fs.readJSON(configFilePath)
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_CONFIG_FILE_PATH_TIPS', {
path: configFilePath
}),
value: configFilePath
})
}
} catch (e) {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_CONFIG_FILE_BROKEN_TIPS'),
value: path.dirname(configFilePath)
})
}
},
[IToolboxItemType.IS_GALLERY_FILE_BROKEN]: async (event) => {
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_GALLERY_FILE_BROKEN)
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
const galleryDB = GalleryDB.getInstance()
if (galleryDB.errorList.length === 0) {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS', {
path: DB_PATH
}),
value: path.dirname(DB_PATH)
})
} else {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_GALLERY_FILE_BROKEN_TIPS'),
value: path.dirname(DB_PATH)
})
}
}
}
export const fixFileMap: IToolboxFixMap<
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
> = {
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async () => {
try {
fs.unlinkSync(dbPathChecker())
} catch (e) {
// do nothing
}
return {
type: IToolboxItemType.IS_CONFIG_FILE_BROKEN,
status: IToolboxItemCheckStatus.SUCCESS
}
},
[IToolboxItemType.IS_GALLERY_FILE_BROKEN]: async () => {
try {
fs.unlinkSync(DB_PATH)
} catch (e) {
// do nothing
}
return {
type: IToolboxItemType.IS_GALLERY_FILE_BROKEN,
status: IToolboxItemCheckStatus.SUCCESS
}
}
}
@@ -0,0 +1,92 @@
import fs from 'fs-extra'
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
import { sendToolboxResWithType } from './utils'
import tunnel from 'tunnel'
import { dbPathChecker } from '~/main/apis/core/datastore/dbChecker'
import { IConfig } from 'picgo'
import axios, { AxiosRequestConfig } from 'axios'
import { T } from '~/main/i18n'
const getProxy = (proxyStr: string): AxiosRequestConfig['proxy'] | false => {
if (proxyStr) {
try {
const proxyOptions = new URL(proxyStr)
return {
host: proxyOptions.hostname,
port: parseInt(proxyOptions.port || '0', 10),
protocol: proxyOptions.protocol
}
} catch (e) {
}
}
return false
}
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
export const checkProxyMap: IToolboxCheckerMap<
IToolboxItemType.HAS_PROBLEM_WITH_PROXY
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async (event) => {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
const configFilePath = dbPathChecker()
if (fs.existsSync(configFilePath)) {
let config: IConfig | undefined
try {
config = await fs.readJSON(configFilePath) as IConfig
} catch (e) {
}
if (!config) {
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
})
}
const proxy = config.picBed?.proxy
if (!proxy) {
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
})
} else {
const proxyOptions = getProxy(proxy)
if (!proxyOptions) {
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT')
})
} else {
const httpsAgent = tunnel.httpsOverHttp({
proxy: {
host: proxyOptions.host,
port: proxyOptions.port
}
})
try {
await axios.get('https://www.google.com', {
httpsAgent
})
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_PROXY_SUCCESS_TIPS')
})
} catch (e) {
console.log(e)
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING')
})
}
}
}
}
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
})
}
}
@@ -0,0 +1,48 @@
import { IRPCActionType, IToolboxItemType } from '~/universal/types/enum'
import { RPCRouter } from '../../router'
import { checkFileMap, fixFileMap } from './checkFile'
import { checkClipboardUploadMap, fixClipboardUploadMap } from './checkClipboardUpload'
import { checkProxyMap } from './checkProxy'
const toolboxRouter = new RPCRouter()
const toolboxCheckMap: Partial<IToolboxCheckerMap<IToolboxItemType>> = {
...checkFileMap,
...checkClipboardUploadMap,
...checkProxyMap
}
const toolboxFixMap: Partial<IToolboxFixMap<IToolboxItemType>> = {
...fixFileMap,
...fixClipboardUploadMap
}
toolboxRouter
.add(IRPCActionType.TOOLBOX_CHECK, async (args, event) => {
const [type] = args as IToolboxCheckArgs
if (type) {
const handler = toolboxCheckMap[type]
if (handler) {
handler(event)
}
} else {
// do check all
for (const key in toolboxCheckMap) {
const handler = toolboxCheckMap[key as IToolboxItemType]
if (handler) {
handler(event)
}
}
}
})
.add(IRPCActionType.TOOLBOX_CHECK_FIX, async (args, event) => {
const [type] = args as IToolboxCheckArgs
const handler = toolboxFixMap[type]
if (handler) {
return await handler(event)
}
})
export {
toolboxRouter
}
@@ -0,0 +1,9 @@
import { IpcMainEvent } from 'electron'
import { IRPCActionType, IToolboxItemType } from '~/universal/types/enum'
export const sendToolboxResWithType = (type: IToolboxItemType) => (event: IpcMainEvent, res?: Omit<IToolboxCheckRes, 'type'>) => {
return event.sender.send(IRPCActionType.TOOLBOX_CHECK_RES, {
...res,
type
})
}
+16
View File
@@ -0,0 +1,16 @@
import { IRPCActionType } from '~/universal/types/enum'
import { RPCRouter } from '../router'
import { getLatestVersion } from '~/main/utils/getLatestVersion'
const versionRouter = new RPCRouter()
versionRouter
.add(IRPCActionType.GET_LATEST_VERSION, async (args) => {
const [type] = args as IGetLatestVersionArgs
const config = await getLatestVersion(type)
return config
})
export {
versionRouter
}
+91
View File
@@ -0,0 +1,91 @@
import yaml from 'js-yaml'
import { ObjectAdapter, I18n } from '@picgo/i18n'
import path from 'path'
import fs from 'fs-extra'
import { getStaticPath } from '#/utils/staticPath'
import { builtinI18nList } from '#/i18n'
class I18nManager {
private i18n: I18n | null = null
private builtinI18nFolder = getStaticPath('i18n')
private outerI18nFolder = ''
private localesMap: Map<string, ILocales> = new Map()
private currentLanguage: string = 'zh-CN'
readonly defaultLanguage: string = 'zh-CN'
private i18nFileList: II18nItem[] = builtinI18nList
setOuterI18nFolder (folder: string) {
this.outerI18nFolder = folder
}
addI18nFile (file: string, label: string) {
this.i18nFileList.push({
label,
value: file
})
}
private getLocales (lang: string): ILocales {
if (this.localesMap.has(lang)) {
return this.localesMap.get(lang)!
}
let localesPath = path.join(this.builtinI18nFolder, `${lang}.yml`)
if (!fs.existsSync(localesPath)) {
localesPath = path.join(this.outerI18nFolder, `${lang}.yml`)
if (!fs.existsSync(localesPath)) {
localesPath = path.join(this.builtinI18nFolder, `${this.defaultLanguage}.yml`)
}
}
try {
const localesString = fs.readFileSync(localesPath, 'utf8')
const locales = yaml.load(localesString) as unknown as ILocales
this.localesMap.set(lang, locales)
return locales
} catch (e) {
console.error(e)
// if error, use default language
localesPath = path.join(this.builtinI18nFolder, `${this.defaultLanguage}.yml`)
const localesString = fs.readFileSync(localesPath, 'utf8')
const locales = yaml.load(localesString) as unknown as ILocales
this.localesMap.set(lang, locales)
return locales
}
}
setCurrentLanguage (lang: string) {
const locales = this.getLocales(lang)
this.currentLanguage = lang
this.initI18n(lang, locales)
}
private initI18n (lang: string = this.defaultLanguage, locales: ILocales) {
const objectAdapter = new ObjectAdapter({
[lang]: locales
})
this.i18n = new I18n({
adapter: objectAdapter,
defaultLanguage: lang
})
}
T (key: ILocalesKey, args: IStringKeyMap = {}): string {
return this.i18n?.translate(key, args) || key
}
get languageList () {
return this.i18nFileList
}
getCurrentLocales () {
return {
lang: this.currentLanguage,
locales: this.getLocales(this.currentLanguage)
}
}
}
export const T = (key: ILocalesKey, args: IStringKeyMap = {}): string => {
return i18nManager.T(key, args)
}
export const i18nManager = new I18nManager()
+26 -19
View File
@@ -1,27 +1,16 @@
import fse from 'fs-extra'
import path from 'path'
import dayjs from 'dayjs'
import util from 'util'
import { dbPathDir } from 'apis/core/datastore/dbChecker'
const STORE_PATH = dbPathDir()
const LOG_PATH = path.join(STORE_PATH, '/picgo.log')
import { app } from 'electron'
import { getLogger } from 'apis/core/utils/localLogger'
const STORE_PATH = app.getPath('userData')
const LOG_PATH = path.join(STORE_PATH, 'picgo-gui-local.log')
const logger = getLogger(LOG_PATH)
// since the error may occur in picgo-core
// so we can't use the log from picgo
export const loggerWriter = (error: Error) => {
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicGo ERROR] startup error`
if (error?.stack) {
log += `\n------Error Stack Begin------\n${util.format(error.stack)}\n-------Error Stack End-------\n`
} else {
const msg = JSON.stringify(error)
log += `${msg}\n`
}
fse.appendFileSync(LOG_PATH, log)
}
const handleProcessError = (error: Error) => {
console.error(error)
loggerWriter(error)
const handleProcessError = (error: Error | string) => {
logger('error', error)
}
process.on('uncaughtException', error => {
@@ -31,3 +20,21 @@ process.on('uncaughtException', error => {
process.on('unhandledRejection', (error: any) => {
handleProcessError(error)
})
// thanks to https://github.com/camunda/camunda-modeler/pull/3314
function bootstrapEPIPESuppression () {
let suppressing = false
function logEPIPEErrorOnce () {
if (suppressing) {
return
}
suppressing = true
handleProcessError('Detected EPIPE error; suppressing further EPIPE errors')
}
require('epipebomb')(process.stdout, logEPIPEErrorOnce)
require('epipebomb')(process.stderr, logEPIPEErrorOnce)
}
bootstrapEPIPESuppression()
+18
View File
@@ -0,0 +1,18 @@
// TODO: so how to import pure esm module in electron main process????? help wanted
// just copy the fix-path because I can't import pure ESM module in electron main process
const shellPath = require('shell-path')
export default function fixPath () {
if (process.platform === 'win32') {
return
}
process.env.PATH = shellPath.sync() || [
'./node_modules/.bin',
'/.nodebrew/current/bin',
'/usr/local/bin',
process.env.PATH
].join(':')
}
+39 -22
View File
@@ -5,15 +5,11 @@ import {
protocol,
Notification
} from 'electron'
import {
createProtocol
} from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import beforeOpen from '~/main/utils/beforeOpen'
import fixPath from 'fix-path'
import ipcList from '~/main/events/ipcList'
import busEventList from '~/main/events/busEventList'
import { IWindowList } from 'apis/app/window/constants'
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
import windowManager from 'apis/app/window/windowManager'
import {
updateShortKeyFromVersion212,
@@ -24,7 +20,7 @@ import {
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import {
createTray
createTray, handleDockIcon
} from 'apis/app/system'
import server from '~/main/server/index'
import updateChecker from '~/main/utils/updateChecker'
@@ -32,11 +28,16 @@ import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import { getUploadFiles } from '~/main/utils/handleArgv'
import db, { GalleryDB } from '~/main/apis/core/datastore'
import bus from '@core/bus'
import { privacyManager } from '~/main/utils/privacyManager'
import logger from 'apis/core/picgo/logger'
import picgo from 'apis/core/picgo'
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'
import { initStaticPath, isDev } from '../utils/env'
const isDevelopment = process.env.NODE_ENV !== 'production'
const isDevelopment = isDev
const handleStartUpFiles = (argv: string[], cwd: string) => {
const files = getUploadFiles(argv, cwd, logger)
@@ -58,9 +59,10 @@ const handleStartUpFiles = (argv: string[], cwd: string) => {
class LifeCycle {
private async beforeReady () {
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
// fix the $PATH in macOS
// fix the $PATH in macOS & linux
fixPath()
beforeOpen()
initI18n()
ipcList.listen()
busEventList.listen()
updateShortKeyFromVersion212(db, db.get('settings.shortKey'))
@@ -70,22 +72,32 @@ class LifeCycle {
private onReady () {
const readyFunction = async () => {
console.log('on ready')
createProtocol('picgo')
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
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())
}
}
const res = await privacyManager.init()
if (!res) {
return app.quit()
}
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)
updateChecker()
// 不需要阻塞
@@ -104,12 +116,10 @@ class LifeCycle {
notice.show()
}
}
await remoteNoticeHandler.init()
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
}
if (!app.isReady()) {
app.on('ready', readyFunction)
} else {
readyFunction()
}
app.whenReady().then(readyFunction)
}
private onRunning () {
@@ -127,13 +137,19 @@ class LifeCycle {
}
})
app.on('activate', () => {
createProtocol('picgo')
if (!windowManager.has(IWindowList.TRAY_WINDOW)) {
windowManager.create(IWindowList.TRAY_WINDOW)
}
if (!windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.create(IWindowList.SETTING_WINDOW)
}
// click dock to open setting window
if (isMacOS) {
handleDockIcon()
if (db.get('settings.showDockIcon') !== false) {
windowManager.get(IWindowList.SETTING_WINDOW)?.show()
}
}
})
app.setLoginItemSettings({
openAtLogin: db.get('settings.autoStart') || false
@@ -180,6 +196,7 @@ class LifeCycle {
if (!gotTheLock) {
app.quit()
} else {
initStaticPath()
await this.beforeReady()
this.onReady()
this.onRunning()
+14 -6
View File
@@ -2,7 +2,9 @@ import { DBStore } from '@picgo/store'
import ConfigStore from '~/main/apis/core/datastore'
import path from 'path'
import fse from 'fs-extra'
import PicGoCore from '#/types/picgo'
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 极端情况可能会出现配置不存在,需要重新写入
@@ -11,18 +13,18 @@ const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IS
enable: true,
key: 'CommandOrControl+Shift+P',
name: 'upload',
label: '快捷上传'
label: T('QUICK_UPLOAD')
}
db.set('settings.shortKey[picgo:upload]', defaultShortKeyConfig)
return true
}
if (shortKeyConfig.upload) {
// @ts-ignore
shortKeyConfig['picgo:upload'] = {
shortKeyConfig[SHORTKEY_COMMAND_UPLOAD] = {
enable: true,
key: shortKeyConfig.upload,
name: 'upload',
label: '快捷上传'
label: T('QUICK_UPLOAD')
}
// @ts-ignore
delete shortKeyConfig.upload
@@ -33,7 +35,12 @@ const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IS
}
const migrateGalleryFromVersion230 = async (configDB: typeof ConfigStore, galleryDB: DBStore, picgo: PicGoCore) => {
const originGallery: ImgInfo[] = configDB.get('uploaded')
const originGallery: ImgInfo[] = picgo.getConfig('uploaded')
// if hasMigrate, we don't need to migrate
const hasMigrate: boolean = configDB.get('__migrateUploaded')
if (hasMigrate) {
return
}
const configPath = configDB.getConfigPath()
const configBakPath = path.join(path.dirname(configPath), 'config.bak.json')
// migrate gallery from config to gallery db
@@ -43,7 +50,8 @@ const migrateGalleryFromVersion230 = async (configDB: typeof ConfigStore, galler
}
await galleryDB.insertMany(originGallery)
picgo.saveConfig({
uploaded: []
uploaded: [],
__migrateUploaded: true
})
}
}
+10 -1
View File
@@ -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
@@ -39,17 +40,25 @@ class Server {
}
private handleRequest = (request: http.IncomingMessage, response: http.ServerResponse) => {
if (request.method === 'OPTIONS') {
handleResponse({
response
})
return
}
if (request.method === 'POST') {
if (!routers.getHandler(request.url!)) {
logger.warn(`[PicGo Server] don't support [${request.url}] url`)
handleResponse({
response,
statusCode: 404,
header: {},
body: {
success: false
}
})
} else if (formUploader.isFileUpload(request)) {
formUploader.handleFileUpload(request, response)
} else {
let body: string = ''
let postObj: IObj
+125
View File
@@ -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()
+2
View File
@@ -24,6 +24,7 @@ router.post('/upload', async ({
// upload with clipboard
logger.info('[PicGo Server] upload clipboard file')
const res = await uploadClipboardFiles()
logger.info('[PicGo Server] upload result:', res)
if (res) {
handleResponse({
response,
@@ -51,6 +52,7 @@ router.post('/upload', async ({
})
const win = windowManager.getAvailableWindow()
const res = await uploadChoosedFiles(win.webContents, pathList)
logger.info('[PicGo Server] upload result', res.join(' ; '))
if (res.length) {
handleResponse({
response,
+4 -1
View File
@@ -4,7 +4,10 @@ export const handleResponse = ({
response,
statusCode = 200,
header = {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'access-control-allow-headers': '*',
'access-control-allow-methods': 'POST, GET, OPTIONS',
'access-control-allow-origin': '*'
},
body = {
success: false
+78 -22
View File
@@ -2,6 +2,10 @@ import fs from 'fs-extra'
import path from 'path'
import os from 'os'
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
import yaml from 'js-yaml'
import { i18nManager } from '~/main/i18n'
// import { ILocales } from '~/universal/types/i18n'
import { getStaticPath } from '#/utils/staticPath'
const configPath = dbPathChecker()
const CONFIG_DIR = path.dirname(configPath)
@@ -11,24 +15,62 @@ function beforeOpen () {
resolveMacWorkFlow()
}
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 右键菜单
*/
function resolveMacWorkFlow () {
const dest = `${os.homedir}/Library/Services/Upload pictures with PicGo.workflow`
const dest = `${os.homedir()}/Library/Services/Upload pictures with PicGo.workflow`
if (fs.existsSync(dest)) {
return true
} else {
try {
fs.copySync(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
copyFileOutsideOfElectronAsar(getStaticPath('Upload pictures with PicGo.workflow'), dest)
} catch (e) {
console.log(e)
}
}
}
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
try {
const file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
const file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
if (!file1 || !file2 || !file1.equals(file2)) {
fs.copyFileSync(filePath1, filePath2)
}
} catch (e) {
console.error(e)
fs.copyFileSync(filePath1, filePath2)
}
}
/**
* 初始化剪贴板生成图片的脚本
*/
@@ -44,36 +86,50 @@ function resolveClipboardImageGenerator () {
})
}
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
try {
const file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
const file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
if (!file1 || !file2 || !file1.equals(file2)) {
fs.copyFileSync(filePath1, filePath2)
}
} catch (e) {
console.error(e)
fs.copyFileSync(filePath1, filePath2)
}
}
function getClipboardFiles () {
const files = [
'/linux.sh',
'/mac.applescript',
'/windows.ps1',
'/windows10.ps1',
'/wsl.sh'
'linux.sh',
'mac.applescript',
'windows.ps1',
'windows10.ps1',
'wsl.sh'
]
return files.map(item => {
return {
origin: path.join(__static, item),
origin: getStaticPath(item),
dest: path.join(CONFIG_DIR, item)
}
})
}
}
/**
* 初始化其他语言文件
*/
function resolveOtherI18nFiles () {
const i18nFolder = path.join(CONFIG_DIR, 'i18n')
if (!fs.pathExistsSync(i18nFolder)) {
fs.mkdirSync(i18nFolder)
}
i18nManager.setOuterI18nFolder(i18nFolder)
const i18nFiles = fs.readdirSync(path.join(CONFIG_DIR, 'i18n'), {
withFileTypes: true
})
i18nFiles.forEach(item => {
if (item.isFile() && item.name?.endsWith('.yml')) {
const i18nFilePath = path.join(i18nFolder, item.name)
const i18nFile = fs.readFileSync(i18nFilePath, 'utf8')
try {
const i18nFileObj = yaml.load(i18nFile) as unknown as ILocales
if (i18nFileObj?.LANG_DISPLAY_LABEL) {
i18nManager.addI18nFile(item.name.replace('.yml', ''), i18nFileObj.LANG_DISPLAY_LABEL)
}
} catch (e) {
console.error(e)
}
}
})
}
export default beforeOpen
@@ -0,0 +1,28 @@
import path from 'path'
import fs from 'fs-extra'
import { getFormImageFolderPath } from '@core/datastore/dbChecker'
import logger from '@core/picgo/logger'
export const cleanupFormUploaderFiles = (fileInfoList?: string[] | ImgInfo[]): void => {
const formImageFolderPath = getFormImageFolderPath()
if (Array.isArray(fileInfoList)) {
fileInfoList.forEach(async fileInfo => {
const filePath = typeof fileInfo === 'string' ? fileInfo : fileInfo?.imgPath
if (!filePath) {
return
}
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)
}
})
}
}
+73 -5
View File
@@ -1,8 +1,11 @@
import fs from 'fs-extra'
import db from '~/main/apis/core/datastore'
import { clipboard, Notification, dialog } from 'electron'
import { handleUrlEncode } from '~/universal/utils/common'
import { readClipboardFilePaths } from 'clip-filepaths'
export const handleCopyUrl = (str: string): void => {
if (db.get('settings.autoCopy') !== false) {
if (db.get('settings.autoCopyUrl') !== false) {
clipboard.writeText(str)
}
}
@@ -14,16 +17,21 @@ export const handleCopyUrl = (str: string): void => {
export const showNotification = (options: IPrivateShowNotificationOption = {
title: '',
body: '',
clickToCopy: false
clickToCopy: false,
copyContent: '',
clickFn: () => {}
}) => {
const notification = new Notification({
title: options.title,
body: options.body,
icon: options.icon || undefined
body: options.body
// icon: options.icon || undefined
})
const handleClick = () => {
if (options.clickToCopy) {
clipboard.writeText(options.body)
clipboard.writeText(options.copyContent || options.body)
}
if (options.clickFn) {
options.clickFn()
}
}
notification.once('click', handleClick)
@@ -69,3 +77,63 @@ export const calcDurationRange = (duration: number) => {
// max range
return 100000
}
/**
* macOS public.file-url will get encoded file path,
* so we need to decode it
*/
export const ensureFilePath = (filePath: string, prefix = 'file://'): string => {
filePath = filePath.replace(prefix, '')
if (fs.existsSync(filePath)) {
return `${prefix}${filePath}`
}
filePath = decodeURIComponent(filePath)
if (fs.existsSync(filePath)) {
return `${prefix}${filePath}`
}
return ''
}
/**
* for builtin clipboard to get image path from clipboard
* @returns
*/
export const getClipboardFilePathList = (): string[] => {
const { filePaths } = readClipboardFilePaths()
return filePaths
}
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(/\/$/, '')
}
+31
View File
@@ -0,0 +1,31 @@
import path from 'path'
import { app } from 'electron'
import { pathToFileURL } from 'url'
export const isDev = !app.isPackaged
const defaultStaticPath = process.env.STATIC_PATH || (isDev
? path.join(process.cwd(), 'public')
: path.join(process.resourcesPath, 'public'))
process.env.STATIC_PATH = defaultStaticPath
const rendererHtmlPath = path.join(__dirname, '../renderer/index.html')
export const initStaticPath = () => defaultStaticPath
export const getRendererBaseUrl = () => {
if (isDev && process.env.ELECTRON_RENDERER_URL) {
return process.env.ELECTRON_RENDERER_URL
}
return pathToFileURL(rendererHtmlPath).toString()
}
export const buildRendererUrl = (hash?: string) => {
const base = getRendererBaseUrl()
if (!hash) return base
const cleanedHash = hash.startsWith('#') ? hash : `#${hash}`
return `${base}${cleanedHash}`
}
export const getStaticPath = () => process.env.STATIC_PATH || defaultStaticPath
+34
View File
@@ -0,0 +1,34 @@
// for referer policy, we can't use it in renderer
import axios from 'axios'
import { RELEASE_URL, RELEASE_URL_BACKUP } from '../../universal/utils/static'
import yaml from 'js-yaml'
export const getLatestVersion = async (isCheckBetaUpdate: boolean = false) => {
let res: string = ''
try {
res = await axios.get(RELEASE_URL, {
headers: {
Referer: 'https://github.com'
}
}).then(r => {
const list = r.data as IStringKeyMap[]
if (isCheckBetaUpdate) {
const betaList = list.filter(item => item.name.includes('beta'))
return betaList[0].name
}
const normalList = list.filter(item => !item.name.includes('beta'))
return normalList[0].name
}).catch(async () => {
const result = await axios.get(isCheckBetaUpdate ? `${RELEASE_URL_BACKUP}/latest.beta.yml` : `${RELEASE_URL_BACKUP}/latest.yml`, {
headers: {
Referer: 'https://github.com'
}
})
const r = yaml.load(result.data) as IStringKeyMap
return r.version
})
} catch (err) {
console.log(err)
}
return res
}
+97
View File
@@ -0,0 +1,97 @@
// fork from https://github.com/sindresorhus/macos-version
// cause I can't change it to common-js module
import process from 'process'
import fs from 'fs'
import semver from 'semver'
export const isMacOS = process.platform === 'darwin'
let version: string | undefined
const clean = (version: string) => {
const { length } = version.split('.')
if (length === 1) {
return `${version}.0.0`
}
if (length === 2) {
return `${version}.0`
}
return version
}
const parseVersion = (plist: string) => {
const matches = /<key>ProductVersion<\/key>\s*<string>([\d.]+)<\/string>/.exec(plist)
if (!matches) {
return
}
return matches[1].replace('10.16', '11')
}
export function macOSVersion (): string {
if (!isMacOS) {
return ''
}
if (!version) {
const file = fs.readFileSync('/System/Library/CoreServices/SystemVersion.plist', 'utf8')
const matches = parseVersion(file)
if (!matches) {
return ''
}
version = clean(matches)
}
return version
}
if (process.env.NODE_ENV === 'test') {
macOSVersion._parseVersion = parseVersion
}
export function isMacOSVersion (semverRange: string) {
if (!isMacOS) {
return false
}
semverRange = semverRange.replace('10.16', '11')
return semver.satisfies(macOSVersion(), clean(semverRange))
}
export function isMacOSVersionGreaterThanOrEqualTo (version: string) {
if (!isMacOS) {
return false
}
version = version.replace('10.16', '11')
return semver.gte(macOSVersion(), clean(version))
}
export function assertMacOSVersion (semverRange: string) {
semverRange = semverRange.replace('10.16', '11')
if (!isMacOSVersion(semverRange)) {
throw new Error(`Requires macOS ${semverRange}`)
}
}
export function assertMacOSVersionGreaterThanOrEqualTo (version: string) {
version = version.replace('10.16', '11')
if (!isMacOSVersionGreaterThanOrEqualTo(version)) {
throw new Error(`Requires macOS ${version} or later`)
}
}
export function assertMacOS () {
if (!isMacOS) {
throw new Error('Requires macOS')
}
}
+5
View File
@@ -10,6 +10,11 @@ const getPicBeds = () => {
name: picgo.helper.uploader.get(item)!.name || item,
visible: visible ? visible.visible : true
}
}).sort((a) => {
if (a.type === 'tcyun') {
return -1
}
return 0
}) as IPicBedType[]
return picBeds
}

Some files were not shown because too many files have changed in this diff Show More