Compare commits

...
152 Commits
Author SHA1 Message Date
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
PiEgg b93b4cffe2 🎉 Release: v2.3.1-beta.0 2022-01-05 22:58:53 +08:00
PiEgg f2a4197ca2 📦 Chore: add mac-arm64 build support 2022-01-05 12:54:02 +08:00
PiEgg 34b3656605 🐛 Fix: mini window drag bug 2022-01-05 12:18:50 +08:00
PiEgg ea20d3b971 📦 Chore: update electron from v6 -> v16 2022-01-04 23:59:35 +08:00
PiEgg 459953f391 🎉 Release: v2.3.0 2021-09-11 11:29:50 +08:00
PiEgg 75e3edcd87 Feature: add open devtool option 2021-08-29 19:04:43 +08:00
PiEgg 5895889059 🐛 Fix: shift key function in gallery page 2021-08-28 21:50:46 +08:00
PiEgg 58420c8c3b 📝 Docs: update FAQ 2021-08-28 17:36:14 +08:00
PiEgg 6c6f84779a 🐛 Fix: urlEncode bug when copy
ISSUES CLOSED: #731
2021-08-23 23:21:58 +08:00
PiEgg a676c083fe 🐛 Fix: some bugs
ISSUES CLOSED: #722
2021-08-21 10:42:52 +08:00
PiEgg 2d3e779e0b 🎉 Release: v2.3.0-beta.8 2021-08-13 20:38:50 +08:00
PiEgg 20d3cf987c 🐛 Fix: settings bug
ISSUES CLOSED: #710
2021-08-13 00:19:43 +08:00
PiEgg ae692632a5 🐛 Fix: upload clipboard images via http should return list
ISSUES CLOSED: #721
2021-08-12 22:17:14 +08:00
PiEgg 019efd354c 🎉 Release: v2.3.0-beta.7 2021-08-01 17:04:21 +08:00
PiEgg 7030f7a764 Feature: finish custom config path
ISSUES CLOSED: #255
2021-08-01 17:02:54 +08:00
PiEgg 96a63ea11a 🐛 Fix: bug of gallery db for plugin 2021-08-01 15:44:59 +08:00
PiEgg f1eb7f4d70 🐛 Fix: gallery db bug 2021-08-01 14:50:25 +08:00
PiEgg 6ddd660d89 Feature: add gallery db 2021-07-27 11:58:24 +08:00
PiEgg c70c3aff78 🚧 WIP: add gallery db 2021-07-27 00:15:11 +08:00
PiEgg 76964ff1a5 🚧 WIP: gallery db in progress 2021-07-25 23:25:36 +08:00
PiEgg 12cecc27e7 🐛 Fix: multiple uploading in the same time will cause rename failed 2021-07-14 23:57:09 +08:00
PiEgg bdf523a060 🐛 Fix: windows ia32 && x64 build options 2021-07-14 23:46:33 +08:00
PiEgg 49e5f343f4 🐛 Fix: enable plugin should reload
ISSUES CLOSED: #659
2021-07-10 19:38:05 +08:00
PiEgg 1657542144 Feature: add win32 support
ISSUES CLOSED: #632
2021-07-10 00:36:31 +08:00
PiEgg 8e5e9ec59a 🔨 Refactor: move guiApi to singleton 2021-07-10 00:25:34 +08:00
PiEgg 06b67e50b9 🐛 Fix: multiple uploading in the same time will cause output conflict
ISSUES CLOSED: #666
2021-05-09 10:52:14 +08:00
PiEgg cf895deb8d 🔨 Refactor: picgo server the way of uploading 2021-05-07 01:04:17 +08:00
PiEgg ab762ef465 🐛 Fix: uploader error in linux
ISSUES CLOSED: #627
2021-04-25 23:29:30 +08:00
PiEgg 92022a6e34 🐛 Fix: use uploader first 2021-04-24 18:16:13 +08:00
186 changed files with 20149 additions and 12507 deletions
+20 -7
View File
@@ -6,20 +6,33 @@ module.exports = {
env: {
node: true
},
parser: "vue-eslint-parser",
'extends': [
'plugin:vue/essential',
parser: 'vue-eslint-parser',
extends: [
'plugin:vue/vue3-recommended',
'@vue/standard',
'@vue/typescript'
],
'plugins': ['@typescript-eslint'],
plugins: ['@typescript-eslint'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
"indent": "off",
"@typescript-eslint/indent": ["error", 2]
indent: 'off',
'no-async-promise-executor': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/indent': ['error', 2],
'vue/no-v-html': 'off'
},
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']
}
-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!
+9 -3
View File
@@ -23,7 +23,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-10.15]
os: [ubuntu-latest, macos-11]
# create steps
steps:
@@ -35,7 +35,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
node-version: '16.x'
- name: Install system deps
if: matrix.os == 'ubuntu-latest'
@@ -50,6 +50,12 @@ jobs:
- name: Build & release app
run: |
npm run release
yarn release
yarn upload-dist
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
PICGO_ENV_COS_SECRET_ID: ${{ secrets.PICGO_ENV_COS_SECRET_ID }}
PICGO_ENV_COS_SECRET_KEY: ${{ secrets.PICGO_ENV_COS_SECRET_KEY }}
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 }}
+58
View File
@@ -0,0 +1,58 @@
# main.yml
# Workflow's name
name: Build
# Workflow's trigger
on: workflow_dispatch
# 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
runs-on: ${{ matrix.os }}
# create a build matrix for jobs
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-11]
# create steps
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
with:
node-version: '16.x'
- name: Install system deps
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils
# step3: yarn
- name: Yarn install
run: |
yarn
yarn global add xvfb-maybe
- name: Build & release app
run: |
yarn release
yarn upload-dist
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
PICGO_ENV_COS_SECRET_ID: ${{ secrets.PICGO_ENV_COS_SECRET_ID }}
PICGO_ENV_COS_SECRET_KEY: ${{ secrets.PICGO_ENV_COS_SECRET_KEY }}
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 }}
+5
View File
@@ -17,3 +17,8 @@ docs/dist/
.env.*.local
dist_electron/
test.js
.env
scripts/*.yml
#Electron-builder output
/dist_electron
+23 -25
View File
@@ -1,38 +1,36 @@
{
// 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"
],
"sourceMaps": true
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"preLaunchTask": "electron-debug",
"args": ["--remote-debugging-port=9223", "./dist_electron"],
"outFiles": ["${workspaceFolder}/dist_electron/**/*.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"]
}
]
}
+5 -3
View File
@@ -20,6 +20,8 @@
"stylusSupremacy.sortProperties": "grouped",
"stylusSupremacy.quoteChar": "\"",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
"prettier.enable": false
}
+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\\."
}
}
}
]
}
+363 -3
View File
@@ -1,3 +1,366 @@
# :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)
### :bug: Bug Fixes
* mini window drag bug ([34b3656](https://github.com/Molunerfinn/PicGo/commit/34b3656))
### :package: Chore
* add mac-arm64 build support ([f2a4197](https://github.com/Molunerfinn/PicGo/commit/f2a4197))
* update electron from v6 -> v16 ([ea20d3b](https://github.com/Molunerfinn/PicGo/commit/ea20d3b))
# :tada: 2.3.0 (2021-09-11)
### :sparkles: Features
* add open devtool option ([75e3edc](https://github.com/Molunerfinn/PicGo/commit/75e3edc))
### :bug: Bug Fixes
* shift key function in gallery page ([5895889](https://github.com/Molunerfinn/PicGo/commit/5895889))
* some bugs ([a676c08](https://github.com/Molunerfinn/PicGo/commit/a676c08)), closes [#722](https://github.com/Molunerfinn/PicGo/issues/722)
* urlEncode bug when copy ([6c6f847](https://github.com/Molunerfinn/PicGo/commit/6c6f847)), closes [#731](https://github.com/Molunerfinn/PicGo/issues/731)
### :pencil: Documentation
* update FAQ ([58420c8](https://github.com/Molunerfinn/PicGo/commit/58420c8))
# :tada: 2.3.0-beta.8 (2021-08-13)
### :bug: Bug Fixes
* settings bug ([20d3cf9](https://github.com/Molunerfinn/PicGo/commit/20d3cf9)), closes [#710](https://github.com/Molunerfinn/PicGo/issues/710)
* upload clipboard images via http should return list ([ae69263](https://github.com/Molunerfinn/PicGo/commit/ae69263)), closes [#721](https://github.com/Molunerfinn/PicGo/issues/721)
# :tada: 2.3.0-beta.7 (2021-08-01)
### :sparkles: Features
* add gallery db ([6ddd660](https://github.com/Molunerfinn/PicGo/commit/6ddd660))
* add win32 support ([1657542](https://github.com/Molunerfinn/PicGo/commit/1657542)), closes [#632](https://github.com/Molunerfinn/PicGo/issues/632)
* finish custom config path ([7030f7a](https://github.com/Molunerfinn/PicGo/commit/7030f7a)), closes [#255](https://github.com/Molunerfinn/PicGo/issues/255)
### :bug: Bug Fixes
* bug of gallery db for plugin ([96a63ea](https://github.com/Molunerfinn/PicGo/commit/96a63ea))
* enable plugin should reload ([49e5f34](https://github.com/Molunerfinn/PicGo/commit/49e5f34)), closes [#659](https://github.com/Molunerfinn/PicGo/issues/659)
* gallery db bug ([f1eb7f4](https://github.com/Molunerfinn/PicGo/commit/f1eb7f4))
* multiple uploading in the same time will cause output conflict ([06b67e5](https://github.com/Molunerfinn/PicGo/commit/06b67e5)), closes [#666](https://github.com/Molunerfinn/PicGo/issues/666)
* multiple uploading in the same time will cause rename failed ([12cecc2](https://github.com/Molunerfinn/PicGo/commit/12cecc2))
* uploader error in linux ([ab762ef](https://github.com/Molunerfinn/PicGo/commit/ab762ef)), closes [#627](https://github.com/Molunerfinn/PicGo/issues/627)
* use uploader first ([92022a6](https://github.com/Molunerfinn/PicGo/commit/92022a6))
* windows ia32 && x64 build options ([bdf523a](https://github.com/Molunerfinn/PicGo/commit/bdf523a))
# :tada: 2.3.0-beta.6 (2021-04-24)
@@ -298,6 +661,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.
+73
View File
@@ -57,3 +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 的版本发现打开后没反应,请重启电脑即可。
+17 -30
View File
@@ -6,7 +6,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 +17,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 +48,21 @@ 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://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 | 国内下载速度可能会慢 |
| [腾讯云COS](https://cloud.tencent.com/product/cos) | https://github.com/Molunerfinn/PicGo/releases 附在更新日志结尾 | All | 感谢 [腾讯云COS](https://cloud.tencent.com/product/cos) 提供的赞助支持 |
| [山东大学镜像站](https://mirrors.sdu.edu.cn/) | https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo | All | 感谢 [山东大学镜像站](https://mirrors.sdu.edu.cn/) 提供的镜像支持 |
| [Scoop](https://scoop.sh/) | `scoop bucket add 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 +82,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)。
### 开发模式
@@ -116,8 +102,8 @@ npm run electron:serve # 重新进入开发模式
**注意**:如果你的网络环境不太好,可能会出现 `electron-builder` 下载 `electron` 二进制文件失败的情况。这个时候需要在 `npm run electron:build` 之前指定一下 `electron` 的源为国内源:
```bash
export ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/"
# 在 Windows 上,则可以使用 set ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/ (无需引号)
export ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"
# 在 Windows 上,则可以使用 set ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ (无需引号)
npm run electron:build
```
@@ -126,7 +112,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 +131,4 @@ npm run electron:build
[MIT](http://opensource.org/licenses/MIT)
Copyright (c) 2017 - 2019 Molunerfinn
Copyright (c) 2017 - Now Molunerfinn
+4 -3
View File
@@ -19,13 +19,14 @@ init:
- git config --global core.autocrlf input
install:
- ps: Install-Product node 12 x64
- ps: Install-Product node 16 x64
- git reset --hard HEAD
- yarn
- node --version
build_script:
#- yarn test
- npm run release
- yarn release
- yarn upload-dist
test: off
test: false
+2 -1
View File
@@ -1,5 +1,6 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
],
plugins: ['@babel/plugin-proposal-optional-chaining']
}
+4 -4
View File
@@ -22,7 +22,7 @@
.col-xs-10.col-xs-offset-1.col-md-7.col-md-offset-0
img(:src="item.url")
.col-xs-10.col-xs-offset-1.col-md-5.col-md-offset-0.display-list__content
.display-list__title {{ item.title }}
.display-list__title {{ item.title }}
.display-list__desc {{ item.desc }}
.row.ex-width.info
.col-xs-10.col-xs-offset-1
@@ -30,7 +30,7 @@
</template>
<script>
export default {
name: '',
name: 'HomePage',
data () {
return {
version: '',
@@ -94,7 +94,7 @@ html,
h1
margin 0
padding 0
font-family "Source Sans Pro","Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif
font-family "Source Sans Pro","Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif
#app
position relative
.mask
@@ -213,4 +213,4 @@ h1
font-size 25px
&__desc
margin-top 12px
</style>
</style>
+92 -61
View File
@@ -1,24 +1,100 @@
{
"name": "picgo",
"version": "2.3.0-beta.6",
"version": "2.4.0-beta.6",
"private": true,
"scripts": {
"dev": "vue-cli-service electron:serve",
"build": "vue-cli-service electron:build",
"lint": "vue-cli-service lint",
"bump": "bump-version",
"cz": "git-cz",
"dev": "vue-cli-service electron:serve",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"gen-i18n": "node ./scripts/gen-i18n-types.js",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
"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"
"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.0.10",
"@picgo/i18n": "^1.0.0",
"@picgo/store": "^2.1.0",
"axios": "^0.19.0",
"compare-versions": "^4.1.3",
"core-js": "^3.27.1",
"element-plus": "^2.3.7",
"epipebomb": "^1.0.0",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"marked": "^7.0.4",
"mitt": "^3.0.0",
"picgo": "^1.5.6",
"qrcode.vue": "^3.3.3",
"shell-path": "2.1.0",
"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",
"@babel/plugin-proposal-optional-chaining": "^7.16.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/lowdb": "^1.0.9",
"@types/node": "^16.10.2",
"@types/request-promise-native": "^1.0.17",
"@types/semver": "^7.3.8",
"@types/tunnel": "^0.0.3",
"@types/uuid": "^9.0.2",
"@types/write-file-atomic": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-router": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/eslint-config-standard": "^8.0.1",
"@vue/eslint-config-typescript": "^11.0.2",
"@vue/runtime-dom": "^3.2.45",
"autoprefixer": "^10.4.14",
"conventional-changelog": "^3.1.18",
"cz-customizable": "^6.2.0",
"dotenv": "^16.0.1",
"dpdm": "^3.13.1",
"electron": "^16.0.6",
"electron-devtools-installer": "^3.2.0",
"eslint": "^8.31.0",
"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": "^9.8.0",
"husky": "^3.1.0",
"postcss": "^8.4.23",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"tailwindcss": "^3.3.2",
"typescript": "^4.4.3",
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4"
},
"commitlint": {
"extends": [
"./node_modules/@picgo/bump-version/commitlint-picgo"
]
},
"config": {
"commitizen": {
@@ -28,58 +104,13 @@
"config": "./node_modules/@picgo/bump-version/.cz-config.js"
}
},
"commitlint": {
"extends": [
"./node_modules/@picgo/bump-version/commitlint-picgo"
]
},
"dependencies": {
"axios": "^0.19.0",
"core-js": "^3.3.2",
"element-ui": "^2.13.0",
"fix-path": "^2.1.0",
"fs-extra": "^8.1.0",
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"picgo": "^1.4.19",
"qrcode.vue": "^1.7.0",
"vue": "^2.6.10",
"vue-gallery": "^2.0.1",
"vue-lazyload": "^1.2.6",
"vue-router": "^3.1.3"
},
"devDependencies": {
"@commitlint/cli": "^8.2.0",
"@picgo/bump-version": "^1.0.3",
"@types/fs-extra": "^8.0.1",
"@types/inquirer": "^6.5.0",
"@types/lowdb": "^1.0.9",
"@types/node": "10.17.6",
"@types/request-promise-native": "^1.0.17",
"@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.0.0",
"@vue/cli-service": "^4.0.0",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/eslint-config-typescript": "^4.0.0",
"commitizen": "^4.0.3",
"conventional-changelog": "^3.1.18",
"cz-customizable": "^6.2.0",
"electron": "^6.0.0",
"electron-devtools-installer": "^3.2.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"husky": "^3.1.0",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"typescript": "~3.7.3",
"vue-cli-plugin-electron-builder": "^1.4.2",
"vue-property-decorator": "^8.3.0",
"vue-template-compiler": "^2.6.10"
"husky": {
"hooks": {
"commit-msg": "npm run lint:dpdm && commitlint -E HUSKY_GIT_PARAMS"
}
},
"resolutions": {
"@types/node": "12.0.2"
"@types/node": "^16.10.2",
"vue-cli-plugin-electron-builder/**/electron-builder": "23.3.3"
}
}
+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>
+30 -14
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-copy >/dev/null 2>&1 || { echo >&1 "no wl-clipboard"; exit 1; }
filePath=`wl-copy -o 2>/dev/null | grep ^file:// | cut -c8-`
if [ ! -n "$filePath" ] ;then
if
wl-copy -t image/png -o >/dev/null 2>&1
then
wl-copy -t image/png image/png -o >$1 2>/dev/null
echo $1
else
echo "no image"
fi
else
echo $filePath
fi
else
echo $filePath
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

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1672454579182" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2800" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M160 0h512l256 256v704c0 35.3472-28.6528 64-64 64H160c-35.3472 0-64-28.6528-64-64V64c0-35.3472 28.6528-64 64-64z" fill="#CCCCCC" p-id="2801"></path><path d="M672 0l256 256h-192c-35.3472 0-64-28.6528-64-64V0z" fill="#EAEAEA" p-id="2802"></path><path d="M384 499.2c0-25.6 5.12-46.08 10.24-58.88 5.12-12.8 15.36-25.6 28.16-35.84 12.8-12.8 25.6-20.48 43.52-25.6 15.36-5.12 30.72-7.68 48.64-7.68 35.84 0 64 10.24 89.6 30.72C627.2 422.4 640 448 640 481.28c0 15.36-5.12 28.16-10.24 40.96s-17.92 28.16-38.4 46.08-28.16 30.72-35.84 38.4c-7.68 7.68-10.24 17.92-15.36 28.16-5.12 10.24-2.56 17.92-2.56 43.52h-51.2c0-25.6 2.56-38.4 5.12-51.2s7.68-23.04 15.36-33.28 15.36-23.04 33.28-40.96c17.92-17.92 30.72-30.72 35.84-38.4 5.12-7.68 10.24-20.48 10.24-38.4s-7.68-30.72-20.48-43.52-30.72-20.48-53.76-20.48c-51.2 0-76.8 35.84-76.8 87.04h-51.2z m153.6 281.6h-51.2v-51.2h51.2v51.2z" fill="#FFFFFF" p-id="2803"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# grab the paths
scriptPath=$(echo $0 | awk '{ print substr( $0, 1, length($0)-6 ) }')"windows10.ps1"
imagePath=$(echo $1 | awk '{ print substr( $0, 1, length($0)-18 ) }')
imageName=$(echo $1 | awk '{ print substr( $0, length($0)-17, length($0) ) }')
# run the powershell script
res=$(powershell.exe -noprofile -noninteractive -nologo -sta -executionpolicy unrestricted -file $(wslpath -w $scriptPath) $(wslpath -w $imagePath)"\\"$imageName)
# note that there is a return symbol in powershell result
noImage=$(echo "no image\r")
# check whether image exists
if [ "$res" = "$noImage" ] ;then
echo "no image"
else
echo $(wslpath -u $res)
fi
+50
View File
@@ -0,0 +1,50 @@
// different platform has different format
// macos
const darwin = [{
appNameWithPrefix: 'PicGo-',
ext: '.dmg',
arch: '-arm64',
'version-file': 'latest-mac.yml'
}, {
appNameWithPrefix: 'PicGo-',
ext: '.dmg',
arch: '-x64',
'version-file': 'latest-mac.yml'
}]
const linux = [{
appNameWithPrefix: 'PicGo-',
ext: '.AppImage',
arch: '',
'version-file': 'latest-linux.yml'
}, {
appNameWithPrefix: 'picgo_',
ext: '.snap',
arch: '_amd64',
'version-file': 'latest-linux.yml'
}]
// windows
const win32 = [{
appNameWithPrefix: 'PicGo-Setup-',
ext: '.exe',
arch: '-ia32',
'version-file': 'latest.yml'
}, {
appNameWithPrefix: 'PicGo-Setup-',
ext: '.exe',
arch: '-x64',
'version-file': 'latest.yml'
}, {
appNameWithPrefix: 'PicGo-Setup-',
ext: '.exe',
arch: '', // 32 & 64
'version-file': 'latest.yml'
}]
module.exports = {
darwin,
linux,
win32
}
+23
View File
@@ -0,0 +1,23 @@
const pkg = require('../package.json')
const version = pkg.version
// TODO: use the same name format
const generateURL = (platform, ext, prefix = 'PicGo-') => {
return `https://picgo-release.molunerfinn.com/${version}/${prefix}${version}${platform}${ext}`
}
const platformExtList = [
['-arm64', '.dmg', 'PicGo-'],
['-x64', '.dmg', 'PicGo-'],
['', '.AppImage', 'PicGo-'],
['-ia32', '.exe', 'PicGo-Setup-'],
['-x64', '.exe', 'PicGo-Setup-'],
['', '.exe', 'PicGo-Setup-'],
['_amd64', '.snap', 'picgo_']
]
const links = platformExtList.map(([arch, ext, prefix]) => {
const markdownLink = `[${prefix}${version}${arch}${ext}](${generateURL(arch, ext, prefix)})`
return markdownLink
})
console.log(links.join('\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)
+175
View File
@@ -0,0 +1,175 @@
// 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_electron')
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 = '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 S3Options = {
credentials: {
accessKeyId: S3_SECRET_ID,
secretAccessKey: S3_SECRET_KEY
},
endpoint: `https://${S3_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]) {
let versionFileHasUploaded = false
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')
}
const client = new S3Client(S3Options)
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()
// upload version file
if (!versionFileHasUploaded) {
const uploadVersionFileToS3 = new Upload({
client,
params: {
Bucket: S3_BUCKET,
Key: `${versionFileName}`,
Body: fs.createReadStream(versionFilePath),
ContentType: mime.lookup(versionFileName)
}
})
console.log('[PicGo Version File] Uploading...', versionFileName)
await uploadVersionFileToS3.done()
versionFileHasUploaded = true
}
}
} else {
console.warn('platform not supported!', platform)
}
} catch (e) {
console.error(e)
}
}
const main = async () => {
await uploadDist()
}
main()
+40 -21
View File
@@ -1,23 +1,31 @@
import Vue from 'vue'
import './renderer/assets/css/tailwind.css'
import { createApp } from 'vue'
import App from './renderer/App.vue'
import router from './renderer/router'
import db from '#/datastore/index'
import ElementUI from 'element-ui'
import ElementUI from 'element-plus'
import 'element-plus/dist/index.css'
import { webFrame } from 'electron'
import 'element-ui/lib/theme-chalk/index.css'
import VueLazyLoad from 'vue-lazyload'
import VueLazyLoad from 'vue3-lazyload'
import axios from 'axios'
import mainMixin from './renderer/utils/mainMixin'
import bus from '@/utils/bus'
import { mainMixin } from './renderer/utils/mainMixin'
import { dragMixin } from '@/utils/mixin'
import { initTalkingData } from './renderer/utils/analytics'
import db from './renderer/utils/db'
import { i18nManager, T } from './renderer/i18n/index'
import { getConfig, saveConfig, sendToMain, triggerRPC } from '@/utils/dataSender'
import { store } from '@/store'
import vue3PhotoPreview from 'vue3-photo-preview'
import 'vue3-photo-preview/dist/index.css'
webFrame.setVisualZoomLevelLimits(1, 1)
webFrame.setLayoutZoomLevelLimits(0, 0)
Vue.config.productionTip = false
Vue.prototype.$builtInPicBed = [
// do here before vue init
// handleURLParams()
const app = createApp(App)
app.config.globalProperties.$builtInPicBed = [
'smms',
'weibo',
'imgur',
'qiniu',
'tcyun',
@@ -25,17 +33,28 @@ Vue.prototype.$builtInPicBed = [
'aliyun',
'github'
]
Vue.prototype.$db = db
Vue.prototype.$http = axios
Vue.prototype.$bus = bus
app.config.unwrapInjectedRef = true
Vue.use(ElementUI)
Vue.use(VueLazyLoad)
Vue.mixin(mainMixin)
app.config.globalProperties.$$db = db
app.config.globalProperties.$http = axios
app.config.globalProperties.$T = T
app.config.globalProperties.$i18n = i18nManager
app.config.globalProperties.getConfig = getConfig
app.config.globalProperties.triggerRPC = triggerRPC
app.config.globalProperties.saveConfig = saveConfig
app.config.globalProperties.sendToMain = sendToMain
new Vue({
router,
render: h => h(App)
}).$mount('#app')
app.mixin(mainMixin)
app.mixin(dragMixin)
app.use(VueLazyLoad, {
error: `file://${__static.replace(/\\/g, '/')}/unknown-file-type.svg`
})
app.use(ElementUI)
app.use(router)
app.use(store)
app.use(vue3PhotoPreview)
app.mount('#app')
initTalkingData()
+1 -1
View File
@@ -6,7 +6,7 @@ The lowest level APIs that are not dependent on each other. The upper APIs depen
## app
Provide key API interfaces for PicGo application, including uploader, window management, shortcut key system, etc
Provide key API interfaces for PicGo application, including uploader, window management, shortcut key system, remotes handler, etc
## gui
+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
}
+33 -11
View File
@@ -4,10 +4,12 @@ import {
} from 'electron'
import logger from '@core/picgo/logger'
import GuiApi from '../../gui'
import db from '#/datastore'
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
@@ -16,10 +18,12 @@ class ShortKeyHandler {
this.isInModifiedMode = flag
})
}
init () {
this.initBuiltInShortKey()
this.initPluginsShortKey()
}
private initBuiltInShortKey () {
const commands = db.get('settings.shortKey') as IShortKeyConfigs
Object.keys(commands)
@@ -27,17 +31,22 @@ 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`)
}
})
}
private initPluginsShortKey () {
// get enabled plugin
const pluginList = picgo.pluginLoader.getList()
for (let item of pluginList) {
for (const item of pluginList) {
const plugin = picgo.pluginLoader.getPlugin(item)
// if a plugin has commands
if (plugin && plugin.commands) {
@@ -46,7 +55,7 @@ class ShortKeyHandler {
continue
}
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
for (let cmd of commands) {
for (const cmd of commands) {
const command = `${item}:${cmd.name}`
if (db.has(`settings.shortKey[${command}]`)) {
const commandConfig = db.get(`settings.shortKey.${command}`) as IShortKeyConfig
@@ -63,6 +72,7 @@ class ShortKeyHandler {
}
}
}
private registerShortKey (config: IShortKeyConfig | IPluginShortKeyConfig, command: string, handler: IShortKeyHandler, writeFlag: boolean) {
shortKeyService.registerCommand(command, handler)
if (config.key) {
@@ -72,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({
@@ -85,6 +95,7 @@ class ShortKeyHandler {
})
}
}
// enable or disable shortKey
bindOrUnbindShortKey (item: IShortKeyConfig, from: string): boolean {
const command = `${from}:${item.name}`
@@ -108,6 +119,7 @@ class ShortKeyHandler {
}
}
}
// update shortKey bindings
updateShortKey (item: IShortKeyConfig, oldKey: string, from: string): boolean {
const command = `${from}:${item.name}`
@@ -121,22 +133,31 @@ class ShortKeyHandler {
})
return true
}
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) {
const guiApi = new GuiApi()
return handler(picgo, guiApi)
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}]`)
}
}
registerPluginShortKey (pluginName: string) {
const plugin = picgo.pluginLoader.getPlugin(pluginName)
if (plugin && plugin.commands) {
@@ -145,7 +166,7 @@ class ShortKeyHandler {
return
}
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
for (let cmd of commands) {
for (const cmd of commands) {
const command = `${pluginName}:${cmd.name}`
if (db.has(`settings.shortKey[${command}]`)) {
const commandConfig = db.get(`settings.shortKey[${command}]`) as IShortKeyConfig
@@ -156,6 +177,7 @@ class ShortKeyHandler {
}
}
}
unregisterPluginShortKey (pluginName: string) {
const commands = db.get('settings.shortKey') as IShortKeyConfigs
const keyList = Object.keys(commands)
@@ -169,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)
})
}
}
@@ -4,15 +4,18 @@ class ShortKeyService {
registerCommand (command: string, handler: IShortKeyHandler) {
this.commandList.set(command, handler)
}
unregisterCommand (command: string) {
this.commandList.delete(command)
}
getShortKeyHandler (command: string): IShortKeyHandler | null {
const handler = this.commandList.get(command)
if (handler) return handler
logger.warn(`cannot find command: ${command}`)
return null
}
getCommandList () {
return [...this.commandList.keys()]
}
+132 -77
View File
@@ -1,47 +1,35 @@
import fs from 'fs-extra'
import {
app,
Menu,
Tray,
dialog,
clipboard,
systemPreferences,
Notification
Notification,
nativeTheme
} from 'electron'
import uploader from 'apis/app/uploader'
import getPicBeds from '~/main/utils/getPicBeds'
import db from '#/datastore'
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 pasteTemplate from '#/utils/pasteTemplate'
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, 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'
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
? `${__static}/menubar-newdarwinTemplate.png`
: `${__static}/menubar.png`
} else {
return `${__static}/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,51 @@ 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(() => {
let img = clipboard.readImage()
let obj: ImgInfo[] = []
setTimeout(async () => {
const img = clipboard.readImage()
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
})
// https://github.com/electron/electron/issues/9035
const imgPath = clipboard.read('public.file-url')
if (imgPath) {
const decodePath = ensureFilePath(imgPath)
if (decodePath === imgPath) {
obj.push({
imgUrl: imgPath
})
} else {
if (decodePath !== '') {
// 带有中文的路径,无法直接被img.src所使用,会被转义
const base64 = await fs.readFile(
decodePath.replace('file://', ''),
{ encoding: 'base64' }
)
obj.push({
imgUrl: `data:image/png;base64,${base64}`
})
}
}
} else {
const imgUrl = img.toDataURL()
// console.log(imgUrl)
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,7 +216,7 @@ export function createTray () {
})
tray.on('drag-enter', () => {
if (systemPreferences.isDarkMode()) {
if (nativeTheme.shouldUseDarkColors) {
tray!.setImage(`${__static}/upload-dark.png`)
} else {
tray!.setImage(`${__static}/upload.png`)
@@ -198,9 +224,12 @@ export function createTray () {
})
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[]) => {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
@@ -210,51 +239,77 @@ export function createTray () {
if (imgs !== false) {
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[i]))
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()
}, i * 100)
db.insert('uploaded', imgs[i])
await GalleryDB.getInstance().insert(imgs[i])
}
handleCopyUrl(pasteText.join('\n'))
trayWindow.webContents.send('dragFiles', imgs)
}
})
// 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)
+44 -28
View File
@@ -3,37 +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 '#/utils/pasteTemplate'
import db from '#/datastore'
import { handleCopyUrl } from '~/main/utils/common'
import { handleUrlEncode } from '#/utils/common'
export const uploadClipboardFiles = async (): Promise<string> => {
import pasteTemplate from '~/main/utils/pasteTemplate'
import db, { GalleryDB } from '~/main/apis/core/datastore'
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()
let 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 trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
handleCopyUrl(pasteTemplate(pasteStyle, img[0]))
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()
db.insert('uploaded', img[0])
trayWindow.webContents.send('clipboardFiles', [])
trayWindow.webContents.send('uploadFiles', img)
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 ''
@@ -51,22 +66,23 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[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)
db.insert('uploaded', imgs[i])
result.push(handleUrlEncode(imgs[i].imgUrl!))
await GalleryDB.getInstance().insert(imgs[i])
result.push(handleUrlEncodeWithSetting(imgs[i].imgUrl!))
}
handleCopyUrl(pasteText.join('\n'))
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('uploadFiles', imgs)
// 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 {
+73 -42
View File
@@ -2,37 +2,36 @@ import {
Notification,
BrowserWindow,
ipcMain,
WebContents
WebContents,
clipboard
} from 'electron'
import dayjs from 'dayjs'
import picgo from '@core/picgo'
import db from '#/datastore'
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, getClipboardFilePath } 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, reject) => {
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'
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
const windowId = window.id
ipcMain.once(`rename${id}`, (evt: Event, newName: string) => {
ipcMain.once(`${RENAME_FILE_NAME}${id}`, (evt: Event, newName: string) => {
resolve(newName)
window.close()
})
window.on('close', () => {
resolve(null)
ipcMain.removeAllListeners(`rename${id}`)
ipcMain.removeAllListeners(`${RENAME_FILE_NAME}${id}`)
windowManager.deleteById(windowId)
})
})
@@ -54,25 +53,25 @@ const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) =>
class Uploader {
private webContents: WebContents | null = null
private uploading: boolean = false
// private uploading: boolean = false
constructor () {
this.init()
}
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', ctx => {
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 +85,19 @@ class Uploader {
let name: undefined | string | null
let fileName: string | undefined
if (autoRename) {
fileName = dayjs().add(index, 'second').format('YYYYMMDDHHmmss') + item.extname
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS') + 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,43 +112,70 @@ class Uploader {
return this
}
async upload (img?: IUploadOption): Promise<ImgInfo[]|false> {
if (this.uploading) {
showNotification({
title: '上传失败',
body: '前序上传还在继续,请稍后再试'
})
return Promise.resolve(false)
}
/**
* use electron's clipboard image to upload
*/
async uploadWithBuildInClipboard (): Promise<ImgInfo[]|false> {
let filePath = ''
try {
const imgPath = getClipboardFilePath()
if (!imgPath) {
const nativeImage = clipboard.readImage()
if (nativeImage.isEmpty()) {
return false
}
const buffer = nativeImage.toPNG()
const baseDir = picgo.baseDir
const fileName = `${dayjs().format('YYYYMMDDHHmmSSS')}.png`
filePath = path.join(baseDir, CLIPBOARD_IMAGE_FOLDER, fileName)
await writeFile(filePath, buffer)
return await this.upload([filePath])
} else {
return await this.upload([imgPath])
}
} 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()
this.uploading = true
const output = await picgo.upload(img)
this.uploading = false
if (Array.isArray(output) && output.every((item: ImgInfo) => item.imgUrl)) {
if (Array.isArray(output) && output.some((item: ImgInfo) => item.imgUrl)) {
if (this.webContents) {
handleTalkingData(this.webContents, {
fromClipboard: !img,
type: db.get('picBed.current') || 'smms',
type: db.get('picBed.uploader') || db.get('picBed.current') || 'smms',
count: img ? img.length : 1,
duration: Date.now() - startTime
} as IAnalyticsData)
}
return output
return output.filter(item => item.imgUrl)
} else {
return false
}
} catch (e) {
this.uploading = false
} catch (e: any) {
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)
}
}
}
+8 -11
View File
@@ -1,24 +1,21 @@
export enum IWindowList {
SETTING_WINDOW = 'SETTING_WINDOW',
TRAY_WINDOW = 'TRAY_WINDOW',
MINI_WINDOW = 'MINI_WINDOW',
RENAME_WINDOW = 'RENAME_WINDOW'
}
const isDevelopment = process.env.NODE_ENV !== 'production'
export const TRAY_WINDOW_URL = isDevelopment
? (process.env.WEBPACK_DEV_SERVER_URL as string)
: `picgo://./index.html`
: 'picgo://./index.html'
export const SETTING_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#main-page/upload`
: `picgo://./index.html#main-page/upload`
: 'picgo://./index.html#main-page/upload'
export const MINI_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page`
: `picgo://./index.html#mini-page`
: 'picgo://./index.html#mini-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`
: 'picgo://./index.html#rename-page'
export const TOOLBOX_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#toolbox-page`
: 'picgo://./index.html#toolbox-page'
+101 -17
View File
@@ -1,19 +1,46 @@
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 '#/datastore'
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 { URLSearchParams } from 'url'
const windowList = new Map<IWindowList, IWindowListItem>()
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,14 +55,16 @@ windowList.set(IWindowList.TRAY_WINDOW, {
transparent: true,
vibrancy: 'ultra-dark',
webPreferences: {
nodeIntegration: true,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true,
backgroundThrottling: false
backgroundThrottling: false,
webSecurity: false
}
}
},
callback (window) {
window.loadURL(TRAY_WINDOW_URL)
window.loadURL(handleWindowParams(TRAY_WINDOW_URL))
window.on('blur', () => {
window.hide()
})
@@ -46,6 +75,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,
@@ -57,10 +87,12 @@ windowList.set(IWindowList.SETTING_WINDOW, {
title: 'PicGo',
vibrancy: 'ultra-dark',
transparent: true,
skipTaskbar: !showDockIcon,
titleBarStyle: 'hidden',
webPreferences: {
backgroundThrottling: false,
nodeIntegration: true,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true,
webSecurity: false
}
@@ -71,11 +103,12 @@ windowList.set(IWindowList.SETTING_WINDOW, {
options.backgroundColor = '#3f3c37'
options.transparent = false
options.icon = `${__static}/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') {
@@ -93,10 +126,10 @@ windowList.set(IWindowList.MINI_WINDOW, {
isValid: process.platform !== 'darwin',
multiple: false,
options () {
let obj: IBrowserWindowOptions = {
const obj: IBrowserWindowOptions = {
height: 64,
width: 64,
show: process.platform === 'linux',
show: isLinux,
frame: false,
fullscreenable: false,
skipTaskbar: true,
@@ -105,18 +138,19 @@ windowList.set(IWindowList.MINI_WINDOW, {
icon: `${__static}/logo.png`,
webPreferences: {
backgroundThrottling: false,
nodeIntegration: true,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true
}
}
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))
}
})
@@ -124,7 +158,7 @@ windowList.set(IWindowList.RENAME_WINDOW, {
isValid: true,
multiple: true,
options () {
let options: IBrowserWindowOptions = {
const options: IBrowserWindowOptions = {
height: 175,
width: 300,
show: true,
@@ -132,7 +166,8 @@ windowList.set(IWindowList.RENAME_WINDOW, {
resizable: false,
vibrancy: 'ultra-dark',
webPreferences: {
nodeIntegration: true,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true,
backgroundThrottling: false
}
@@ -146,7 +181,7 @@ windowList.set(IWindowList.RENAME_WINDOW, {
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 }
@@ -164,4 +199,53 @@ 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,
title: `PicGo ${T('TOOLBOX')}`,
vibrancy: 'ultra-dark',
icon: `${__static}/logo.png`,
webPreferences: {
backgroundThrottling: false,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true,
webSecurity: false
}
}
if (process.platform !== 'darwin') {
options.backgroundColor = '#3f3c37'
options.autoHideMenuBar = true
options.transparent = false
}
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()
@@ -34,6 +31,7 @@ class WindowManager implements IWindowManager {
return null
}
}
get (name: IWindowList) {
if (this.has(name)) {
return this.windowMap.get(name)!
@@ -42,9 +40,11 @@ class WindowManager implements IWindowManager {
return window
}
}
has (name: IWindowList) {
return this.windowMap.has(name)
}
// useless
// delete (name: IWindowList) {
// const window = this.windowMap.get(name)
@@ -60,6 +60,7 @@ class WindowManager implements IWindowManager {
this.windowIdMap.delete(id)
}
}
getAvailableWindow () {
const miniWindow = this.windowMap.get(IWindowList.MINI_WINDOW)
if (miniWindow && miniWindow.isVisible()) {
+4 -4
View File
@@ -14,7 +14,7 @@ export const uploadWithClipboardFiles = (): Promise<{
success: boolean,
result?: string[]
}> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
bus.once(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, (result: string) => {
if (result) {
return resolve({
@@ -35,7 +35,7 @@ export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
success: boolean,
result?: string[]
}> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
bus.once(UPLOAD_WITH_FILES_RESPONSE, (result: string[]) => {
if (result.length) {
return resolve({
@@ -55,7 +55,7 @@ export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
// get available window id:
// miniWindow or settingWindow or trayWindow
export const getWindowId = (): Promise<number> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
bus.once(GET_WINDOW_ID_REPONSE, (id: number) => {
resolve(id)
})
@@ -65,7 +65,7 @@ export const getWindowId = (): Promise<number> => {
// get settingWindow id:
export const getSettingWindowId = (): Promise<number> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
bus.once(GET_SETTING_WINDOW_ID_RESPONSE, (id: number) => {
resolve(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`
+139
View File
@@ -0,0 +1,139 @@
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'
const STORE_PATH = APP.getPath('userData')
const configFilePath = path.join(STORE_PATH, 'data.json')
const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json')
export const defaultConfigPath = configFilePath
let _configFilePath = ''
let hasCheckPath = false
const errorMsg = {
broken: T('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT'),
brokenButBackup: T('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP')
}
/** ensure notification list */
if (!global.notificationList) global.notificationList = []
function dbChecker () {
if (process.type !== 'renderer') {
// db save bak
try {
const { dbPath, dbBackupPath } = getGalleryDBPath()
if (fs.existsSync(dbPath)) {
fs.copyFileSync(dbPath, dbBackupPath)
}
} catch (e) {
console.error(e)
}
const configFilePath = dbPathChecker()
if (!fs.existsSync(configFilePath)) {
return
}
let configFile: string = '{}'
const optionsTpl = {
title: T('TIPS_NOTICE'),
body: ''
}
// config save bak
try {
configFile = fs.readFileSync(configFilePath, { encoding: 'utf-8' })
JSON.parse(configFile)
} catch (e) {
fs.unlinkSync(configFilePath)
if (fs.existsSync(configFileBackupPath)) {
try {
configFile = fs.readFileSync(configFileBackupPath, { encoding: 'utf-8' })
JSON.parse(configFile)
writeFile.sync(configFilePath, configFile, { encoding: 'utf-8' })
const stats = fs.statSync(configFileBackupPath)
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) {
optionsTpl.body = errorMsg.broken
global.notificationList?.push(optionsTpl)
return
}
}
optionsTpl.body = errorMsg.broken
global.notificationList?.push(optionsTpl)
return
}
writeFile.sync(configFileBackupPath, configFile, { encoding: 'utf-8' })
}
}
/**
* Get config path
*/
function dbPathChecker (): string {
if (_configFilePath) {
return _configFilePath
}
// defaultConfigPath
_configFilePath = defaultConfigPath
// if defaultConfig path is not exit
// do not parse the content of config
if (!fs.existsSync(defaultConfigPath)) {
return _configFilePath
}
try {
const configString = fs.readFileSync(defaultConfigPath, { encoding: 'utf-8' })
const config = JSON.parse(configString)
const userConfigPath: string = config.configPath || ''
if (userConfigPath) {
if (fs.existsSync(userConfigPath) && userConfigPath.endsWith('.json')) {
_configFilePath = userConfigPath
return _configFilePath
}
}
return _configFilePath
} catch (e) {
const picgoLogPath = path.join(STORE_PATH, 'picgo-gui-local.log')
const logger = getLogger(picgoLogPath)
if (!hasCheckPath) {
const optionsTpl = {
title: T('TIPS_NOTICE'),
body: T('TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR')
}
global.notificationList?.push(optionsTpl)
hasCheckPath = true
}
logger('error', e)
_configFilePath = defaultConfigPath
return _configFilePath
}
}
function dbPathDir () {
return path.dirname(dbPathChecker())
}
function getGalleryDBPath (): {
dbPath: string
dbBackupPath: string
} {
const configPath = dbPathChecker()
const dbPath = path.join(path.dirname(configPath), 'picgo.db')
const dbBackupPath = path.join(path.dirname(dbPath), 'picgo.bak.db')
return {
dbPath,
dbBackupPath
}
}
export {
dbChecker,
dbPathChecker,
dbPathDir,
getGalleryDBPath
}
+94
View File
@@ -0,0 +1,94 @@
import fs from 'fs-extra'
import { dbPathChecker, dbPathDir, getGalleryDBPath } from './dbChecker'
import { DBStore, JSONStore } from '@picgo/store'
import { T } from '~/main/i18n'
const STORE_PATH = dbPathDir()
if (!fs.pathExistsSync(STORE_PATH)) {
fs.mkdirpSync(STORE_PATH)
}
const CONFIG_PATH: string = dbPathChecker()
export const DB_PATH: string = getGalleryDBPath().dbPath
class ConfigStore {
private db: JSONStore
constructor () {
this.db = new JSONStore(CONFIG_PATH)
if (!this.db.has('picBed')) {
this.db.set('picBed', {
current: 'smms', // deprecated
uploader: 'smms',
smms: {
token: ''
}
})
}
if (!this.db.has('settings.shortKey')) {
this.db.set('settings.shortKey[picgo:upload]', {
enable: true,
key: 'CommandOrControl+Shift+P',
name: 'upload',
label: T('QUICK_UPLOAD')
})
}
this.read()
}
flush () {
this.db = new JSONStore(CONFIG_PATH)
}
read () {
this.db.read()
return this.db
}
get (key = ''): any {
if (key === '') {
return this.db.read()
}
return this.db.get(key)
}
set (key: string, value: any): void {
return this.db.set(key, value)
}
has (key: string) {
return this.db.has(key)
}
unset (key: string, value: any): boolean {
return this.db.unset(key, value)
}
getConfigPath () {
return CONFIG_PATH
}
}
const db = new ConfigStore()
export default db
// v2.3.0 add gallery db
class GalleryDB {
private static instance: DBStore
private constructor () {
console.log('init gallery db')
}
public static getInstance (): DBStore {
if (!GalleryDB.instance) {
GalleryDB.instance = new DBStore(DB_PATH, 'gallery')
}
return GalleryDB.instance
}
}
export {
GalleryDB
}
+24 -12
View File
@@ -1,14 +1,12 @@
import PicGoCore from '~/universal/types/picgo'
import {
app
} from 'electron'
import path from 'path'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo') as typeof PicGoCore
const STORE_PATH = app.getPath('userData')
import { dbChecker, dbPathChecker } from 'apis/core/datastore/dbChecker'
import pkg from 'root/package.json'
import { PicGo } from 'picgo'
import db from 'apis/core/datastore'
import debounce from 'lodash/debounce'
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
const CONFIG_PATH = dbPathChecker()
dbChecker()
const picgo = new PicGo(CONFIG_PATH)
picgo.saveConfig({
@@ -16,7 +14,21 @@ picgo.saveConfig({
PICGO_ENV: 'GUI'
})
// @ts-ignore
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
+96
View File
@@ -0,0 +1,96 @@
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) => {
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[]) => {
if (hasUncathcedError) {
if (checkLogFileIsLarge(logPath).isLarge) {
recreateLogFile(logPath)
}
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
}
}
}
export {
getLogger
}
+118 -28
View File
@@ -4,9 +4,10 @@ import {
Notification,
ipcMain
} from 'electron'
import db from '#/datastore'
import db, { GalleryDB } from 'apis/core/datastore'
import { dbPathChecker, defaultConfigPath, getGalleryDBPath } from 'apis/core/datastore/dbChecker'
import uploader from 'apis/app/uploader'
import pasteTemplate from '#/utils/pasteTemplate'
import pasteTemplate from '~/main/utils/pasteTemplate'
import { handleCopyUrl } from '~/main/utils/common'
import {
getWindowId,
@@ -15,27 +16,43 @@ import {
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 {
// eslint-disable-next-line no-use-before-define
private static instance: GuiApi
private windowId: number = -1
private settingWindowId: number = -1
private constructor () {
console.log('init gui api')
}
public static getInstance (): GuiApi {
if (!GuiApi.instance) {
GuiApi.instance = new GuiApi()
}
return GuiApi.instance
}
private async showSettingWindow () {
this.settingWindowId = await getSettingWindowId()
const settingWindow = BrowserWindow.fromId(this.settingWindowId)
if (settingWindow.isVisible()) {
if (settingWindow?.isVisible()) {
return true
}
settingWindow.show()
return new Promise<void>((resolve, reject) => {
settingWindow?.show()
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve()
}, 1000) // TODO: a better way to wait page loaded.
})
}
private getWebcontentsByWindowId (id: number) {
return BrowserWindow.fromId(id).webContents
private getWebContentsByWindowId (id: number) {
return BrowserWindow.fromId(id)?.webContents
}
async showInputBox (options: IShowInputBoxOption = {
@@ -43,46 +60,42 @@ class GuiApi implements IGuiApi {
placeholder: ''
}) {
await this.showSettingWindow()
this.getWebcontentsByWindowId(this.settingWindowId)
.send(SHOW_INPUT_BOX, options)
return new Promise<string>((resolve, reject) => {
this.getWebContentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
return new Promise<string>((resolve) => {
ipcMain.once(SHOW_INPUT_BOX, (event: Event, value: string) => {
resolve(value)
})
})
}
showFileExplorer (options: IShowFileExplorerOption = {}) {
return new Promise<string>(async (resolve, reject) => {
this.windowId = await getWindowId()
dialog.showOpenDialog(BrowserWindow.fromId(this.windowId), options, (filename: string) => {
resolve(filename)
})
})
async showFileExplorer (options: IShowFileExplorerOption = {}) {
this.windowId = await getWindowId()
const res = await dialog.showOpenDialog(BrowserWindow.fromId(this.windowId)!, options)
return res.filePaths || []
}
async upload (input: IUploadOption) {
this.windowId = await getWindowId()
const webContents = this.getWebcontentsByWindowId(this.windowId)
const imgs = await uploader.setWebContents(webContents).upload(input)
const webContents = this.getWebContentsByWindowId(this.windowId)
const imgs = await uploader.setWebContents(webContents!).upload(input)
if (imgs !== false) {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[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()
}, i * 100)
db.insert('uploaded', imgs[i])
await GalleryDB.getInstance().insert(imgs[i])
}
handleCopyUrl(pasteText.join('\n'))
webContents.send('uploadFiles', imgs)
webContents.send('updateGallery')
webContents?.send('uploadFiles', imgs)
webContents?.send(IRPCActionType.UPDATE_GALLERY)
return imgs
}
return []
@@ -105,10 +118,10 @@ class GuiApi implements IGuiApi {
type: 'info',
buttons: ['Yes', 'No']
}) {
return new Promise<IShowMessageBoxResult>(async (resolve, reject) => {
return new Promise<IShowMessageBoxResult>(async (resolve) => {
this.windowId = await getWindowId()
dialog.showMessageBox(
BrowserWindow.fromId(this.windowId),
BrowserWindow.fromId(this.windowId)!,
options
).then((res) => {
resolve({
@@ -118,6 +131,83 @@ class GuiApi implements IGuiApi {
})
})
}
/**
* v2.4.0+
* @param options
*/
async showConfigDialog<T extends IStringKeyMap> (options: IPicGoPluginShowConfigDialogOption) {
await this.showSettingWindow()
this.getWebContentsByWindowId(this.settingWindowId)?.send(IRPCActionType.OPEN_CONFIG_DIALOG, options)
return new Promise<T | false>((resolve) => {
ipcMain.once(IRPCActionType.OPEN_CONFIG_DIALOG, (event: Event, value: T | false) => {
resolve(value)
})
})
}
/**
* get picgo config/data path
*/
async getConfigPath () {
const currentConfigPath = dbPathChecker()
const galleryDBPath = getGalleryDBPath().dbPath
return {
defaultConfigPath,
currentConfigPath,
galleryDBPath
}
}
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: 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)
}
})
})
}
})
}
return Reflect.get(target, prop)
}
})
}
}
export default GuiApi
+2 -3
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,14 +21,13 @@ 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,
[GET_SETTING_WINDOW_ID]: busCallGetSettingWindowId,
[CREATE_APP_MENU]: createMenu
}
for (let i in eventList) {
for (const i in eventList) {
bus.on(i, eventList[i])
}
}
+104 -24
View File
@@ -1,20 +1,33 @@
import {
app,
ipcMain,
shell,
Notification,
IpcMainEvent
IpcMainEvent,
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 '#/utils/pasteTemplate'
import db from '#/datastore'
import pasteTemplate from '~/main/utils/pasteTemplate'
import db, { GalleryDB } from '~/main/apis/core/datastore'
import server from '~/main/server'
import getPicBeds from '~/main/utils/getPicBeds'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import bus from '@core/bus'
import {
TOGGLE_SHORTKEY_MODIFIED_MODE
TOGGLE_SHORTKEY_MODIFIED_MODE,
OPEN_DEVTOOLS,
SHOW_MINI_PAGE_MENU,
MINIMIZE_WINDOW,
CLOSE_WINDOW,
SHOW_MAIN_PAGE_MENU,
SHOW_UPLOAD_PAGE_MENU,
OPEN_USER_STORE_FILE,
OPEN_URL,
SHOW_PLUGIN_PAGE_MENU,
SET_MINI_WINDOW_POS,
GET_PICBEDS
} from '#/events/constants'
import {
uploadClipboardFiles,
@@ -22,6 +35,11 @@ import {
} from '~/main/apis/app/uploader/apis'
import picgoCoreIPC from './picgoCoreIPC'
import { handleCopyUrl } from '~/main/utils/common'
import { buildMainPageMenu, buildMiniPageMenu, buildPluginPageMenu, buildPicBedListMenu } from './remotes/menu'
import path from 'path'
import { T } from '~/main/i18n'
const STORE_PATH = app.getPath('userData')
export default {
listen () {
@@ -29,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]))
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()
db.insert('uploaded', img[0])
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()
})
@@ -62,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()
}
@@ -79,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()
}
@@ -94,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()
})
@@ -116,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()
@@ -128,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
})
@@ -141,6 +166,61 @@ export default {
ipcMain.on('updateServer', () => {
server.restart()
})
ipcMain.on(OPEN_DEVTOOLS, (event: IpcMainEvent) => {
event.sender.openDevTools()
})
// menu & window methods
ipcMain.on(SHOW_MINI_PAGE_MENU, () => {
const window = windowManager.get(IWindowList.MINI_WINDOW)!
const menu = buildMiniPageMenu()
menu.popup({
window
})
})
ipcMain.on(SHOW_MAIN_PAGE_MENU, () => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const menu = buildMainPageMenu(window)
menu.popup({
window
})
})
ipcMain.on(SHOW_UPLOAD_PAGE_MENU, () => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const menu = buildPicBedListMenu()
menu.popup({
window
})
})
ipcMain.on(SHOW_PLUGIN_PAGE_MENU, (evt: IpcMainEvent, plugin: IPicGoPlugin) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const menu = buildPluginPageMenu(plugin)
menu.popup({
window
})
})
ipcMain.on(MINIMIZE_WINDOW, () => {
const window = BrowserWindow.getFocusedWindow()
window?.minimize()
})
ipcMain.on(CLOSE_WINDOW, () => {
const window = BrowserWindow.getFocusedWindow()
if (process.platform === 'linux') {
window?.hide()
} else {
window?.close()
}
})
ipcMain.on(OPEN_USER_STORE_FILE, (evt: IpcMainEvent, filePath: string) => {
const abFilePath = path.join(STORE_PATH, filePath)
shell.openPath(abFilePath)
})
ipcMain.on(OPEN_URL, (evt: IpcMainEvent, url: string) => {
shell.openExternal(url)
})
ipcMain.on(SET_MINI_WINDOW_POS, (evt: IpcMainEvent, pos: IMiniWindowPos) => {
const window = BrowserWindow.getFocusedWindow()
window?.setBounds(pos)
})
},
dispose () {}
}
+227 -83
View File
@@ -5,29 +5,46 @@ import {
shell,
IpcMainEvent,
ipcMain,
app
clipboard
} from 'electron'
import PicGoCore from '~/universal/types/picgo'
import { IPicGoHelperType } from '#/types/enum'
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 {
PICGO_SAVE_CONFIG,
PICGO_GET_CONFIG,
PICGO_GET_DB,
PICGO_INSERT_DB,
PICGO_INSERT_MANY_DB,
PICGO_UPDATE_BY_ID_DB,
PICGO_GET_BY_ID_DB,
PICGO_REMOVE_BY_ID_DB,
PICGO_OPEN_FILE,
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 = app.getPath('userData')
// const PluginHandler = requireFunc('picgo/lib/PluginHandler').default
const STORE_PATH = path.dirname(dbPathChecker())
// const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
type PicGoNotice = {
title: string,
body: string[]
}
interface GuiMenuItem {
label: string
handle: (arg0: PicGoCore, arg1: GuiApi) => Promise<void>
@@ -50,7 +67,7 @@ const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => {
}
const handleConfigWithFunction = (config: any[]) => {
for (let i in config) {
for (const i in config) {
if (typeof config[i].default === 'function') {
config[i].default = config[i].default()
}
@@ -64,15 +81,17 @@ const handleConfigWithFunction = (config: any[]) => {
const getPluginList = (): IPicGoPlugin[] => {
const pluginList = picgo.pluginLoader.getFullList()
const list = []
for (let i in pluginList) {
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 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) {
@@ -115,8 +134,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)
}
})
}
@@ -133,7 +163,7 @@ const handlePluginInstall = () => {
shortKeyHandler.registerPluginShortKey(res.body[0])
} else {
showNotification({
title: '插件安装失败',
title: T('PLUGIN_INSTALL_FAILED'),
body: res.body as string
})
}
@@ -142,47 +172,45 @@ const handlePluginInstall = () => {
})
}
const handlePluginUninstall = () => {
ipcMain.on('uninstallPlugin', async (event: IpcMainEvent, msg: string) => {
const dispose = handleNPMError()
const res = await picgo.pluginHandler.uninstall([msg])
if (res.success) {
event.sender.send('uninstallSuccess', res.body[0])
shortKeyHandler.unregisterPluginShortKey(res.body[0])
} else {
showNotification({
title: '插件卸载失败',
body: res.body as string
})
}
event.sender.send('hideLoading')
dispose()
})
const handlePluginUninstall = async (fullName: string) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const dispose = handleNPMError()
const res = await picgo.pluginHandler.uninstall([fullName])
if (res.success) {
window.webContents.send('uninstallSuccess', res.body[0])
shortKeyHandler.unregisterPluginShortKey(res.body[0])
} else {
showNotification({
title: T('PLUGIN_UNINSTALL_FAILED'),
body: res.body as string
})
}
window.webContents.send('hideLoading')
dispose()
}
const handlePluginUpdate = () => {
ipcMain.on('updatePlugin', async (event: IpcMainEvent, msg: string) => {
const dispose = handleNPMError()
const res = await picgo.pluginHandler.update([msg])
if (res.success) {
event.sender.send('updateSuccess', res.body[0])
} else {
showNotification({
title: '插件更新失败',
body: res.body as string
})
}
event.sender.send('hideLoading')
dispose()
})
const handlePluginUpdate = async (fullName: string) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const dispose = handleNPMError()
const res = await picgo.pluginHandler.update([fullName])
if (res.success) {
window.webContents.send('updateSuccess', res.body[0])
} else {
showNotification({
title: T('PLUGIN_UPDATE_FAILED'),
body: res.body as string
})
}
window.webContents.send('hideLoading')
dispose()
}
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) {
@@ -196,26 +224,27 @@ 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)
}
})
}
// TODO: remove it
const handlePluginActions = () => {
ipcMain.on('pluginActions', (event: IpcMainEvent, name: string, label: string) => {
const plugin = picgo.pluginLoader.getPlugin(name)
const guiApi = new GuiApi()
if (plugin?.guiMenu?.(picgo)?.length) {
const menu: GuiMenuItem[] = plugin.guiMenu(picgo)
menu.forEach(item => {
if (item.label === label) {
item.handle(picgo, guiApi)
item.handle(picgo, GuiApi.getInstance())
}
})
}
@@ -224,56 +253,171 @@ const handlePluginActions = () => {
const handleRemoveFiles = () => {
ipcMain.on('removeFiles', (event: IpcMainEvent, files: ImgInfo[]) => {
const guiApi = new GuiApi()
setTimeout(() => {
picgo.emit('remove', files, guiApi)
picgo.emit('remove', files, GuiApi.getInstance())
}, 500)
})
}
const handlePicGoSaveData = () => {
ipcMain.on('picgoSaveData', (event: IpcMainEvent, data: IObj) => {
const handlePicGoSaveConfig = () => {
ipcMain.on(PICGO_SAVE_CONFIG, (event: IpcMainEvent, data: IObj) => {
picgo.saveConfig(data)
})
}
const handlePicGoGetConfig = () => {
ipcMain.on(PICGO_GET_CONFIG, (event: IpcMainEvent, key: string | undefined, callbackId: string) => {
const result = picgo.getConfig(key)
event.sender.send(PICGO_GET_CONFIG, result, callbackId)
})
}
const handleImportLocalPlugin = () => {
ipcMain.on('importLocalPlugin', (event: IpcMainEvent) => {
ipcMain.on('importLocalPlugin', async (event: IpcMainEvent) => {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
dialog.showOpenDialog(settingWindow, {
const res = await dialog.showOpenDialog(settingWindow, {
properties: ['openDirectory']
}, async (filePath: string[]) => {
if (filePath.length > 0) {
const res = await picgo.pluginHandler.install(filePath)
if (res.success) {
const list = getPluginList()
})
const filePaths = res.filePaths
if (filePaths.length > 0) {
const res = await picgo.pluginHandler.install(filePaths)
if (res.success) {
try {
const list = simpleClone(getPluginList())
event.sender.send('pluginList', list)
} catch (e: any) {
event.sender.send('pluginList', [])
showNotification({
title: '导入插件成功',
body: ''
})
} else {
showNotification({
title: '导入插件失败',
body: res.body as string
title: T('TIPS_GET_PLUGIN_LIST_FAILED'),
body: e.message
})
}
showNotification({
title: T('PLUGIN_IMPORT_SUCCEED'),
body: ''
})
} else {
showNotification({
title: T('PLUGIN_IMPORT_FAILED'),
body: res.body as string
})
}
event.sender.send('hideLoading')
})
}
event.sender.send('hideLoading')
})
}
const handlePicGoGalleryDB = () => {
ipcMain.on(PICGO_GET_DB, async (event: IpcMainEvent, filter: IFilter, callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.get(filter)
event.sender.send(PICGO_GET_DB, res, callbackId)
})
ipcMain.on(PICGO_INSERT_DB, async (event: IpcMainEvent, value: IObject, callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.insert(value)
event.sender.send(PICGO_INSERT_DB, res, callbackId)
})
ipcMain.on(PICGO_INSERT_MANY_DB, async (event: IpcMainEvent, value: IObject[], callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.insertMany(value)
event.sender.send(PICGO_INSERT_MANY_DB, res, callbackId)
})
ipcMain.on(PICGO_UPDATE_BY_ID_DB, async (event: IpcMainEvent, id: string, value: IObject[], callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.updateById(id, value)
event.sender.send(PICGO_UPDATE_BY_ID_DB, res, callbackId)
})
ipcMain.on(PICGO_GET_BY_ID_DB, async (event: IpcMainEvent, id: string, callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.getById(id)
event.sender.send(PICGO_GET_BY_ID_DB, res, callbackId)
})
ipcMain.on(PICGO_REMOVE_BY_ID_DB, async (event: IpcMainEvent, id: string, callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.removeById(id)
event.sender.send(PICGO_REMOVE_BY_ID_DB, res, callbackId)
})
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)
if (copy) {
clipboard.writeText(txt)
}
return txt
})
}
const handleOpenFile = () => {
ipcMain.on(PICGO_OPEN_FILE, (event: IpcMainEvent, fileName: string) => {
const abFilePath = path.join(STORE_PATH, fileName)
shell.openPath(abFilePath)
})
}
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()
handlePluginInstall()
handlePluginUninstall()
handlePluginUpdate()
handleGetPicBedConfig()
handlePluginActions()
handleRemoveFiles()
handlePicGoSaveData()
handlePicGoSaveConfig()
handlePicGoGetConfig()
handlePicGoGalleryDB()
handleImportLocalPlugin()
}
handleOpenFile()
handleOpenWindow()
handleI18n()
handleRPCActions()
},
// TODO: separate to single file
handlePluginUninstall,
handlePluginUpdate
}
+318
View File
@@ -0,0 +1,318 @@
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum'
import { Menu, BrowserWindow, app, dialog } from 'electron'
import getPicBeds from '~/main/utils/getPicBeds'
import picgo from '@core/picgo'
import {
uploadClipboardFiles
} from '~/main/apis/app/uploader/apis'
import { privacyManager } from '~/main/utils/privacyManager'
import pkg from 'root/package.json'
import GuiApi from 'apis/gui'
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
handle: (arg0: PicGoCore, arg1: GuiApi) => Promise<void>
}
const buildMiniPageMenu = () => {
const submenu = buildPicBedListMenu()
const template = [
{
label: T('OPEN_MAIN_WINDOW'),
click () {
windowManager.get(IWindowList.SETTING_WINDOW)!.show()
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
},
{
label: T('CHOOSE_DEFAULT_PICBED'),
type: 'submenu',
submenu
},
{
label: T('UPLOAD_BY_CLIPBOARD'),
click () {
uploadClipboardFiles()
}
},
{
label: T('HIDE_WINDOW'),
click () {
BrowserWindow.getFocusedWindow()!.hide()
}
},
{
label: T('PRIVACY_AGREEMENT'),
click () {
privacyManager.show(false)
}
},
{
label: T('RELOAD_APP'),
click () {
app.relaunch()
app.exit(0)
}
},
{
role: 'quit',
label: T('QUIT')
}
]
// @ts-ignore
return Menu.buildFromTemplate(template)
}
const buildMainPageMenu = (win: BrowserWindow) => {
const template = [
{
label: T('ABOUT'),
click () {
dialog.showMessageBox({
title: 'PicGo',
message: 'PicGo',
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
})
}
},
{
label: T('SPONSOR_PICGO'),
click () {
win?.webContents?.send(SHOW_MAIN_PAGE_DONATION)
}
},
{
label: T('SHOW_PICBED_QRCODE'),
click () {
win?.webContents?.send(SHOW_MAIN_PAGE_QRCODE)
}
},
{
label: T('OPEN_TOOLBOX'),
click () {
const window = windowManager.create(IWindowList.TOOLBOX_WINDOW)
window?.show()
}
},
{
label: T('SHOW_DEVTOOLS'),
click () {
win?.webContents?.openDevTools()
}
},
{
label: T('PRIVACY_AGREEMENT'),
click () {
privacyManager.show(false)
}
}
]
// @ts-ignore
return Menu.buildFromTemplate(template)
}
const buildPicBedListMenu = () => {
const picBeds = getPicBeds()
const currentPicBed = picgo.getConfig('picBed.uploader')
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: !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')
}
}
}
})
: 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)
}
// TODO: separate to single file
const handleRestoreState = (item: string, name: string): void => {
if (item === 'uploader') {
const current = picgo.getConfig('picBed.current')
if (current === name) {
picgo.saveConfig({
'picBed.current': 'smms',
'picBed.uploader': 'smms'
})
}
}
if (item === 'transformer') {
const current = picgo.getConfig('picBed.transformer')
if (current === name) {
picgo.saveConfig({
'picBed.transformer': 'path'
})
}
}
}
const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const menu = [{
label: T('ENABLE_PLUGIN'),
enabled: !plugin.enabled,
click () {
picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: true
})
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, true)
}
}, {
label: T('DISABLE_PLUGIN'),
enabled: plugin.enabled,
click () {
picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: false
})
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)
}
if (plugin.config.uploader.name) {
handleRestoreState('uploader', plugin.config.uploader.name)
}
}
}, {
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: T('UPDATE_PLUGIN'),
click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
picgoCoreIPC.handlePluginUpdate(plugin.fullName)
}
}]
for (const i in plugin.config) {
if (plugin.config[i].config.length > 0) {
const obj = {
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
const configName = plugin.config[i].fullName || plugin.config[i].name
const config = plugin.config[i].config
window.webContents.send(PICGO_CONFIG_PLUGIN, currentType, configName, config)
}
}
menu.push(obj)
}
}
// handle transformer
if (plugin.config.transformer.name) {
const currentTransformer = picgo.getConfig<string>('picBed.transformer') || 'path'
const pluginTransformer = plugin.config.transformer.name
const obj = {
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'
if (currentTransformer === transformer) {
picgo.saveConfig({
'picBed.transformer': 'path'
})
} else {
picgo.saveConfig({
'picBed.transformer': transformer
})
}
}
}
menu.push(obj)
}
// plugin custom menus
if (plugin.guiMenu) {
menu.push({
// @ts-ignore
type: 'separator'
})
for (const i of plugin.guiMenu) {
menu.push({
label: i.label,
click () {
// ipcRenderer.send('pluginActions', plugin.fullName, i.label)
const picgPlugin = picgo.pluginLoader.getPlugin(plugin.fullName)
if (picgPlugin?.guiMenu?.(picgo)?.length) {
const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo)
menu.forEach(item => {
if (item.label === i.label) {
item.handle(picgo, GuiApi.getInstance())
}
})
}
}
})
}
}
// @ts-ignore
return Menu.buildFromTemplate(menu)
}
export {
buildMiniPageMenu,
buildMainPageMenu,
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
}
+90
View File
@@ -0,0 +1,90 @@
import yaml from 'js-yaml'
import { ObjectAdapter, I18n } from '@picgo/i18n'
import path from 'path'
import fs from 'fs-extra'
import { builtinI18nList } from '#/i18n'
class I18nManager {
private i18n: I18n | null = null
private builtinI18nFolder = path.join(__static, '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()
+25 -18
View File
@@ -1,27 +1,16 @@
import { app } from 'electron'
import fse from 'fs-extra'
import path from 'path'
import dayjs from 'dayjs'
import util from 'util'
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.log')
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
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(':')
}
+54 -21
View File
@@ -10,29 +10,34 @@ import {
} 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
updateShortKeyFromVersion212,
migrateGalleryFromVersion230
} from '~/main/migrate'
import {
uploadChoosedFiles,
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'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import { getUploadFiles } from '~/main/utils/handleArgv'
import db from '#/datastore'
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'
const isDevelopment = process.env.NODE_ENV !== 'production'
@@ -54,33 +59,48 @@ const handleStartUpFiles = (argv: string[], cwd: string) => {
}
class LifeCycle {
private beforeReady () {
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'))
await migrateGalleryFromVersion230(db, GalleryDB.getInstance(), picgo)
}
private onReady () {
app.on('ready', async () => {
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) {
console.error('Vue Devtools failed to install:', e.toString())
} catch (e: any) {
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()
// 不需要阻塞
@@ -92,15 +112,19 @@ class LifeCycle {
handleStartUpFiles(process.argv, process.cwd())
}
if (global.notificationList?.length > 0) {
while (global.notificationList.length) {
if (global.notificationList && global.notificationList?.length > 0) {
while (global.notificationList?.length) {
const option = global.notificationList.pop()
const notice = new Notification(option!)
notice.show()
}
}
})
await remoteNoticeHandler.init()
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
}
app.whenReady().then(readyFunction)
}
private onRunning () {
app.on('second-instance', (event, commandLine, workingDirectory) => {
logger.info('detect second instance')
@@ -123,6 +147,13 @@ class LifeCycle {
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
@@ -135,6 +166,7 @@ class LifeCycle {
process.env.XDG_CURRENT_DESKTOP = 'Unity'
}
}
private onQuit () {
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
@@ -162,12 +194,13 @@ class LifeCycle {
}
}
}
launchApp () {
async launchApp () {
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
this.beforeReady()
await this.beforeReady()
this.onReady()
this.onRunning()
this.onQuit()
+35 -6
View File
@@ -1,24 +1,30 @@
import DB from '#/datastore'
import { DBStore } from '@picgo/store'
import ConfigStore from '~/main/apis/core/datastore'
import path from 'path'
import fse from 'fs-extra'
import { PicGo as PicGoCore } from 'picgo'
import { T } from '~/main/i18n'
import { SHORTKEY_COMMAND_UPLOAD } from 'apis/core/bus/constants'
// from v2.1.2
const updateShortKeyFromVersion212 = (db: typeof DB, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
// #557 极端情况可能会出现配置不存在,需要重新写入
if (shortKeyConfig === undefined) {
const defaultShortKeyConfig = {
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
@@ -28,6 +34,29 @@ const updateShortKeyFromVersion212 = (db: typeof DB, shortKeyConfig: IShortKeyCo
return false
}
const migrateGalleryFromVersion230 = async (configDB: typeof ConfigStore, galleryDB: DBStore, picgo: PicGoCore) => {
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
if (originGallery && Array.isArray(originGallery) && originGallery?.length > 0) {
if (fse.existsSync(configBakPath)) {
fse.copyFileSync(configPath, configBakPath)
}
await galleryDB.insertMany(originGallery)
picgo.saveConfig({
uploaded: [],
__migrateUploaded: true
})
}
}
export {
updateShortKeyFromVersion212
updateShortKeyFromVersion212,
migrateGalleryFromVersion230
}
+17 -4
View File
@@ -29,6 +29,7 @@ class Server {
}
this.httpServer = http.createServer(this.handleRequest)
}
private checkIfConfigIsValid (config: IObj | undefined) {
if (config && config.port && config.host && (config.enable !== undefined)) {
return true
@@ -36,14 +37,21 @@ class Server {
return false
}
}
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
}
@@ -57,8 +65,8 @@ class Server {
request.on('end', () => {
try {
postObj = (body === '') ? {} : JSON.parse(body)
} catch (err) {
logger.error(`[PicGo Server]`, err)
} catch (err: any) {
logger.error('[PicGo Server]', err)
return handleResponse({
response,
body: {
@@ -67,7 +75,7 @@ class Server {
}
})
}
logger.info(`[PicGo Server] get the request`)
logger.info('[PicGo Server] get the request', body)
const handler = routers.getHandler(request.url!)
handler!({
...postObj,
@@ -81,6 +89,7 @@ class Server {
response.end()
}
}
// port as string is a bug
private listen = (port: number | string) => {
logger.info(`[PicGo Server] is listening at ${port}`)
@@ -102,17 +111,21 @@ class Server {
}
})
}
startup () {
console.log('startup', this.config.enable)
if (this.config.enable) {
this.listen(this.config.port)
}
}
shutdown (hasStarted?: boolean) {
this.httpServer.close()
if (!hasStarted) {
logger.info('[PicGo Server] shutdown')
}
}
restart () {
this.config = picgo.getConfig('settings.server')
this.shutdown()
+1
View File
@@ -4,6 +4,7 @@ class Router {
get (url: string, callback: routeHandler): void {
this.router.set(url, callback)
}
post (url: string, callback: routeHandler): void {
this.router.set(url, callback)
}
+21 -14
View File
@@ -1,12 +1,16 @@
import router from './router'
import {
uploadWithClipboardFiles,
uploadWithFiles
} from '@core/bus/apis'
import {
handleResponse
} from './utils'
import logger from '@core/picgo/logger'
import windowManager from 'apis/app/window/windowManager'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import path from 'path'
import { dbPathDir } from 'apis/core/datastore/dbChecker'
const STORE_PATH = dbPathDir()
const LOG_PATH = path.join(STORE_PATH, 'picgo.log')
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
router.post('/upload', async ({
response,
@@ -19,13 +23,14 @@ router.post('/upload', async ({
if (list.length === 0) {
// upload with clipboard
logger.info('[PicGo Server] upload clipboard file')
const res = await uploadWithClipboardFiles()
if (res.success) {
const res = await uploadClipboardFiles()
logger.info('[PicGo Server] upload result:', res)
if (res) {
handleResponse({
response,
body: {
success: true,
result: res.result
result: [res]
}
})
} else {
@@ -33,7 +38,7 @@ router.post('/upload', async ({
response,
body: {
success: false,
message: 'upload error'
message: errorMessage
}
})
}
@@ -45,13 +50,15 @@ router.post('/upload', async ({
path: item
}
})
const res = await uploadWithFiles(pathList)
if (res.success) {
const win = windowManager.getAvailableWindow()
const res = await uploadChoosedFiles(win.webContents, pathList)
logger.info('[PicGo Server] upload result', res.join(' ; '))
if (res.length) {
handleResponse({
response,
body: {
success: true,
result: res.result
result: res
}
})
} else {
@@ -59,18 +66,18 @@ router.post('/upload', async ({
response,
body: {
success: false,
message: 'upload error'
message: errorMessage
}
})
}
}
} catch (err) {
} catch (err: any) {
logger.error(err)
handleResponse({
response,
body: {
success: false,
message: err
message: errorMessage
}
})
}
+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
+80 -26
View File
@@ -1,47 +1,81 @@
import fs from 'fs-extra'
import path from 'path'
import os from 'os'
import { remote, app } from 'electron'
import pkg from 'root/package.json'
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
import yaml from 'js-yaml'
import { i18nManager } from '~/main/i18n'
// import { ILocales } from '~/universal/types/i18n'
const APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData')
function injectPicGoVersion () {
global.PICGO_GUI_VERSION = pkg.version
global.PICGO_CORE_VERSION = pkg.dependencies.picgo.replace('^', '')
}
const configPath = dbPathChecker()
const CONFIG_DIR = path.dirname(configPath)
function beforeOpen () {
injectPicGoVersion()
if (process.platform === 'darwin') {
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(path.join(__static, '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)
}
}
/**
* 初始化剪贴板生成图片的脚本
*/
function resolveClipboardImageGenerator () {
let clipboardFiles = getClipboardFiles()
if (!fs.pathExistsSync(path.join(STORE_PATH, 'windows10.ps1'))) {
const clipboardFiles = getClipboardFiles()
if (!fs.pathExistsSync(path.join(CONFIG_DIR, 'windows10.ps1'))) {
clipboardFiles.forEach(item => {
fs.copyFileSync(item.origin, item.dest)
})
@@ -51,30 +85,50 @@ function resolveClipboardImageGenerator () {
})
}
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
let file1 = fs.readFileSync(filePath1)
let file2 = fs.readFileSync(filePath2)
if (!file1.equals(file2)) {
fs.copyFileSync(filePath1, filePath2)
}
}
function getClipboardFiles () {
let files = [
const files = [
'/linux.sh',
'/mac.applescript',
'/windows.ps1',
'/windows10.ps1'
'/windows10.ps1',
'/wsl.sh'
]
return files.map(item => {
return {
origin: path.join(__static, item),
dest: path.join(STORE_PATH, 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
+90 -6
View File
@@ -1,5 +1,7 @@
import db from '#/datastore'
import fs from 'fs-extra'
import db from '~/main/apis/core/datastore'
import { clipboard, Notification, dialog } from 'electron'
import { handleUrlEncode } from '~/universal/utils/common'
export const handleCopyUrl = (str: string): void => {
if (db.get('settings.autoCopy') !== false) {
@@ -14,16 +16,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)
@@ -34,7 +41,7 @@ export const showNotification = (options: IPrivateShowNotificationOption = {
}
export const showMessageBox = (options: any) => {
return new Promise<IShowMessageBoxResult>(async (resolve, reject) => {
return new Promise<IShowMessageBoxResult>(async (resolve) => {
dialog.showMessageBox(
options
).then((res) => {
@@ -69,3 +76,80 @@ 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 getClipboardFilePath = (): string => {
// TODO: linux support
const img = clipboard.readImage()
if (img.isEmpty()) {
if (process.platform === 'win32') {
const imgPath = clipboard.readBuffer('FileNameW')?.toString('ucs2')?.replace(RegExp(String.fromCharCode(0), 'g'), '')
if (imgPath) {
return imgPath
}
}
} else {
if (process.platform === 'darwin') {
let imgPath = clipboard.read('public.file-url') // will get file://xxx/xxx
imgPath = ensureFilePath(imgPath)
if (imgPath) {
return imgPath.replace('file://', '')
}
}
}
return ''
}
export const handleUrlEncodeWithSetting = (url: string) => {
if (db.get('settings.encodeOutputURL') === true) {
url = handleUrlEncode(url)
}
return url
}
export const replaceHost = (url: string, oldHost: string, newHost: string) => {
try {
const parsedUrl = new URL(url)
if (parsedUrl.host === oldHost) {
parsedUrl.host = newHost
}
return parsedUrl.toString()
} catch {
return url
}
}
export const getHost = (url: string = '') => {
try {
const parsedUrl = new URL(url)
return parsedUrl.host
} catch {
return ''
}
}
/**
* remove protocol and suffix
*/
export const removeProtocolAndSuffix = (url: string = '') => {
return url.replace(/^(https?:\/\/)?/, '').replace(/\/$/, '')
}
+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
}
+8 -2
View File
@@ -1,6 +1,7 @@
import path from 'path'
import fs from 'fs-extra'
import Logger from 'picgo/dist/src/lib/Logger'
import { Logger } from 'picgo'
import { isUrl } from '~/universal/utils/common'
interface IResultFileObject {
path: string
}
@@ -34,12 +35,17 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd(), logger: Logger
return null // for uploading images in clipboard
} else if ((fileList?.length || 0) > 0) {
const result = fileList!.map(item => {
if (isUrl(item)) {
return {
path: item
}
}
if (path.isAbsolute(item)) {
return {
path: item
}
} else {
let tempPath = path.join(cwd, item)
const tempPath = path.join(cwd, item)
if (fs.existsSync(tempPath)) {
return {
path: tempPath
+6
View File
@@ -0,0 +1,6 @@
import db from '~/main/apis/core/datastore'
import { i18nManager } from '~/main/i18n'
export const initI18n = () => {
const currentLanguage = db.get('settings.language') || 'zh-CN'
i18nManager.setCurrentLanguage(currentLanguage)
}
+169
View File
@@ -0,0 +1,169 @@
import { trimValues } from '#/utils/common'
import picgo from '@core/picgo'
import { v4 as uuid } from 'uuid'
export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IPicGoPluginConfig[] => {
for (const i in config) {
if (typeof config[i].default === 'function') {
config[i].default = config[i].default()
}
if (typeof config[i].choices === 'function') {
config[i].choices = (config[i].choices as Function)()
}
}
return config as IPicGoPluginConfig[]
}
export const completeUploaderMetaConfig = (originData: IStringKeyMap): IUploaderConfigListItem => {
return Object.assign({
_configName: 'Default'
}, trimValues(originData), {
_id: uuid(),
_createdAt: Date.now(),
_updatedAt: Date.now()
})
}
/**
* get picbed config by type
* it will trigger the uploader config function & get the uploader config result
* & not just read from
*/
export const getPicBedConfig = (type: string) => {
const name = picgo.helper.uploader.get(type)?.name || type
if (picgo.helper.uploader.get(type)?.config) {
const _config = picgo.helper.uploader.get(type)!.config!(picgo)
const config = handleConfigWithFunction(_config)
return {
config,
name
}
} else {
return {
config: [],
name
}
}
}
export const changeCurrentUploader = (type: string, config?: IStringKeyMap, id?: string) => {
if (!type) {
return
}
if (id) {
picgo.saveConfig({
[`uploader.${type}.defaultId`]: id
})
}
if (config) {
picgo.saveConfig({
[`picBed.${type}`]: config
})
}
picgo.saveConfig({
'picBed.current': type,
'picBed.uploader': type
})
}
export const selectUploaderConfig = (type: string, id: string) => {
const { configList } = getUploaderConfigList(type)
const config = configList.find((item: IStringKeyMap) => item._id === id)
if (config) {
picgo.saveConfig({
[`uploader.${type}.defaultId`]: id,
[`picBed.${type}`]: config
})
}
}
export const getUploaderConfigList = (type: string): IUploaderConfigItem => {
if (!type) {
return {
configList: [],
defaultId: ''
}
}
const currentUploaderConfig = picgo.getConfig<IStringKeyMap>(`uploader.${type}`) ?? {}
let configList = currentUploaderConfig.configList
let defaultId = currentUploaderConfig.defaultId || ''
if (!configList) {
const res = upgradeUploaderConfig(type)
configList = res.configList
defaultId = res.defaultId
}
return {
configList,
defaultId
}
}
/**
* delete uploader config by type & id
*/
export const deleteUploaderConfig = (type: string, id: string): IUploaderConfigItem | void => {
const { configList, defaultId } = getUploaderConfigList(type)
if (configList.length <= 1) {
return
}
let newDefaultId = defaultId
const updatedConfigList = configList.filter((item: IStringKeyMap) => item._id !== id)
if (id === defaultId) {
newDefaultId = updatedConfigList[0]._id
changeCurrentUploader(type, updatedConfigList[0], updatedConfigList[0]._id)
}
picgo.saveConfig({
[`uploader.${type}.configList`]: updatedConfigList
})
return {
configList: updatedConfigList,
defaultId: newDefaultId
}
}
/**
* upgrade old uploader config to new format
*/
export const upgradeUploaderConfig = (type: string): {
configList: IStringKeyMap[]
defaultId: string
} => {
const uploaderConfig = picgo.getConfig<IStringKeyMap>(`picBed.${type}`) ?? {}
if (!uploaderConfig._id) {
Object.assign(uploaderConfig, completeUploaderMetaConfig(uploaderConfig))
}
const uploaderConfigList = [uploaderConfig]
picgo.saveConfig({
[`uploader.${type}`]: {
configList: uploaderConfigList,
defaultId: uploaderConfig._id
},
[`picBed.${type}`]: uploaderConfig
})
return {
configList: uploaderConfigList,
defaultId: uploaderConfig._id
}
}
export const updateUploaderConfig = (type: string, id: string, config: IStringKeyMap) => {
const { configList, defaultId } = getUploaderConfigList(type)
const existConfig = configList.find((item: IStringKeyMap) => item._id === id)
let updatedConfig: IUploaderConfigListItem
let updatedDefaultId = defaultId
if (existConfig) {
updatedConfig = Object.assign(existConfig, trimValues(config), {
_updatedAt: Date.now()
})
} else {
updatedConfig = completeUploaderMetaConfig(config)
updatedDefaultId = updatedConfig._id
configList.push(updatedConfig)
}
picgo.saveConfig({
[`uploader.${type}.configList`]: configList,
[`uploader.${type}.defaultId`]: updatedDefaultId,
[`picBed.${type}`]: updatedConfig
})
}
+37
View File
@@ -0,0 +1,37 @@
import { IPasteStyle } from '#/types/enum'
import { handleUrlEncodeWithSetting } from './common'
const formatCustomLink = (customLink: string, item: ImgInfo) => {
const fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')
const url = item.url || item.imgUrl
const extName = item.extname
const formatObj = {
url,
fileName,
extName
}
const keys = Object.keys(formatObj) as ['url', 'fileName', 'extName']
keys.forEach(item => {
if (customLink.indexOf(`$${item}`) !== -1) {
const reg = new RegExp(`\\$${item}`, 'g')
customLink = customLink.replace(reg, formatObj[item])
}
})
return customLink
}
export default (style: IPasteStyle, item: ImgInfo, customLink: string | undefined) => {
const url = handleUrlEncodeWithSetting(item.url || item.imgUrl)
const _customLink = customLink || '$url'
const tpl = {
markdown: `![](${url})`,
HTML: `<img src="${url}"/>`,
URL: url,
UBB: `[IMG]${url}[/IMG]`,
Custom: formatCustomLink(_customLink, {
...item,
url
})
}
return tpl[style]
}
+5 -31
View File
@@ -1,13 +1,9 @@
import db from '#/datastore'
import { ipcMain } from 'electron'
import db from '~/main/apis/core/datastore'
import { showMessageBox } from '~/main/utils/common'
import { SHOW_PRIVACY_MESSAGE } from '~/universal/events/constants'
import { T } from '~/main/i18n'
class PrivacyManager {
async init () {
ipcMain.on(SHOW_PRIVACY_MESSAGE, () => {
this.show(false)
})
async check () {
if (db.get('settings.privacyEnsure') !== true) {
const res = await this.show(true)
// cancel
@@ -25,30 +21,8 @@ class PrivacyManager {
const res = await showMessageBox({
type: 'info',
buttons: showCancel ? ['Yes', 'No'] : ['Yes'],
title: '隐私协议',
message: `
本软件尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更优质的服务,本软件会按照本隐私权政策的规定使用和收集您的一些行为信息。您在同意本软件服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本软件服务使用协议不可分割的一部分,如果不同意将无法使用。本协议会定期更新。
1.适用范围
a)在您使用本软件时,本软件会记录的您对本软件的一些操作行为信息,包括但不限于您使用本软件进行文件上传的耗时、类型、数量等信息。
2.信息的使用
a)在获得您的使用数据之后,本软件会将其上传至数据分析服务器,以便分析数据后,提供给您更好的服务。
3.信息披露
a)本软件不会将您的信息披露给不受信任的第三方。
b)根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露;
c)如您出现违反中国有关法律、法规或者相关规则的情况,需要向第三方披露;
4.信息安全
a)本软件不会收集您的个人信息、密钥信息等隐私信息,所收集的信息仅仅作为改善软件、优化体验、了解软件日活等用途。
`
title: T('PRIVACY_AGREEMENT'),
message: T('PRIVACY')
})
return res
}
+14 -18
View File
@@ -1,11 +1,12 @@
import { dialog, shell } from 'electron'
import db from '#/datastore'
import axios from 'axios'
import db from '~/main/apis/core/datastore'
import pkg from 'root/package.json'
import { lt } from 'semver'
import { T } from '~/main/i18n'
import { getLatestVersion } from '~/main/utils/getLatestVersion'
const version = pkg.version
const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
const releaseUrlBackup = 'https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@latest/package.json'
// const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases'
// const releaseUrlBackup = 'https://picgo-1251750343.cos.ap-chengdu.myqcloud.com'
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
const checkVersion = async () => {
@@ -15,25 +16,20 @@ const checkVersion = async () => {
showTip = true
}
if (showTip) {
let res: any
try {
res = await axios.get(releaseUrl).catch(async () => {
const result = await axios.get(releaseUrlBackup)
return result
})
} catch (err) {
console.log(err)
}
if (res.status === 200) {
const latest = res.data.version || res.data.name
const isCheckBetaUpdate = db.get('settings.checkBetaUpdate') !== false
const res: string = await getLatestVersion(isCheckBetaUpdate)
if (res !== '') {
const latest = res
const result = compareVersion2Update(version, latest)
if (result) {
dialog.showMessageBox({
type: 'info',
title: '发现新版本',
title: T('FIND_NEW_VERSION'),
buttons: ['Yes', 'No'],
message: `发现新版本${latest},更新了很多功能,是否去下载最新的版本?`,
checkboxLabel: '以后不再提醒',
message: T('TIPS_FIND_NEW_VERSION', {
v: latest
}),
checkboxLabel: T('NO_MORE_NOTICE'),
checkboxChecked: false
}).then(res => {
if (res.response === 0) { // if selected yes
+34 -3
View File
@@ -1,12 +1,43 @@
<template>
<div id="app">
<router-view></router-view>
<router-view />
</div>
</template>
<script>
<script lang="ts" setup>
import { useStore } from '@/hooks/useStore'
import { onBeforeMount, onMounted, onUnmounted } from 'vue'
import { getConfig } from './utils/dataSender'
import type { IConfig } from 'picgo'
import bus from './utils/bus'
import { FORCE_UPDATE } from '~/universal/events/constants'
import { useATagClick } from './hooks/useATagClick'
useATagClick()
const store = useStore()
onBeforeMount(async () => {
const config = await getConfig<IConfig>()
if (config) {
store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
}
})
onMounted(() => {
bus.on(FORCE_UPDATE, () => {
store?.updateForceUpdateTime()
})
})
onUnmounted(() => {
bus.off(FORCE_UPDATE)
})
</script>
<script lang="ts">
export default {
name: 'picgo'
name: 'PicGoApp'
}
</script>
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-40
View File
@@ -1,40 +0,0 @@
<template>
<div id="choose-pic-bed">
<span>选择 {{ label }} 作为你默认图床</span>
<el-switch
v-model="value"
@change="choosePicBed"
>
</el-switch>
</div>
</template>
<script>
export default {
name: 'choose-pic-bed',
props: {
type: String,
label: String
},
data () {
return {
value: false
}
},
created () {
if (this.type === this.$db.get('picBed.current')) {
this.value = true
}
},
methods: {
choosePicBed (val) {
this.letPicGoSaveData({
'picBed.current': this.type,
'picBed.uploader': this.type
})
this.$emit('update:choosed', this.type)
}
}
}
</script>
<style lang='stylus'>
</style>

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