Merge branch 'dev' into release

This commit is contained in:
Kuingsmile 2024-06-14 15:55:40 +08:00
commit f0dcd0c7b3
180 changed files with 4829 additions and 6396 deletions

17
.vscode/settings.json vendored
View File

@ -1,24 +1,7 @@
{ {
"eslint.enable": true,
"eslint.alwaysShowStatus": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"vue",
"typescriptreact"
],
"[stylus]": { "[stylus]": {
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"stylusSupremacy.insertSemicolons": false,
"stylusSupremacy.insertBraces": false,
"stylusSupremacy.insertNewLineBetweenSelectors": true,
"stylusSupremacy.insertParenthesisAroundIfCondition": false,
"stylusSupremacy.alwaysUseNoneOverZero": true,
"stylusSupremacy.alwaysUseZeroWithoutUnit": true,
"stylusSupremacy.sortProperties": "grouped",
"stylusSupremacy.quoteChar": "\"",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit" "source.fixAll.eslint": "explicit"
}, },

View File

@ -1,3 +1,31 @@
# :tada: 2.9.0 (2024-06-14)
### :sparkles: Features
* **custom:** add tray tooltip ([8a565c1](https://github.com/Kuingsmile/piclist/commit/8a565c1))
* **custom:** optimize get config speed ([106290f](https://github.com/Kuingsmile/piclist/commit/106290f))
* **custom:** refactor all main ipc event ([5ddc182](https://github.com/Kuingsmile/piclist/commit/5ddc182))
* **custom:** smms delete for repeat file ([07e7a26](https://github.com/Kuingsmile/piclist/commit/07e7a26))
* **custom:** support create bucket for s3 ([226f170](https://github.com/Kuingsmile/piclist/commit/226f170))
* **custom:** update i18n force update ([e9c386d](https://github.com/Kuingsmile/piclist/commit/e9c386d))
* **custom:** use new ssh2 and node-ssh ([2290e4e](https://github.com/Kuingsmile/piclist/commit/2290e4e))
### :bug: Bug Fixes
* **custom:** change manage file name ([d7028fc](https://github.com/Kuingsmile/piclist/commit/d7028fc))
* **custom:** fix bug while using webp plugin ([30341d4](https://github.com/Kuingsmile/piclist/commit/30341d4)), closes [#205](https://github.com/Kuingsmile/piclist/issues/205)
* **custom:** fix css error ([0c241bc](https://github.com/Kuingsmile/piclist/commit/0c241bc))
* **custom:** fix url copy error for dirs ([f1a7a13](https://github.com/Kuingsmile/piclist/commit/f1a7a13))
### :pencil: Documentation
* **custom:** v2.9.0 changelog ([7226cc8](https://github.com/Kuingsmile/piclist/commit/7226cc8))
## :tada: 2.8.6 (2024-05-26) ## :tada: 2.8.6 (2024-05-26)

View File

@ -1,19 +1,32 @@
### ✨ Features ### ✨ Features
- 现在从相册删除云端图片时的日志记录于日志文件中,而不是打印到控制台 - 图床
- 现在软件内窗口打开手册和打开图床手册时,会根据软件语言自动设置语言 - 现在`sm.ms`图床上传重复图片时,后上传的图片也支持云端删除了
- 现在设置自定义mini窗口图标和保持置顶后会即时生效不再需要重启软件 - 管理
- 优化了同步配置时的下载速度 - 现在s3图床支持新建存储通
- 优化了监听剪贴板功能的性能表现 - 调整了管理页面的部分布局
- 代码高亮样式调整为stackoverflow-light
- 界面
- 现在托盘菜单会根据当前状态显示开启/关闭剪贴板监听,而不是始终显示全部菜单
- 现在托盘菜单会根据当前状态显示开启/关闭mini窗口
- 现在鼠标悬停于托盘图标时,会显示当前图床和配置名
- 移除了设置页面部分不必要的全局通知
- 优化了英文下的设置页面布局
- 性能
- 现在启动软件时会自动清空剪贴板图片缓存文件夹
- 优化了读取配置的性能表现
- 优化了多个页面的加载速度
- 升级vue等依赖至最新版本
### 🐛 Bug Fixes ### 🐛 Bug Fixes
- 修复了第一次进入页面时,下拉选择项显示的默认值是后台值而非标签的问题 - 修复了从obsidan发送删除请求时会导致软件闪退的问题
- 修复了tray页面监听器没有正确移除的问题 - 修复了使用`webp`插件时,上传剪贴板图片会导致软件闪退的问题
- 修复了设置了图片处理操作后上传URL会报错的问题
### 📦Chore - 修复了设置原始PicGo窗口大小后重新打开设置界面时窗口大小数值没有更新的问题
- 修复了主界面菜单加载和点击时反复触发获取图床配置导致的卡顿问题
- 优化了[官网](https://piclist.cn)的加载速度,添加了`配置文件结构`的说明 - 修复了切换语言时部分下拉框没有更新的问题
- mac打包平台迁移至`macos-12` - 修复了部分翻译错误
- 移除了已废弃的配置项相关的代码 - 管理页面
- 移除了管理配置中试剂未使用的`currentPicBedConfig`配置项 - 修复了部分图床的文件夹复制链接错误的问题
- 修复了文件浏览页面的css错误

View File

@ -1,19 +1,32 @@
### ✨ Features ### ✨ Features
- Now when deleting cloud images from the album, the log is recorded in the log file instead of being printed to the console - PicBed
- Now when opening the manual and the image bed manual in the software window, the language will be automatically set according to the software language - Now when uploading duplicate images to `sm.ms` image bed, the later uploaded image also supports cloud deletion
- Now the custom mini window icon and keep top will take effect immediately after setting, no need to restart the software - Management
- Optimized the download speed of synchronizing configuration - Now s3 image bed supports creating new storage channels
- Optimized the performance of listening to the clipboard function - Adjusted some layouts of the management page
- The code highlighting style is adjusted to stackoverflow-light
- Interface
- Now the tray menu will display the open/close clipboard monitoring according to the current status, instead of always displaying all menus
- Now the tray menu will display the open/close mini window according to the current status
- Now when the mouse hovers over the tray icon, the current image bed and configuration name will be displayed
- Removed some unnecessary global notifications on the settings page
- Optimized the layout of the settings page in English
- Performance
- Now the software will automatically clear the clipboard image cache folder when starting
- Optimized the performance of reading configuration
- Optimized the loading speed of multiple pages
- Upgrade dependencies such as vue to the latest version
### 🐛 Bug Fixes ### 🐛 Bug Fixes
- Fixed the problem that the default value displayed in the drop-down selection when entering the page for the first time is the background value rather than the label - Fix the problem that the software crashes when sending a delete request from obsidan
- Fixed the problem that the tray page listener was not correctly removed - Fix the problem that the software crashes when uploading clipboard images using the `webp` plugin
- Fix the problem that an error occurs when uploading a URL after setting image processing operations
### 📦Chore - Fix the problem that the window size value is not updated when the original PicGo window size is set and the setting interface is reopened
- Fix the problem that the main interface menu repeatedly triggers the lag caused by repeatedly triggering the acquisition of the image bed configuration when loading and clicking
- Optimized the loading speed of [official website](https://piclist.cn), added the description of `configuration file structure` - Fix the problem that some drop-down boxes are not updated when switching languages
- Mac packaging platform migrated to `macos-12` - Fix some translation errors
- Removed the code related to the deprecated configuration items - Management page
- Removed the `currentPicBedConfig` configuration item that is not used in the management configuration - Fix the problem that the folder copy link of some image beds is incorrect
- Fix the css error of the file browsing page

View File

@ -1,6 +1,6 @@
{ {
"name": "piclist", "name": "piclist",
"version": "2.8.6", "version": "2.9.0",
"author": { "author": {
"name": "Kuingsmile", "name": "Kuingsmile",
"email": "pkukuing@gmail.com" "email": "pkukuing@gmail.com"
@ -38,7 +38,7 @@
"@aws-sdk/lib-storage": "^3.421.0", "@aws-sdk/lib-storage": "^3.421.0",
"@aws-sdk/s3-request-presigner": "^3.421.0", "@aws-sdk/s3-request-presigner": "^3.421.0",
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@highlightjs/vue-plugin": "^2.1.0", "@highlightjs/vue-plugin": "^2.1.2",
"@nodelib/fs.walk": "^2.0.0", "@nodelib/fs.walk": "^2.0.0",
"@octokit/rest": "^19.0.7", "@octokit/rest": "^19.0.7",
"@picgo/i18n": "^1.0.0", "@picgo/i18n": "^1.0.0",
@ -48,11 +48,11 @@
"ali-oss": "^6.18.1", "ali-oss": "^6.18.1",
"axios": "^1.6.8", "axios": "^1.6.8",
"compare-versions": "^4.1.3", "compare-versions": "^4.1.3",
"core-js": "^3.33.3", "core-js": "^3.37.1",
"cos-nodejs-sdk-v5": "^2.12.5", "cos-nodejs-sdk-v5": "^2.12.5",
"dexie": "^3.2.4", "dexie": "^3.2.4",
"electron-updater": "^6.1.4", "electron-updater": "^6.1.4",
"element-plus": "2.4.4", "element-plus": "2.7.4",
"epipebomb": "^1.0.0", "epipebomb": "^1.0.0",
"fast-xml-parser": "^4.3.2", "fast-xml-parser": "^4.3.2",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@ -60,34 +60,34 @@
"got": "^12.6.0", "got": "^12.6.0",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"hpagent": "^1.2.0", "hpagent": "^1.2.0",
"keycode": "^2.2.0",
"lowdb": "^1.0.0", "lowdb": "^1.0.0",
"marked": "^9.1.5", "marked": "^9.1.5",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"node-ssh-no-cpu-features": "^1.0.1", "node-ssh-no-cpu-features": "^2.0.0",
"nodejs-file-downloader": "^4.12.1", "nodejs-file-downloader": "^4.12.1",
"piclist": "^1.8.8", "piclist": "^1.8.10",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.0", "pinia-plugin-persistedstate": "^3.2.1",
"proxy-agent": "^5.0.0", "proxy-agent": "^5.0.0",
"qiniu": "7.9.0", "qiniu": "7.9.0",
"qrcode.vue": "^3.4.1", "qrcode.vue": "^3.4.1",
"querystring": "^0.2.1", "querystring": "^0.2.1",
"shell-path": "2.1.0", "shell-path": "2.1.0",
"ssh2-no-cpu-features": "^1.0.0", "ssh2-no-cpu-features": "^2.0.0",
"upyun": "^3.4.6", "upyun": "^3.4.6",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"video.js": "^8.6.1", "video.js": "^8.6.1",
"vue": "^3.3.13", "vue": "^3.4.27",
"vue-router": "^4.2.5", "vue-router": "^4.3.2",
"vue3-lazyload": "^0.3.8", "vue3-lazyload": "^0.3.8",
"vue3-photo-preview": "^0.3.0", "vue3-photo-preview": "^0.3.0",
"webdav": "^5.3.1", "webdav": "^5.3.1",
"write-file-atomic": "^4.0.1" "write-file-atomic": "^4.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/video.js": "^7.3.58",
"@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@electron/notarize": "^2.1.0", "@electron/notarize": "^2.1.0",
"@types/ali-oss": "^6.16.11", "@types/ali-oss": "^6.16.11",
@ -99,11 +99,10 @@
"@types/mime-types": "^2.1.4", "@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.11", "@types/multer": "^1.4.11",
"@types/node": "^16.10.2", "@types/node": "^16.10.2",
"@types/request-promise-native": "^1.0.21",
"@types/semver": "^7.5.6", "@types/semver": "^7.5.6",
"@types/tunnel": "^0.0.6", "@types/tunnel": "^0.0.7",
"@types/upyun": "^3.4.3", "@types/upyun": "^3.4.3",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.8",
"@types/write-file-atomic": "^4.0.3", "@types/write-file-atomic": "^4.0.3",
"@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0", "@typescript-eslint/parser": "^6.12.0",
@ -114,22 +113,22 @@
"@vue/cli-service": "^5.0.8", "@vue/cli-service": "^5.0.8",
"@vue/eslint-config-standard": "^8.0.1", "@vue/eslint-config-standard": "^8.0.1",
"@vue/eslint-config-typescript": "^12.0.0", "@vue/eslint-config-typescript": "^12.0.0",
"@vue/runtime-dom": "^3.3.13", "@vue/runtime-dom": "^3.4.27",
"conventional-changelog": "^5.1.0", "conventional-changelog": "^5.1.0",
"cz-customizable": "^7.0.0", "cz-customizable": "^7.0.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"dpdm": "^3.14.0", "dpdm": "^3.14.0",
"electron": "^22.0.2", "electron": "^22.3.27",
"eslint": "^8.54.0", "eslint": "^8.54.0",
"eslint-config-standard": ">=16.0.0", "eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-n": "^16.6.2",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.2.0",
"eslint-plugin-vue": "^9.18.1", "eslint-plugin-vue": "^9.26.0",
"husky": "^3.1.0", "husky": "^3.1.0",
"node-bump-version": "^1.0.2", "node-bump-version": "^1.0.2",
"node-loader": "^2.0.0", "node-loader": "^2.0.0",
"npm-check-updates": "^16.14.12", "npm-check-updates": "^16.14.20",
"stylus": "^0.59.0", "stylus": "^0.59.0",
"stylus-loader": "^7.1.3", "stylus-loader": "^7.1.3",
"typescript": "^4.9.5", "typescript": "^4.9.5",

View File

@ -2,6 +2,7 @@ LANG_DISPLAY_LABEL: 'English'
ABOUT: About ABOUT: About
OPEN_MAIN_WINDOW: Open Main Window OPEN_MAIN_WINDOW: Open Main Window
OPEN_MINI_WINDOW: Open Mini Window OPEN_MINI_WINDOW: Open Mini Window
HIDE_MINI_WINDOW: Hide Mini Window
CHOOSE_DEFAULT_PICBED: Choose Default Picbed CHOOSE_DEFAULT_PICBED: Choose Default Picbed
OPEN_UPDATE_HELPER: Open Update Helper OPEN_UPDATE_HELPER: Open Update Helper
RELOAD_APP: Reload App RELOAD_APP: Reload App
@ -311,7 +312,7 @@ SETTINGS_SYNC_MANAGE_CONFIG: Manage configuration
SETTINGS_AUTO_IMPORT: Auto import config in manage page SETTINGS_AUTO_IMPORT: Auto import config in manage page
SETTINGS_AUTO_IMPORT_SELECT_PICBED: Select picbed SETTINGS_AUTO_IMPORT_SELECT_PICBED: Select picbed
SETTINGS_TAB_SYSTEM: System SETTINGS_TAB_SYSTEM: System
SETTINGS_TAB_SYNC_CONFIG: Sync and Configuration SETTINGS_TAB_SYNC_CONFIG: Configuration
SETTINGS_TAB_UPLOAD: Upload SETTINGS_TAB_UPLOAD: Upload
SETTINGS_TAB_ADVANCED: Advanced SETTINGS_TAB_ADVANCED: Advanced
SETTINGS_TAB_UPDATE: Update SETTINGS_TAB_UPDATE: Update
@ -378,37 +379,37 @@ TIPS_GET_PLUGIN_LIST_FAILED: Get plugin list failed
# manageSetting # manageSetting
MANAGE_SETTING_TITLE: Manage Setting MANAGE_SETTING_TITLE: Manage Setting
MANAGE_SETTING_AUTO_FRESH_TITLE: Auto refresh file list when entering new directory MANAGE_SETTING_ISAUTOREFRESH_TITLE: Auto refresh file list when entering new directory
MANAGE_SETTING_AUTO_FRESH_TIPS: Only applies to non-paginated mode, data is cached to indexdb to speed up loading speed MANAGE_SETTING_ISAUTOREFRESH_TIPS: Only applies to non-paginated mode, data is cached to indexdb to speed up loading speed
MANAGE_SETTING_CLEAR_CACHE_TITLE: 'Clear file list cache database, currently in use:' MANAGE_SETTING_CLEAR_CACHE_TITLE: 'Clear file list cache database, currently in use:'
MANAGE_SETTING_CLEAR_CACHE_FREE_TITLE: 'Available:' MANAGE_SETTING_CLEAR_CACHE_FREE_TITLE: 'Available:'
MANAGE_SETTING_CLEAR_CACHE_TIPS: After clearing, the file list will be reloaded when entering a new directory next time MANAGE_SETTING_CLEAR_CACHE_TIPS: After clearing, the file list will be reloaded when entering a new directory next time
MANAGE_SETTING_CLEAR_CACHE_PROMPT: Are you sure you want to clear the file list cache database? MANAGE_SETTING_CLEAR_CACHE_PROMPT: Are you sure you want to clear the file list cache database?
MANAGE_SETTING_CLEAR_CACHE_BUTTON: Clear MANAGE_SETTING_CLEAR_CACHE_BUTTON: Clear
MANAGE_SETTING_SHOW_THUMBNAIL_TITLE: Display the original image instead of format icon (requires public access permissions) MANAGE_SETTING_ISSHOWTHUMBNAIL_TITLE: Display the original image instead of format icon (requires public access permissions)
MANAGE_SETTING_SHOW_FILE_LIST_TYPE_TITLE: Default display mode for the file list MANAGE_SETTING_ISSHOWLIST_TITLE: Default display mode for the file list
MANAGE_SETTING_SHOW_FILE_LIST_TYPE_LIST: List MANAGE_SETTING_ISSHOWLIST_ON: List
MANAGE_SETTING_SHOW_FILE_LIST_TYPE_CARD: Card MANAGE_SETTING_ISSHOWLIST_OFF: Card
MANAGE_SETTING_FORCE_CUSTOM_URL_HTTPS_TITLE: Force custom URL to use HTTPS MANAGE_SETTING_ISFORCECUSTOMURLHTTPS_TITLE: Force custom URL to use HTTPS
MANAGE_SETTING_FORCE_CUSTOM_URL_HTTPS_TIPS: After enabling, all operations will automatically add the https prefix to custom domains MANAGE_SETTING_ISFORCECUSTOMURLHTTPS_TIPS: After enabling, all operations will automatically add the https prefix to custom domains
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_UPLOAD_TITLE: Preserve directory structure when uploading MANAGE_SETTING_ISUPLOADKEEPDIRSTRUCTURE_TITLE: Preserve directory structure when uploading
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_UPLOAD_TIPS: After disabling, all files will be expanded to the specified directory MANAGE_SETTING_ISUPLOADKEEPDIRSTRUCTURE_TIPS: After disabling, all files will be expanded to the specified directory
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_A: Download MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TITLE_A: Download
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_B: ' File ' MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TITLE_B: ' File '
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_C: will preserve the directory structure MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TITLE_C: will preserve the directory structure
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_D: ' Folder ' MANAGE_SETTING_ISDOWNLOADFOLDERKEEPDIRSTRUCTURE_TITLE_D: ' Folder '
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_FILE_TIPS: After enabling, the original directory structure will be preserved MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TIPS: After enabling, the original directory structure will be preserved
MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TITLE: Maximum number of files to download simultaneously (1-9999) MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TITLE: Maximum number of files to download simultaneously (1-9999)
MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TIPS: Not work on Tencent MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TIPS: Not work on Tencent
MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_INPUT_TIPS: Please enter the maximum number of files to download simultaneously MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_INPUT_TIPS: Please enter the maximum number of files to download simultaneously
MANAGE_SETTING_SEARCH_IGNORE_CASE_TITLE: Should file search be case-insensitive MANAGE_SETTING_ISIGNORECASE_TITLE: Should file search be case-insensitive
MANAGE_SETTING_SEARCH_IGNORE_CASE_TIPS: After enabling, the search will be case-insensitive MANAGE_SETTING_ISIGNORECASE_TIPS: After enabling, the search will be case-insensitive
MANAGE_SETTING_TIMESTAMP_RENAME_TITLE: Rename uploaded files with timestamp - (highest priority) MANAGE_SETTING_TIMESTAMPRENAME_TITLE: Rename uploaded files with timestamp - (highest priority)
MANAGE_SETTING_TIMESTAMP_RENAME_TIPS: After enabling, the uploaded file will be renamed with the timestamp MANAGE_SETTING_TIMESTAMPRENAME_TIPS: After enabling, the uploaded file will be renamed with the timestamp
MANAGE_SETTING_RANDOM_STRING_RENAME_TITLE: Rename uploaded files with random strings - (medium priority) MANAGE_SETTING_RANDOMSTRINGRENAME_TITLE: Rename uploaded files with random strings - (medium priority)
MANAGE_SETTING_RANDOM_STRING_RENAME_TIPS: Random string length is 20 MANAGE_SETTING_RANDOMSTRINGRENAME_TIPS: Random string length is 20
MANAGE_SETTING_CUSTOM_RENAME_TITLE: Rename uploaded files with custom names - (lowest priority) MANAGE_SETTING_CUSTOMRENAME_TITLE: Rename uploaded files with custom names - (lowest priority)
MANAGE_SETTING_CUSTOM_RENAME_TIPS: After enabling, the uploaded file will be renamed with the custom pattern MANAGE_SETTING_CUSTOMRENAME_TIPS: After enabling, the uploaded file will be renamed with the custom pattern
MANAGE_SETTING_CUSTOM_PATTERN_TITLE: Custom rename format, placeholders can be freely combined, please refer to the table below MANAGE_SETTING_CUSTOM_PATTERN_TITLE: Custom rename format, placeholders can be freely combined, please refer to the table below
MANAGE_SETTING_CUSTOM_PATTERN_TIPS: Please enter the custom rename format MANAGE_SETTING_CUSTOM_PATTERN_TIPS: Please enter the custom rename format
MANAGE_SETTING_CUSTOM_PATTERN_TABLE_TITLE: Placeholder MANAGE_SETTING_CUSTOM_PATTERN_TABLE_TITLE: Placeholder
@ -430,8 +431,8 @@ MANAGE_SETTING_CHOOSE_DOWNLOAD_FOLDER_BUTTON: Choose folder
MANAGE_SETTING_COPY_MESSAGE: Copied MANAGE_SETTING_COPY_MESSAGE: Copied
MANAGE_SETTING_CLEAR_CACHE_SUCCESS: Cleared successfully MANAGE_SETTING_CLEAR_CACHE_SUCCESS: Cleared successfully
MANAGE_SETTING_CLEAR_CACHE_FAILED: Clear failed MANAGE_SETTING_CLEAR_CACHE_FAILED: Clear failed
MANAGE_SETTING_ENCODE_URL_WHEN_COPY: Encode URL when copy MANAGE_SETTING_ISENCODEURL_TITLE: Encode URL when copy
MANAGE_SETTING_ENCODE_URL_WHEN_COPY_TIPS: After enabling, the URL will be encoded when copying MANAGE_SETTING_ISENCODEURL_TIPS: After enabling, the URL will be encoded when copying
# Empty # Empty
MANAGE_NO_DATA: No data MANAGE_NO_DATA: No data
@ -758,13 +759,18 @@ MANAGE_BUCKET_DELETE_BTN: Delete
MANAGE_BUCKET_SORT_TITLE: Sort MANAGE_BUCKET_SORT_TITLE: Sort
MANAGE_BUCKET_SORT_NAME: Name MANAGE_BUCKET_SORT_NAME: Name
MANAGE_BUCKET_SORT_SIZE: Size MANAGE_BUCKET_SORT_SIZE: Size
MANAGE_BUCKET_SORT_TYPE: Type MANAGE_BUCKET_SORT_EXT: Type
MANAGE_BUCKET_SORT_TIME: Time MANAGE_BUCKET_SORT_TIME: Time
MANAGE_BUCKET_SORT_SELECTED: Selected status MANAGE_BUCKET_SORT_CHECK: Selected status
MANAGE_BUCKET_INIT: Init MANAGE_BUCKET_SORT_INIT: Init
MANAGE_BUCKET_URL_UPLOAD_DIALOG_TITLE: Please enter URL(s), support multiple URLs separated by line breaks MANAGE_BUCKET_URL_UPLOAD_DIALOG_TITLE: Please enter URL(s), support multiple URLs separated by line breaks
MANAGE_BUCKET_URL_UPLOAD_DIALOG_CONFIRM: Confirm MANAGE_BUCKET_URL_UPLOAD_DIALOG_CONFIRM: Confirm
MANAGE_BUCKET_URL_UPLOAD_DIALOG_CANCEL: Cancel MANAGE_BUCKET_URL_UPLOAD_DIALOG_CANCEL: Cancel
MANAGE_BUCKET_URL_FORMAT_MARKDOWN: Markdown
MANAGE_BUCKET_URL_FORMAT_MARKDOWN_WITH_LINK: Markdown-link
MANAGE_BUCKET_URL_FORMAT_URL: Url
MANAGE_BUCKET_URL_FORMAT_HTML: Html
MANAGE_BUCKET_URL_FORMAT_BBCODE: BBCode
MANAGE_BUCKET_URL_FORMAT_CUSTOM: Custom MANAGE_BUCKET_URL_FORMAT_CUSTOM: Custom
MANAGE_BUCKET_URL_FORMAT_PRESIGN: Presigned link MANAGE_BUCKET_URL_FORMAT_PRESIGN: Presigned link
MANAGE_BUCKET_FILE_INFO_TITLE: File information MANAGE_BUCKET_FILE_INFO_TITLE: File information
@ -944,19 +950,16 @@ MANAGE_NEW_BUCKET_QINIU_BUCKETNAME_RULE_MSG_B: Bucket name length cannot exceed
MANAGE_NEW_BUCKET_QINIU_BUCKETNAME_RULE_MSG_C: Bucket names can only contain lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen. MANAGE_NEW_BUCKET_QINIU_BUCKETNAME_RULE_MSG_C: Bucket names can only contain lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen.
MANAGE_NEW_BUCKET_QINIU_REGION: Region MANAGE_NEW_BUCKET_QINIU_REGION: Region
MANAGE_NEW_BUCKET_QINIU_ACL_DESC: Public Access MANAGE_NEW_BUCKET_QINIU_ACL_DESC: Public Access
MANAGE_NEW_BUCKET_UPYUN_NAME: Upyun MANAGE_NEW_BUCKET_S3PLIST_NAME: S3-Compatible
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_DESC: Bucket Name MANAGE_NEW_BUCKET_S3PLIST_BUCKETNAME_DESC: Bucket Name
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_PLACEHOLDER: Please enter bucket name MANAGE_NEW_BUCKET_S3PLIST_BUCKETNAME_PLACEHOLDER: Please enter the Bucket name
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_RULE_MSG_A: Bucket name cannot be empty MANAGE_NEW_BUCKET_S3PLIST_BUCKETNAME_RULE_MSG_A: Bucket name can't be empty
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_RULE_MSG_B: Bucket name length should be between 5-20 characters MANAGE_NEW_BUCKET_S3PLIST_REGION: Region
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_RULE_MSG_C: Bucket names can only contain lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen. MANAGE_NEW_BUCKET_S3PLIST_ACL_DESC: Access Control
MANAGE_NEW_BUCKET_UPYUN_OPERATORNAME_DESC: Operator Name MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_RW: Public Read and Write
MANAGE_NEW_BUCKET_UPYUN_OPERATORNAME_PLACEHOLDER: Please enter operator name MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_R: Public Read
MANAGE_NEW_BUCKET_UPYUN_OPERATORNAME_RULE_MSG_A: Operator name cannot be empty MANAGE_NEW_BUCKET_S3PLIST_ACL_PRIVATE: Private
MANAGE_NEW_BUCKET_UPYUN_PASSWORD_DESC: Password MANAGE_NEW_BUCKET_S3PLIST_ACL_AUTHENTICATED_READ: Authenticated Read
MANAGE_NEW_BUCKET_UPYUN_PASSWORD_PLACEHOLDER: Please enter password
MANAGE_NEW_BUCKET_UPYUN_PASSWORD_RULE_MSG_A: Password cannot be empty
# ---renderer i18n end--- # ---renderer i18n end---
# plugins # plugins

View File

@ -2,6 +2,7 @@ LANG_DISPLAY_LABEL: 中文
ABOUT: 关于 ABOUT: 关于
OPEN_MAIN_WINDOW: 打开主窗口 OPEN_MAIN_WINDOW: 打开主窗口
OPEN_MINI_WINDOW: 打开mini窗口 OPEN_MINI_WINDOW: 打开mini窗口
HIDE_MINI_WINDOW: 隐藏mini窗口
CHOOSE_DEFAULT_PICBED: 选择默认图床 CHOOSE_DEFAULT_PICBED: 选择默认图床
OPEN_UPDATE_HELPER: 打开更新助手 OPEN_UPDATE_HELPER: 打开更新助手
RELOAD_APP: 重启应用 RELOAD_APP: 重启应用
@ -380,37 +381,37 @@ TIPS_GET_PLUGIN_LIST_FAILED: 获取插件列表失败
# manageSetting # manageSetting
MANAGE_SETTING_TITLE: 管理页面设置 MANAGE_SETTING_TITLE: 管理页面设置
MANAGE_SETTING_AUTO_FRESH_TITLE: 每次进入新目录时,是否自动刷新文件列表 MANAGE_SETTING_ISAUTOREFRESH_TITLE: 每次进入新目录时,是否自动刷新文件列表
MANAGE_SETTING_AUTO_FRESH_TIPS: 仅对不分页模式有效,默认在加载过一次后自动缓存到数据库来加快下次加载速度 MANAGE_SETTING_ISAUTOREFRESH_TIPS: 仅对不分页模式有效,默认在加载过一次后自动缓存到数据库来加快下次加载速度
MANAGE_SETTING_CLEAR_CACHE_TITLE: '清空文件列表缓存数据库 已占用:' MANAGE_SETTING_CLEAR_CACHE_TITLE: '清空文件列表缓存数据库 已占用:'
MANAGE_SETTING_CLEAR_CACHE_FREE_TITLE: '剩余可用:' MANAGE_SETTING_CLEAR_CACHE_FREE_TITLE: '剩余可用:'
MANAGE_SETTING_CLEAR_CACHE_TIPS: 清空后下次进入新目录时将会重新加载文件列表 MANAGE_SETTING_CLEAR_CACHE_TIPS: 清空后下次进入新目录时将会重新加载文件列表
MANAGE_SETTING_CLEAR_CACHE_PROMPT: 确定要清空文件列表缓存数据库吗? MANAGE_SETTING_CLEAR_CACHE_PROMPT: 确定要清空文件列表缓存数据库吗?
MANAGE_SETTING_CLEAR_CACHE_BUTTON: 清空 MANAGE_SETTING_CLEAR_CACHE_BUTTON: 清空
MANAGE_SETTING_SHOW_THUMBNAIL_TITLE: 图片显示为原图而非默认文件格式图标(需要存储桶可公开访问) MANAGE_SETTING_ISSHOWTHUMBNAIL_TITLE: 图片显示为原图而非默认文件格式图标(需要存储桶可公开访问)
MANAGE_SETTING_SHOW_FILE_LIST_TYPE_TITLE: 文件列表默认显示方式 MANAGE_SETTING_ISSHOWLIST_TITLE: 文件列表默认显示方式
MANAGE_SETTING_SHOW_FILE_LIST_TYPE_LIST: 列表 MANAGE_SETTING_ISSHOWLIST_ON: 列表
MANAGE_SETTING_SHOW_FILE_LIST_TYPE_CARD: 卡片 MANAGE_SETTING_ISSHOWLIST_OFF: 卡片
MANAGE_SETTING_FORCE_CUSTOM_URL_HTTPS_TITLE: 为自定义域名开启强制HTTPS MANAGE_SETTING_ISFORCECUSTOMURLHTTPS_TITLE: 为自定义域名开启强制HTTPS
MANAGE_SETTING_FORCE_CUSTOM_URL_HTTPS_TIPS: 开启后, 复制链接等操作将会自动为自定义域名添加https前缀 MANAGE_SETTING_ISFORCECUSTOMURLHTTPS_TIPS: 开启后, 复制链接等操作将会自动为自定义域名添加https前缀
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_UPLOAD_TITLE: 上传时保留目录结构 MANAGE_SETTING_ISUPLOADKEEPDIRSTRUCTURE_TITLE: 上传时保留目录结构
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_UPLOAD_TIPS: 关闭后会将所有文件展开到指定目录下 MANAGE_SETTING_ISUPLOADKEEPDIRSTRUCTURE_TIPS: 关闭后会将所有文件展开到指定目录下
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_A: 下载 MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TITLE_A: 下载
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_B: 文件 MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TITLE_B: 文件
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_C: 时保留目录结构 MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TITLE_C: 时保留目录结构
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_D: 文件夹 MANAGE_SETTING_ISDOWNLOADFOLDERKEEPDIRSTRUCTURE_TITLE_D: 文件夹
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_FILE_TIPS: 开启后,下载时会保留原始目录结构 MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TIPS: 开启后,下载时会保留原始目录结构
MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TITLE: 最大同时下载文件数(1-9999) MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TITLE: 最大同时下载文件数(1-9999)
MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TIPS: 腾讯云由于后端实现不同,该设置不生效 MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TIPS: 腾讯云由于后端实现不同,该设置不生效
MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_INPUT_TIPS: 请输入最大同时下载文件数 MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_INPUT_TIPS: 请输入最大同时下载文件数
MANAGE_SETTING_SEARCH_IGNORE_CASE_TITLE: 文件搜索时,是否忽略大小写 MANAGE_SETTING_ISIGNORECASE_TITLE: 文件搜索时,是否忽略大小写
MANAGE_SETTING_SEARCH_IGNORE_CASE_TIPS: 开启后,搜索时会忽略大小写 MANAGE_SETTING_ISIGNORECASE_TIPS: 开启后,搜索时会忽略大小写
MANAGE_SETTING_TIMESTAMP_RENAME_TITLE: 上传文件时间戳重命名--(优先级最高) MANAGE_SETTING_TIMESTAMPRENAME_TITLE: 上传文件时间戳重命名--(优先级最高)
MANAGE_SETTING_TIMESTAMP_RENAME_TIPS: 开启后,上传文件时会自动重命名为时间戳 MANAGE_SETTING_TIMESTAMPRENAME_TIPS: 开启后,上传文件时会自动重命名为时间戳
MANAGE_SETTING_RANDOM_STRING_RENAME_TITLE: 上传文件随机字符串重命名--(优先级中) MANAGE_SETTING_RANDOMSTRINGRENAME_TITLE: 上传文件随机字符串重命名--(优先级中)
MANAGE_SETTING_RANDOM_STRING_RENAME_TIPS: 随机字符串长度为20 MANAGE_SETTING_RANDOMSTRINGRENAME_TIPS: 随机字符串长度为20
MANAGE_SETTING_CUSTOM_RENAME_TITLE: 上传文件自定义重命名--(优先级最低) MANAGE_SETTING_CUSTOMRENAME_TITLE: 上传文件自定义重命名--(优先级最低)
MANAGE_SETTING_CUSTOM_RENAME_TIPS: 请填写自定义重命名格式 MANAGE_SETTING_CUSTOMRENAME_TIPS: 请填写自定义重命名格式
MANAGE_SETTING_CUSTOM_PATTERN_TITLE: 自定义重命名格式,占位符请参考下表,可自由组合 MANAGE_SETTING_CUSTOM_PATTERN_TITLE: 自定义重命名格式,占位符请参考下表,可自由组合
MANAGE_SETTING_CUSTOM_PATTERN_TIPS: 请填写自定义重命名格式 MANAGE_SETTING_CUSTOM_PATTERN_TIPS: 请填写自定义重命名格式
MANAGE_SETTING_CUSTOM_PATTERN_TABLE_TITLE: 占位符 MANAGE_SETTING_CUSTOM_PATTERN_TABLE_TITLE: 占位符
@ -432,8 +433,8 @@ MANAGE_SETTING_CHOOSE_DOWNLOAD_FOLDER_BUTTON: 选择目录
MANAGE_SETTING_COPY_MESSAGE: 已复制 MANAGE_SETTING_COPY_MESSAGE: 已复制
MANAGE_SETTING_CLEAR_CACHE_SUCCESS: 清除成功 MANAGE_SETTING_CLEAR_CACHE_SUCCESS: 清除成功
MANAGE_SETTING_CLEAR_CACHE_FAILED: 清除失败 MANAGE_SETTING_CLEAR_CACHE_FAILED: 清除失败
MANAGE_SETTING_ENCODE_URL_WHEN_COPY: 复制链接时进行URL编码 MANAGE_SETTING_ISENCODEURL_TITLE: 复制链接时进行URL编码
MANAGE_SETTING_ENCODE_URL_WHEN_COPY_TIPS: 根据平台选择是否开启 MANAGE_SETTING_ISENCODEURL_TIPS: 根据平台选择是否开启
# Empty # Empty
MANAGE_NO_DATA: 暂无数据 MANAGE_NO_DATA: 暂无数据
@ -763,13 +764,18 @@ MANAGE_BUCKET_DELETE_BTN: 删除
MANAGE_BUCKET_SORT_TITLE: 排序 MANAGE_BUCKET_SORT_TITLE: 排序
MANAGE_BUCKET_SORT_NAME: 文件名 MANAGE_BUCKET_SORT_NAME: 文件名
MANAGE_BUCKET_SORT_SIZE: 大小 MANAGE_BUCKET_SORT_SIZE: 大小
MANAGE_BUCKET_SORT_TYPE: 类型 MANAGE_BUCKET_SORT_EXT: 类型
MANAGE_BUCKET_SORT_TIME: 时间 MANAGE_BUCKET_SORT_TIME: 时间
MANAGE_BUCKET_SORT_SELECTED: 选中状态 MANAGE_BUCKET_SORT_CHECK: 选中状态
MANAGE_BUCKET_INIT: 初始化 MANAGE_BUCKET_SORT_INIT: 初始化
MANAGE_BUCKET_URL_UPLOAD_DIALOG_TITLE: 请输入URL支持多个URL以换行分隔 MANAGE_BUCKET_URL_UPLOAD_DIALOG_TITLE: 请输入URL支持多个URL以换行分隔
MANAGE_BUCKET_URL_UPLOAD_DIALOG_CONFIRM: 确定 MANAGE_BUCKET_URL_UPLOAD_DIALOG_CONFIRM: 确定
MANAGE_BUCKET_URL_UPLOAD_DIALOG_CANCEL: 取消 MANAGE_BUCKET_URL_UPLOAD_DIALOG_CANCEL: 取消
MANAGE_BUCKET_URL_FORMAT_MARKDOWN: Markdown
MANAGE_BUCKET_URL_FORMAT_MARKDOWN_WITH_LINK: Markdown-link
MANAGE_BUCKET_URL_FORMAT_URL: Url
MANAGE_BUCKET_URL_FORMAT_HTML: Html
MANAGE_BUCKET_URL_FORMAT_BBCODE: BBCode
MANAGE_BUCKET_URL_FORMAT_CUSTOM: 自定义 MANAGE_BUCKET_URL_FORMAT_CUSTOM: 自定义
MANAGE_BUCKET_URL_FORMAT_PRESIGN: 预签名链接 MANAGE_BUCKET_URL_FORMAT_PRESIGN: 预签名链接
MANAGE_BUCKET_FILE_INFO_TITLE: 文件信息 MANAGE_BUCKET_FILE_INFO_TITLE: 文件信息
@ -949,19 +955,16 @@ MANAGE_NEW_BUCKET_QINIU_BUCKETNAME_RULE_MSG_B: Bucket名称长度不能超过63
MANAGE_NEW_BUCKET_QINIU_BUCKETNAME_RULE_MSG_C: Bucket名称只能包含小写字母、数字和中划线且不能以中划线开头和结尾 MANAGE_NEW_BUCKET_QINIU_BUCKETNAME_RULE_MSG_C: Bucket名称只能包含小写字母、数字和中划线且不能以中划线开头和结尾
MANAGE_NEW_BUCKET_QINIU_REGION: 区域 MANAGE_NEW_BUCKET_QINIU_REGION: 区域
MANAGE_NEW_BUCKET_QINIU_ACL_DESC: 公开访问 MANAGE_NEW_BUCKET_QINIU_ACL_DESC: 公开访问
MANAGE_NEW_BUCKET_UPYUN_NAME: 又拍云 MANAGE_NEW_BUCKET_S3PLIST_NAME: S3兼容云
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_DESC: Bucket名 MANAGE_NEW_BUCKET_S3PLIST_BUCKETNAME_DESC: Bucket名
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_PLACEHOLDER: 请输入Bucket名 MANAGE_NEW_BUCKET_S3PLIST_BUCKETNAME_PLACEHOLDER: 请输入Bucket名
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_RULE_MSG_A: Bucket名不能为空 MANAGE_NEW_BUCKET_S3PLIST_BUCKETNAME_RULE_MSG_A: Bucket名不能为空
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_RULE_MSG_B: Bucket名称长度为5-20个字符 MANAGE_NEW_BUCKET_S3PLIST_REGION: 区域
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_RULE_MSG_C: Bucket名称只能包含小写字母、数字和中划线且不能以中划线开头和结尾 MANAGE_NEW_BUCKET_S3PLIST_ACL_DESC: 访问权限
MANAGE_NEW_BUCKET_UPYUN_OPERATORNAME_DESC: 操作员 MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_RW: 公共读写
MANAGE_NEW_BUCKET_UPYUN_OPERATORNAME_PLACEHOLDER: 请输入操作员 MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_R: 公共读
MANAGE_NEW_BUCKET_UPYUN_OPERATORNAME_RULE_MSG_A: 操作员不能为空 MANAGE_NEW_BUCKET_S3PLIST_ACL_PRIVATE: 私有
MANAGE_NEW_BUCKET_UPYUN_PASSWORD_DESC: 密码 MANAGE_NEW_BUCKET_S3PLIST_ACL_AUTHENTICATED_READ: 授权读
MANAGE_NEW_BUCKET_UPYUN_PASSWORD_PLACEHOLDER: 请输入密码
MANAGE_NEW_BUCKET_UPYUN_PASSWORD_RULE_MSG_A: 密码不能为空
# ---renderer i18n end--- # ---renderer i18n end---

View File

@ -2,6 +2,7 @@ LANG_DISPLAY_LABEL: 繁體中文
ABOUT: 關於 ABOUT: 關於
OPEN_MAIN_WINDOW: 打開主視窗 OPEN_MAIN_WINDOW: 打開主視窗
OPEN_MINI_WINDOW: 打開mini視窗 OPEN_MINI_WINDOW: 打開mini視窗
HIDE_MINI_WINDOW: 隱藏mini視窗
CHOOSE_DEFAULT_PICBED: 選擇預設圖床 CHOOSE_DEFAULT_PICBED: 選擇預設圖床
OPEN_UPDATE_HELPER: 開啟更新助手 OPEN_UPDATE_HELPER: 開啟更新助手
RELOAD_APP: 重啟程式 RELOAD_APP: 重啟程式
@ -54,7 +55,7 @@ TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: 請自行創建文件夾:${path}
MANUAL_PAGE_OPEN_TIP: 請選擇打開方式 MANUAL_PAGE_OPEN_TIP: 請選擇打開方式
MANUAL_PAGE_OPEN_TIP_TITLE: Tips MANUAL_PAGE_OPEN_TIP_TITLE: Tips
MANUAL_PAGE_OPEN_BY_BROWSER: 瀏覽器 MANUAL_PAGE_OPEN_BY_BROWSER: 瀏覽器
MANUAL_PAGE_OPEN_BY_BUILD_IN: 內置窗口s MANUAL_PAGE_OPEN_BY_BUILD_IN: 內置窗口
MANUAL_PAGE_OPEN_SETTING_TIP: 選擇打開手冊方式 MANUAL_PAGE_OPEN_SETTING_TIP: 選擇打開手冊方式
# ---renderer i18n begin--- # ---renderer i18n begin---
@ -378,37 +379,37 @@ TIPS_GET_PLUGIN_LIST_FAILED: 取得插件列表失敗
# manageSetting # manageSetting
MANAGE_SETTING_TITLE: 管理設定 MANAGE_SETTING_TITLE: 管理設定
MANAGE_SETTING_AUTO_FRESH_TITLE: 每次進入新目錄時,是否自動重新整理檔案列表 MANAGE_SETTING_ISAUTOREFRESH_TITLE: 每次進入新目錄時,是否自動重新整理檔案列表
MANAGE_SETTING_AUTO_FRESH_TIPS: 僅對不分頁模式有效,預設會在載入後自動快取至資料庫以提升下次載入速度 MANAGE_SETTING_ISAUTOREFRESH_TIPS: 僅對不分頁模式有效,預設會在載入後自動快取至資料庫以提升下次載入速度
MANAGE_SETTING_CLEAR_CACHE_TITLE: '清空檔案列表快取資料庫 已佔用:' MANAGE_SETTING_CLEAR_CACHE_TITLE: '清空檔案列表快取資料庫 已佔用:'
MANAGE_SETTING_CLEAR_CACHE_FREE_TITLE: '剩餘可用:' MANAGE_SETTING_CLEAR_CACHE_FREE_TITLE: '剩餘可用:'
MANAGE_SETTING_CLEAR_CACHE_TIPS: 清空後下次進入新目錄時將會重新載入檔案列表 MANAGE_SETTING_CLEAR_CACHE_TIPS: 清空後下次進入新目錄時將會重新載入檔案列表
MANAGE_SETTING_CLEAR_CACHE_PROMPT: 確定要清空檔案列表快取資料庫嗎? MANAGE_SETTING_CLEAR_CACHE_PROMPT: 確定要清空檔案列表快取資料庫嗎?
MANAGE_SETTING_CLEAR_CACHE_BUTTON: 清空 MANAGE_SETTING_CLEAR_CACHE_BUTTON: 清空
MANAGE_SETTING_SHOW_THUMBNAIL_TITLE: 顯示圖片的原始圖像而非預設的檔案格式圖示(需要存儲桶公開訪問權限) MANAGE_SETTING_ISSHOWTHUMBNAIL_TITLE: 顯示圖片的原始圖像而非預設的檔案格式圖示(需要存儲桶公開訪問權限)
MANAGE_SETTING_SHOW_FILE_LIST_TYPE_TITLE: 檔案列表預設顯示方式 MANAGE_SETTING_ISSHOWLIST_TITLE: 檔案列表預設顯示方式
MANAGE_SETTING_SHOW_FILE_LIST_TYPE_LIST: 列表 MANAGE_SETTING_ISSHOWLIST_ON: 列表
MANAGE_SETTING_SHOW_FILE_LIST_TYPE_CARD: 卡片 MANAGE_SETTING_ISSHOWLIST_OFF: 卡片
MANAGE_SETTING_FORCE_CUSTOM_URL_HTTPS_TITLE: 自定義域名啟用強制 HTTPS MANAGE_SETTING_ISFORCECUSTOMURLHTTPS_TITLE: 自定義域名啟用強制 HTTPS
MANAGE_SETTING_FORCE_CUSTOM_URL_HTTPS_TIPS: 開啟後,複製鏈結等操作將會自動為自定義域名添加 HTTPS 前綴 MANAGE_SETTING_ISFORCECUSTOMURLHTTPS_TIPS: 開啟後,複製鏈結等操作將會自動為自定義域名添加 HTTPS 前綴
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_UPLOAD_TITLE: 保留上傳時的目錄結構 MANAGE_SETTING_ISUPLOADKEEPDIRSTRUCTURE_TITLE: 保留上傳時的目錄結構
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_UPLOAD_TIPS: 停用後,所有文件將會展開到指定目錄下 MANAGE_SETTING_ISUPLOADKEEPDIRSTRUCTURE_TIPS: 停用後,所有文件將會展開到指定目錄下
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_A: 下載 MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TITLE_A: 下載
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_B: 文件 MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TITLE_B: 文件
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_C: 時保留目錄結構 MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TITLE_C: 時保留目錄結構
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_TITLE_D: 目錄 MANAGE_SETTING_ISDOWNLOADFOLDERKEEPDIRSTRUCTURE_TITLE_D: 目錄
MANAGE_SETTING_KEEP_FOLDER_STRUCTURE_DOWNLOAD_FILE_TIPS: 啟用後,下載時會保留原始目錄結構 MANAGE_SETTING_ISDOWNLOADFILEKEEPDIRSTRUCTURE_TIPS: 啟用後,下載時會保留原始目錄結構
MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TITLE: 最大同時下載檔案數量(1-9999) MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TITLE: 最大同時下載檔案數量(1-9999)
MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TIPS: 由於後端實現方式不同,此設定在腾讯云上不生效 MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_TIPS: 由於後端實現方式不同,此設定在腾讯云上不生效
MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_INPUT_TIPS: 請輸入最大同時下載檔案數量 MANAGE_SETTING_MAX_DOWNLOAD_FILE_SIZE_INPUT_TIPS: 請輸入最大同時下載檔案數量
MANAGE_SETTING_SEARCH_IGNORE_CASE_TITLE: 搜尋檔案時,是否忽略大小寫 MANAGE_SETTING_ISIGNORECASE_TITLE: 搜尋檔案時,是否忽略大小寫
MANAGE_SETTING_SEARCH_IGNORE_CASE_TIPS: 啟用後,搜尋時將會忽略大小寫 MANAGE_SETTING_ISIGNORECASE_TIPS: 啟用後,搜尋時將會忽略大小寫
MANAGE_SETTING_TIMESTAMP_RENAME_TITLE: 上傳檔案時間戳重新命名--(最高優先級) MANAGE_SETTING_TIMESTAMPRENAME_TITLE: 上傳檔案時間戳重新命名--(最高優先級)
MANAGE_SETTING_TIMESTAMP_RENAME_TIPS: 啟用後,上傳檔案時將會使用時間戳重新命名 MANAGE_SETTING_TIMESTAMPRENAME_TIPS: 啟用後,上傳檔案時將會使用時間戳重新命名
MANAGE_SETTING_RANDOM_STRING_RENAME_TITLE: 上傳檔案隨機字符串重新命名--(中優先級) MANAGE_SETTING_RANDOMSTRINGRENAME_TITLE: 上傳檔案隨機字符串重新命名--(中優先級)
MANAGE_SETTING_RANDOM_STRING_RENAME_TIPS: 隨機字符串長度為20 MANAGE_SETTING_RANDOMSTRINGRENAME_TIPS: 隨機字符串長度為20
MANAGE_SETTING_CUSTOM_RENAME_TITLE: 上傳檔案自定義重新命名--(最低優先級) MANAGE_SETTING_CUSTOMRENAME_TITLE: 上傳檔案自定義重新命名--(最低優先級)
MANAGE_SETTING_CUSTOM_RENAME_TIPS: 啟用後,上傳檔案時將會使用自定義重新命名 MANAGE_SETTING_CUSTOMRENAME_TIPS: 啟用後,上傳檔案時將會使用自定義重新命名
MANAGE_SETTING_CUSTOM_PATTERN_TITLE: 自訂重新命名格式,占位符請參考下表,可自由組合 MANAGE_SETTING_CUSTOM_PATTERN_TITLE: 自訂重新命名格式,占位符請參考下表,可自由組合
MANAGE_SETTING_CUSTOM_PATTERN_TIPS: 請輸入自訂重新命名格式 MANAGE_SETTING_CUSTOM_PATTERN_TIPS: 請輸入自訂重新命名格式
MANAGE_SETTING_CUSTOM_PATTERN_TABLE_TITLE: 占位符 MANAGE_SETTING_CUSTOM_PATTERN_TABLE_TITLE: 占位符
@ -430,8 +431,8 @@ MANAGE_SETTING_CHOOSE_DOWNLOAD_FOLDER_BUTTON: 選擇目錄
MANAGE_SETTING_COPY_MESSAGE: 已複製 MANAGE_SETTING_COPY_MESSAGE: 已複製
MANAGE_SETTING_CLEAR_CACHE_SUCCESS: 清除成功 MANAGE_SETTING_CLEAR_CACHE_SUCCESS: 清除成功
MANAGE_SETTING_CLEAR_CACHE_FAILED: 清除失敗 MANAGE_SETTING_CLEAR_CACHE_FAILED: 清除失敗
MANAGE_SETTING_ENCODE_URL_WHEN_COPY: 複製鏈結時編碼 MANAGE_SETTING_ISENCODEURL_TITLE: 複製鏈結時編碼
MANAGE_SETTING_ENCODE_URL_WHEN_COPY_TIPS: 啟用後,複製鏈結時將會編碼 MANAGE_SETTING_ISENCODEURL_TIPS: 啟用後,複製鏈結時將會編碼
# Empty # Empty
MANAGE_NO_DATA: 暫無數據 MANAGE_NO_DATA: 暫無數據
@ -758,13 +759,18 @@ MANAGE_BUCKET_DELETE_BTN: 刪除
MANAGE_BUCKET_SORT_TITLE: 排序 MANAGE_BUCKET_SORT_TITLE: 排序
MANAGE_BUCKET_SORT_NAME: 檔案名稱 MANAGE_BUCKET_SORT_NAME: 檔案名稱
MANAGE_BUCKET_SORT_SIZE: 大小 MANAGE_BUCKET_SORT_SIZE: 大小
MANAGE_BUCKET_SORT_TYPE: 類型 MANAGE_BUCKET_SORT_EXT: 類型
MANAGE_BUCKET_SORT_TIME: 時間 MANAGE_BUCKET_SORT_TIME: 時間
MANAGE_BUCKET_SORT_SELECTED: 選取狀態 MANAGE_BUCKET_SORT_CHECK: 選取狀態
MANAGE_BUCKET_INIT: 初始化 MANAGE_BUCKET_SORT_INIT: 初始化
MANAGE_BUCKET_URL_UPLOAD_DIALOG_TITLE: 請輸入 URL支援多個 URL以換行分隔 MANAGE_BUCKET_URL_UPLOAD_DIALOG_TITLE: 請輸入 URL支援多個 URL以換行分隔
MANAGE_BUCKET_URL_UPLOAD_DIALOG_CONFIRM: 確定 MANAGE_BUCKET_URL_UPLOAD_DIALOG_CONFIRM: 確定
MANAGE_BUCKET_URL_UPLOAD_DIALOG_CANCEL: 取消 MANAGE_BUCKET_URL_UPLOAD_DIALOG_CANCEL: 取消
MANAGE_BUCKET_URL_FORMAT_MARKDOWN: Markdown
MANAGE_BUCKET_URL_FORMAT_MARKDOWN_WITH_LINK: Markdown-link
MANAGE_BUCKET_URL_FORMAT_URL: Url
MANAGE_BUCKET_URL_FORMAT_HTML: Html
MANAGE_BUCKET_URL_FORMAT_BBCODE: BBCode
MANAGE_BUCKET_URL_FORMAT_CUSTOM: 自訂 MANAGE_BUCKET_URL_FORMAT_CUSTOM: 自訂
MANAGE_BUCKET_URL_FORMAT_PRESIGN: 預簽名連結 MANAGE_BUCKET_URL_FORMAT_PRESIGN: 預簽名連結
MANAGE_BUCKET_FILE_INFO_TITLE: 檔案資訊 MANAGE_BUCKET_FILE_INFO_TITLE: 檔案資訊
@ -944,18 +950,16 @@ MANAGE_NEW_BUCKET_QINIU_BUCKETNAME_RULE_MSG_B: Bucket名稱長度不能超過63
MANAGE_NEW_BUCKET_QINIU_BUCKETNAME_RULE_MSG_C: Bucket名稱只能包含小寫字母、數字和中橫線且不能以中橫線開頭和結尾 MANAGE_NEW_BUCKET_QINIU_BUCKETNAME_RULE_MSG_C: Bucket名稱只能包含小寫字母、數字和中橫線且不能以中橫線開頭和結尾
MANAGE_NEW_BUCKET_QINIU_REGION: 區域 MANAGE_NEW_BUCKET_QINIU_REGION: 區域
MANAGE_NEW_BUCKET_QINIU_ACL_DESC: 公開訪問 MANAGE_NEW_BUCKET_QINIU_ACL_DESC: 公開訪問
MANAGE_NEW_BUCKET_UPYUN_NAME: 又拍雲 MANAGE_NEW_BUCKET_S3PLIST_NAME: S3兼容雲
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_DESC: Bucket名稱 MANAGE_NEW_BUCKET_S3PLIST_BUCKETNAME_DESC: Bucket名稱
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_PLACEHOLDER: 請輸入Bucket名稱 MANAGE_NEW_BUCKET_S3PLIST_BUCKETNAME_PLACEHOLDER: 請輸入Bucket名稱
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_RULE_MSG_A: Bucket名稱不能為空 MANAGE_NEW_BUCKET_S3PLIST_BUCKETNAME_RULE_MSG_A: Bucket名稱不能為空
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_RULE_MSG_B: Bucket名稱長度為5-20個字符 MANAGE_NEW_BUCKET_S3PLIST_REGION: 區域
MANAGE_NEW_BUCKET_UPYUN_BUCKETNAME_RULE_MSG_C: Bucket名稱只能包含小寫字母、數字和中橫線且不能以中橫線開頭和結尾 MANAGE_NEW_BUCKET_S3PLIST_ACL_DESC: 訪問權限
MANAGE_NEW_BUCKET_UPYUN_OPERATORNAME_DESC: 操作員 MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_RW: 公共讀寫
MANAGE_NEW_BUCKET_UPYUN_OPERATORNAME_PLACEHOLDER: 請輸入操作員 MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_R: 公共讀
MANAGE_NEW_BUCKET_UPYUN_OPERATORNAME_RULE_MSG_A: 操作員不能為空 MANAGE_NEW_BUCKET_S3PLIST_ACL_PRIVATE: 私有
MANAGE_NEW_BUCKET_UPYUN_PASSWORD_DESC: 密碼 MANAGE_NEW_BUCKET_S3PLIST_ACL_AUTHENTICATED_READ: 授權讀
MANAGE_NEW_BUCKET_UPYUN_PASSWORD_PLACEHOLDER: 請輸入密碼
MANAGE_NEW_BUCKET_UPYUN_PASSWORD_RULE_MSG_A: 密碼不能為空
# ---renderer i18n end--- # ---renderer i18n end---
# plugins # plugins

View File

@ -1,3 +1,3 @@
import { bootstrap } from '~/main/lifeCycle' import { bootstrap } from '~/lifeCycle'
bootstrap.launchApp() bootstrap.launchApp()

View File

@ -1,81 +1,45 @@
import { webFrame } from 'electron'
// Vue 相关
import { createApp } from 'vue'
import App from './renderer/App.vue'
import router from './renderer/router'
import ElementUI from 'element-plus' import ElementUI from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import VueLazyLoad from 'vue3-lazyload'
import { initTalkingData } from './renderer/utils/analytic'
import vue3PhotoPreview from 'vue3-photo-preview'
import 'vue3-photo-preview/dist/index.css'
import VueVideoPlayer from '@videojs-player/vue'
// Electron 相关
import { webFrame } from 'electron'
// Axios
import axios from 'axios'
// Mixins
import { mainMixin } from './renderer/utils/mainMixin'
import { dragMixin } from '@/utils/mixin'
// 数据库
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 { createPinia } from 'pinia' import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
// 代码高亮 import { createApp } from 'vue'
import 'highlight.js/styles/atom-one-dark.css' import VueLazyLoad from 'vue3-lazyload'
import hljsVuePlugin from '@highlightjs/vue-plugin'
import hljsCommon from 'highlight.js/lib/common' import vue3PhotoPreview from 'vue3-photo-preview'
import 'vue3-photo-preview/dist/index.css'
import VueVideoPlayer from '@videojs-player/vue'
import 'video.js/dist/video-js.css' import 'video.js/dist/video-js.css'
import 'highlight.js/styles/stackoverflow-light.css'
import hljsVuePlugin from '@highlightjs/vue-plugin'
import 'highlight.js/lib/common'
import App from '@/App.vue'
import router from '@/router'
import { sendRPC, sendToMain, triggerRPC } from '@/utils/common'
import db from '@/utils/db'
import { T } from '@/i18n/index'
import { store } from '@/store'
import { initTalkingData } from '@/utils/analytic'
import { dragMixin } from '@/utils/mixin'
webFrame.setVisualZoomLevelLimits(1, 1) webFrame.setVisualZoomLevelLimits(1, 1)
const app = createApp(App) const app = createApp(App)
app.config.globalProperties.$builtInPicBed = [
'smms',
'imgur',
'qiniu',
'tcyun',
'upyun',
'aliyun',
'github',
'webdavplist',
'local',
'sftpplist',
'telegraphplist',
'piclist',
'lskyplist',
'aws-s3-plist'
]
app.config.globalProperties.$$db = db app.config.globalProperties.$$db = db
app.config.globalProperties.$http = axios
app.config.globalProperties.$T = T app.config.globalProperties.$T = T
app.config.globalProperties.$i18n = i18nManager
app.config.globalProperties.getConfig = getConfig
app.config.globalProperties.triggerRPC = triggerRPC app.config.globalProperties.triggerRPC = triggerRPC
app.config.globalProperties.saveConfig = saveConfig app.config.globalProperties.sendRPC = sendRPC
app.config.globalProperties.sendToMain = sendToMain app.config.globalProperties.sendToMain = sendToMain
app.mixin(mainMixin)
app.mixin(dragMixin) app.mixin(dragMixin)
const pinia = createPinia() const pinia = createPinia()
pinia.use(piniaPluginPersistedstate) pinia.use(piniaPluginPersistedstate)
app.use(VueLazyLoad, { app.use(VueLazyLoad, {
error: `file://${__static.replace(/\\/g, '/')}/unknown-file-type.svg` error: `file://${__static.replace(/\\/g, '/')}/unknown-file-type.svg`
}) })
@ -84,9 +48,7 @@ app.use(router)
app.use(store) app.use(store)
app.use(vue3PhotoPreview) app.use(vue3PhotoPreview)
app.use(pinia) app.use(pinia)
console.log(hljsCommon.highlightAuto('<h1>Highlight.js has been registered successfully!</h1>').value)
app.use(hljsVuePlugin) app.use(hljsVuePlugin)
app.use(VueVideoPlayer) app.use(VueVideoPlayer)
app.mount('#app') app.mount('#app')
initTalkingData() initTalkingData()

View File

@ -1,25 +1,22 @@
// External dependencies
import axios from 'axios' import axios from 'axios'
import {
app,
clipboard,
dialog,
shell
} from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path' import path from 'path'
import { gte, lte } from 'semver'
// Electron modules import windowManager from 'apis/app/window/windowManager'
import { app, clipboard, dialog, shell } from 'electron' import { showNotification } from '~/utils/common'
// Custom modules and utilities
import windowManager from '../window/windowManager'
import { showNotification } from '~/main/utils/common'
// Custom types/enums
import { import {
IRemoteNoticeActionType, IRemoteNoticeActionType,
IRemoteNoticeTriggerCount, IRemoteNoticeTriggerCount,
IRemoteNoticeTriggerHook IRemoteNoticeTriggerHook
} from '#/types/enum' } from '#/types/enum'
// External utility functions
import { gte, lte } from 'semver'
// for test // for test
const REMOTE_NOTICE_URL = 'https://release.piclist.cn/remote-notice.json' const REMOTE_NOTICE_URL = 'https://release.piclist.cn/remote-notice.json'

View File

@ -1,24 +1,18 @@
// External dependencies
import { import {
globalShortcut globalShortcut
} from 'electron' } from 'electron'
import shortKeyService from 'apis/app/shortKey/shortKeyService'
import GuiApi from 'apis/gui'
import bus from '@core/bus' import bus from '@core/bus'
import db from '~/main/apis/core/datastore' import db from '@core/datastore'
import logger from '@core/picgo/logger' import logger from '@core/picgo/logger'
import picgo from '@core/picgo' import picgo from '@core/picgo'
// Electron modules
// Custom utilities and modules
import GuiApi from '../../gui'
import shortKeyService from './shortKeyService'
// Custom types/enums
// External utility functions
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants' import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { configPaths } from '~/universal/utils/configPaths' import { configPaths } from '#/utils/configPaths'
class ShortKeyHandler { class ShortKeyHandler {
private isInModifiedMode: boolean = false private isInModifiedMode: boolean = false

View File

@ -1,52 +1,45 @@
// External dependencies import {
app,
clipboard,
dialog,
Menu,
MenuItem,
MenuItemConstructorOptions,
nativeTheme,
Notification,
Tray
} from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
// Electron modules import db, { GalleryDB } from '@core/datastore'
import { import picgo from '@core/picgo'
app,
Menu,
Tray,
dialog,
clipboard,
Notification,
screen,
nativeTheme
} from 'electron'
// Custom utilities and modules
import uploader from 'apis/app/uploader' import uploader from 'apis/app/uploader'
import db, { GalleryDB } from '~/main/apis/core/datastore' import { uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import { buildPicBedListMenu } from '~/events/remotes/menu'
import { T } from '~/i18n'
import clipboardPoll from '~/utils/clipboardPoll'
import { ensureFilePath, handleCopyUrl, setTray, tray } from '~/utils/common'
import { isMacOSVersionGreaterThanOrEqualTo } from '~/utils/getMacOSVersion'
import pasteTemplate from '~/utils/pasteTemplate'
import { configPaths } from '#/utils/configPaths'
import { IPasteStyle, IWindowList } from '#/types/enum' import { IPasteStyle, IWindowList } from '#/types/enum'
import pasteTemplate from '~/main/utils/pasteTemplate'
import pkg from 'root/package.json' import pkg from 'root/package.json'
import { ensureFilePath, handleCopyUrl } from '~/main/utils/common' import { hideMiniWindow, openMainWindow, openMiniWindow } from '~/utils/windowHelper'
import { T } from '~/main/i18n'
import { isMacOSVersionGreaterThanOrEqualTo } from '~/main/utils/getMacOSVersion'
import { buildPicBedListMenu } from '~/main/events/remotes/menu'
import clipboardPoll from '~/main/utils/clipboardPoll'
import picgo from '../../core/picgo'
import { uploadClipboardFiles } from '../uploader/apis'
import { configPaths } from '~/universal/utils/configPaths'
let contextMenu: Menu | null let contextMenu: Menu | null
let tray: Tray | null
export function setDockMenu () { export function setDockMenu () {
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false
const dockMenu = Menu.buildFromTemplate([ const dockMenu = Menu.buildFromTemplate([
{ {
label: T('OPEN_MAIN_WINDOW'), label: T('OPEN_MAIN_WINDOW'),
click () { click: openMainWindow
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
settingWindow!.show()
settingWindow!.focus()
if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
}, },
{ {
label: T('START_WATCH_CLIPBOARD'), label: T('START_WATCH_CLIPBOARD'),
@ -59,7 +52,7 @@ export function setDockMenu () {
}) })
setDockMenu() setDockMenu()
}, },
enabled: !isListeningClipboard visible: !isListeningClipboard
}, },
{ {
label: T('STOP_WATCH_CLIPBOARD'), label: T('STOP_WATCH_CLIPBOARD'),
@ -69,7 +62,7 @@ export function setDockMenu () {
clipboardPoll.removeAllListeners() clipboardPoll.removeAllListeners()
setDockMenu() setDockMenu()
}, },
enabled: isListeningClipboard visible: isListeningClipboard
} }
]) ])
app.dock.setMenu(dockMenu) app.dock.setMenu(dockMenu)
@ -81,59 +74,27 @@ export function createMenu () {
{ {
label: 'PicList', label: 'PicList',
submenu: [ submenu: [
{ { label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
label: T('OPEN_MAIN_WINDOW'), { label: T('RELOAD_APP'), click () { app.relaunch(); app.exit(0) } }
click () {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false
settingWindow!.show()
settingWindow!.focus()
if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
},
{
label: T('RELOAD_APP'),
click () {
app.relaunch()
app.exit(0)
}
}
] ]
}, },
{ { label: T('CHOOSE_DEFAULT_PICBED'), type: 'submenu', submenu },
label: T('CHOOSE_DEFAULT_PICBED'),
type: 'submenu',
// @ts-ignore
submenu
},
{ {
label: 'Edit', label: 'Edit',
// @ts-ignore
submenu: [ submenu: [
// @ts-ignore { label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' }, { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
// @ts-ignore
{ label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
{ type: 'separator' }, { type: 'separator' },
// @ts-ignore { label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' }, { label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
// @ts-ignore { label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' }, { label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectAll' }
// @ts-ignore
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
// @ts-ignore
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' }
] ]
}, },
{ {
label: T('QUIT'), label: T('QUIT'),
submenu: [ submenu: [
{ { label: T('QUIT'), role: 'quit' }
label: T('QUIT'),
role: 'quit'
}
] ]
} }
]) ])
@ -143,30 +104,9 @@ export function createMenu () {
export function createContextMenu () { export function createContextMenu () {
const ClipboardWatcher = clipboardPoll const ClipboardWatcher = clipboardPoll
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
if (process.platform === 'darwin' || process.platform === 'win32') { const isMiniWindowVisible = windowManager.has(IWindowList.MINI_WINDOW) && windowManager.get(IWindowList.MINI_WINDOW)!.isVisible()
const submenu = buildPicBedListMenu()
const template = [ const startWatchClipboard = () => {
{
label: T('OPEN_MAIN_WINDOW'),
click () {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false
settingWindow!.show()
settingWindow!.focus()
if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
},
{
label: T('CHOOSE_DEFAULT_PICBED'),
type: 'submenu',
// @ts-ignore
submenu
},
{
label: T('START_WATCH_CLIPBOARD'),
click () {
db.set(configPaths.settings.isListeningClipboard, true) db.set(configPaths.settings.isListeningClipboard, true)
ClipboardWatcher.startListening() ClipboardWatcher.startListening()
ClipboardWatcher.on('change', () => { ClipboardWatcher.on('change', () => {
@ -174,63 +114,29 @@ export function createContextMenu () {
uploadClipboardFiles() uploadClipboardFiles()
}) })
createContextMenu() createContextMenu()
}, }
enabled: !isListeningClipboard
}, const stopWatchClipboard = () => {
{
label: T('STOP_WATCH_CLIPBOARD'),
click () {
db.set(configPaths.settings.isListeningClipboard, false) db.set(configPaths.settings.isListeningClipboard, false)
ClipboardWatcher.stopListening() ClipboardWatcher.stopListening()
ClipboardWatcher.removeAllListeners() ClipboardWatcher.removeAllListeners()
createContextMenu() createContextMenu()
},
enabled: isListeningClipboard
},
{
label: T('RELOAD_APP'),
click () {
app.relaunch()
app.exit(0)
} }
},
// @ts-ignore if (process.platform === 'darwin' || process.platform === 'win32') {
{ const submenu = buildPicBedListMenu()
role: 'quit', const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
label: T('QUIT') { label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
} { label: T('CHOOSE_DEFAULT_PICBED'), type: 'submenu', submenu },
] as any { label: T('START_WATCH_CLIPBOARD'), click: startWatchClipboard, visible: !isListeningClipboard },
{ label: T('STOP_WATCH_CLIPBOARD'), click: stopWatchClipboard, visible: isListeningClipboard },
{ label: T('RELOAD_APP'), click () { app.relaunch(); app.exit(0) } },
{ label: T('QUIT'), role: 'quit' }
]
if (process.platform === 'win32') { if (process.platform === 'win32') {
template.splice(2, 0, template.splice(2, 0,
{ { label: T('OPEN_MINI_WINDOW'), click () { openMiniWindow(false) }, visible: !isMiniWindowVisible },
label: T('OPEN_MINI_WINDOW'), { label: T('HIDE_MINI_WINDOW'), click: hideMiniWindow, visible: isMiniWindowVisible }
click () {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
miniWindow.removeAllListeners()
if (db.get(configPaths.settings.miniWindowOntop)) {
miniWindow.setAlwaysOnTop(true)
}
const { width, height } = screen.getPrimaryDisplay().workAreaSize
const lastPosition = db.get(configPaths.settings.miniWindowPosition)
if (lastPosition) {
miniWindow.setPosition(lastPosition[0], lastPosition[1])
} else {
miniWindow.setPosition(width - 100, height - 100)
}
const setPositionFunc = () => {
const position = miniWindow.getPosition()
db.set(configPaths.settings.miniWindowPosition, position)
}
miniWindow.on('close', setPositionFunc)
miniWindow.on('move', setPositionFunc)
miniWindow.show()
miniWindow.focus()
const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false
if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) {
windowManager.get(IWindowList.SETTING_WINDOW)!.hide()
}
}
}
) )
} }
contextMenu = Menu.buildFromTemplate(template) contextMenu = Menu.buildFromTemplate(template)
@ -243,70 +149,11 @@ export function createContextMenu () {
// 目前的实现无法正常工作 // 目前的实现无法正常工作
contextMenu = Menu.buildFromTemplate([ contextMenu = Menu.buildFromTemplate([
{ { label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
label: T('OPEN_MAIN_WINDOW'), { label: T('OPEN_MINI_WINDOW'), click () { openMiniWindow(false) }, visible: !isMiniWindowVisible },
click () { { label: T('HIDE_MINI_WINDOW'), click: hideMiniWindow, visible: isMiniWindowVisible },
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) { label: T('START_WATCH_CLIPBOARD'), click: startWatchClipboard, visible: !isListeningClipboard },
const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false { label: T('STOP_WATCH_CLIPBOARD'), click: stopWatchClipboard, visible: isListeningClipboard },
settingWindow!.show()
settingWindow!.focus()
if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
},
{
label: T('OPEN_MINI_WINDOW'),
click () {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
miniWindow.removeAllListeners()
if (db.get(configPaths.settings.miniWindowOntop)) {
miniWindow.setAlwaysOnTop(true)
}
const { width, height } = screen.getPrimaryDisplay().workAreaSize
const lastPosition = db.get(configPaths.settings.miniWindowPosition)
if (lastPosition) {
miniWindow.setPosition(lastPosition[0], lastPosition[1])
} else {
miniWindow.setPosition(width - 100, height - 100)
}
const setPositionFunc = () => {
const position = miniWindow.getPosition()
db.set(configPaths.settings.miniWindowPosition, position)
}
miniWindow.on('close', setPositionFunc)
miniWindow.on('move', setPositionFunc)
miniWindow.show()
miniWindow.focus()
const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false
if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) {
windowManager.get(IWindowList.SETTING_WINDOW)!.hide()
}
}
},
{
label: T('START_WATCH_CLIPBOARD'),
click () {
db.set(configPaths.settings.isListeningClipboard, true)
ClipboardWatcher.startListening()
ClipboardWatcher.on('change', () => {
picgo.log.info('clipboard changed')
uploadClipboardFiles()
})
createContextMenu()
},
enabled: !isListeningClipboard
},
{
label: T('STOP_WATCH_CLIPBOARD'),
click () {
db.set(configPaths.settings.isListeningClipboard, false)
ClipboardWatcher.stopListening()
ClipboardWatcher.removeAllListeners()
createContextMenu()
},
enabled: isListeningClipboard
},
{ {
label: T('ABOUT'), label: T('ABOUT'),
click () { click () {
@ -318,11 +165,7 @@ export function createContextMenu () {
}) })
} }
}, },
// @ts-ignore { label: T('QUIT'), role: 'quit' }
{
role: 'quit',
label: T('QUIT')
}
]) ])
} }
} }
@ -336,9 +179,10 @@ const getTrayIcon = () => {
} }
} }
export function createTray () { export function createTray (tooltip: string) {
const menubarPic = getTrayIcon() const menubarPic = getTrayIcon()
tray = new Tray(menubarPic) setTray(new Tray(menubarPic))
tray.setToolTip(tooltip)
// click事件在Mac和Windows上可以触发在Ubuntu上无法触发Unity不支持 // click事件在Mac和Windows上可以触发在Ubuntu上无法触发Unity不支持
if (process.platform === 'darwin' || process.platform === 'win32') { if (process.platform === 'darwin' || process.platform === 'win32') {
tray.on('right-click', () => { tray.on('right-click', () => {
@ -348,7 +192,7 @@ export function createTray () {
createContextMenu() createContextMenu()
tray!.popUpContextMenu(contextMenu!) tray!.popUpContextMenu(contextMenu!)
}) })
tray.on('click', (event, bounds) => { tray.on('click', (_, bounds) => {
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
toggleWindow(bounds) toggleWindow(bounds)
setTimeout(async () => { setTimeout(async () => {
@ -412,7 +256,7 @@ export function createTray () {
// drop-files only be supported in macOS // drop-files only be supported in macOS
// so the tray window must be available // so the tray window must be available
tray.on('drop-files', async (event: Event, files: string[]) => { tray.on('drop-files', async (_: Event, files: string[]) => {
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const rawInput = cloneDeep(files) const rawInput = cloneDeep(files)
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)! const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!

View File

@ -1,30 +1,22 @@
// External dependencies
import fs from 'fs-extra'
import { cloneDeep } from 'lodash'
// Electron modules
import { import {
Notification, Notification,
WebContents WebContents
} from 'electron' } from 'electron'
import fs from 'fs-extra'
import { cloneDeep } from 'lodash'
// Custom utilities and modules
import windowManager from 'apis/app/window/windowManager'
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 ALLApi from '@/apis/allApi'
import picgo from '@core/picgo' import picgo from '@core/picgo'
import GuiApi from '../../gui' import db, { GalleryDB } from '@core/datastore'
import uploader from '.'
import uploader from 'apis/app/uploader'
import windowManager from 'apis/app/window/windowManager'
import { T } from '~/i18n/index'
import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common'
import pasteTemplate from '~/utils/pasteTemplate'
import { IPasteStyle, IWindowList } from '#/types/enum' import { IPasteStyle, IWindowList } from '#/types/enum'
import { picBedsCanbeDeleted } from '#/utils/static' import { configPaths } from '#/utils/configPaths'
import path from 'path'
import SSHClient from '~/main/utils/sshClient'
import { ISftpPlistConfig } from 'piclist'
import { getRawData } from '~/renderer/utils/common'
import { configPaths } from '~/universal/utils/configPaths'
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => { const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
const useBuiltinClipboard = db.get(configPaths.settings.useBuiltinClipboard) === undefined ? true : !!db.get(configPaths.settings.useBuiltinClipboard) const useBuiltinClipboard = db.get(configPaths.settings.useBuiltinClipboard) === undefined ? true : !!db.get(configPaths.settings.useBuiltinClipboard)
@ -129,62 +121,3 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
return [] return []
} }
} }
async function deleteSFTPFile (config: ISftpPlistConfig, fileName: string) {
try {
const client = SSHClient.instance
await client.connect(config)
const uploadPath = `/${(config.uploadPath || '')}/`.replace(/\/+/g, '/')
const remote = path.join(uploadPath, fileName)
const deleteResult = await client.deleteFileSFTP(config, remote)
client.close()
return deleteResult
} catch (err: any) {
picgo.log.error(err)
return false
}
}
export const deleteChoosedFiles = async (list: ImgInfo[]): Promise<boolean[]> => {
const result = []
for (const item of list) {
if (item.id) {
try {
const dbStore = GalleryDB.getInstance()
const file = await dbStore.getById(item.id)
await dbStore.removeById(item.id)
if (await picgo.getConfig(configPaths.settings.deleteCloudFile)) {
if (item.type !== undefined && picBedsCanbeDeleted.includes(item.type)) {
const noteFunc = (value: boolean) => {
const notification = new Notification({
title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'),
body: T(value ? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED' : 'GALLERY_SYNC_DELETE_NOTICE_FAILED')
})
notification.show()
}
if (item.type === 'sftpplist') {
const { fileName, config } = item
setTimeout(() => {
deleteSFTPFile(getRawData(config), fileName || '').then(noteFunc)
}, 0)
} else {
setTimeout(() => {
ALLApi.delete(item).then(noteFunc)
}, 0)
}
}
}
setTimeout(() => {
picgo.emit('remove', [file], GuiApi.getInstance())
}, 500)
result.push(true)
} catch (e) {
result.push(false)
}
}
}
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
}
return result
}

View File

@ -1,39 +1,34 @@
// External dependencies
import dayjs from 'dayjs' import dayjs from 'dayjs'
import {
BrowserWindow,
clipboard,
ipcMain,
Notification,
WebContents
} from 'electron'
import fs from 'fs-extra'
import util from 'util' import util from 'util'
import path from 'path' import path from 'path'
import writeFile from 'write-file-atomic'
import fse from 'fs-extra'
// Electron modules
import {
Notification,
BrowserWindow,
ipcMain,
WebContents,
clipboard
} from 'electron'
// Custom utilities and modules
import picgo from '@core/picgo'
import db from '~/main/apis/core/datastore'
import windowManager from 'apis/app/window/windowManager'
import { showNotification, getClipboardFilePath, calcDurationRange } from '~/main/utils/common'
import logger from '@core/picgo/logger'
import { T } from '~/main/i18n'
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static'
// Custom types/enums
import { IWindowList } from '#/types/enum'
// External utility functions
import { IPicGo } from 'piclist' import { IPicGo } from 'piclist'
import writeFile from 'write-file-atomic'
import windowManager from 'apis/app/window/windowManager'
import db from '@core/datastore'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import { T } from '~/i18n'
import { showNotification, getClipboardFilePath, calcDurationRange } from '~/utils/common'
import { import {
GET_RENAME_FILE_NAME, GET_RENAME_FILE_NAME,
RENAME_FILE_NAME, RENAME_FILE_NAME,
TALKING_DATA_EVENT TALKING_DATA_EVENT
} from '~/universal/events/constants' } from '#/events/constants'
import { configPaths } from '~/universal/utils/configPaths' import { ICOREBuildInEvent, IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static'
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => { const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
return new Promise((resolve) => { return new Promise((resolve) => {
@ -66,21 +61,22 @@ const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) =>
class Uploader { class Uploader {
private webContents: WebContents | null = null private webContents: WebContents | null = null
// private uploading: boolean = false
constructor () { constructor () {
this.init() this.init()
} }
init () { init () {
picgo.on('notification', (message: Electron.NotificationConstructorOptions | undefined) => { picgo.on(ICOREBuildInEvent.NOTIFICATION, (message: Electron.NotificationConstructorOptions | undefined) => {
const notification = new Notification(message) const notification = new Notification(message)
notification.show() notification.show()
}) })
picgo.on('uploadProgress', (progress: any) => { picgo.on(ICOREBuildInEvent.UPLOAD_PROGRESS, (progress: any) => {
this.webContents?.send('uploadProgress', progress) this.webContents?.send('uploadProgress', progress)
}) })
picgo.on('beforeTransform', () => {
picgo.on(ICOREBuildInEvent.BEFORE_TRANSFORM, () => {
if (db.get(configPaths.settings.uploadNotification)) { if (db.get(configPaths.settings.uploadNotification)) {
const notification = new Notification({ const notification = new Notification({
title: T('UPLOAD_PROGRESS'), title: T('UPLOAD_PROGRESS'),
@ -89,6 +85,7 @@ class Uploader {
notification.show() notification.show()
} }
}) })
picgo.helper.beforeUploadPlugins.register('renameFn', { picgo.helper.beforeUploadPlugins.register('renameFn', {
handle: async (ctx: IPicGo) => { handle: async (ctx: IPicGo) => {
const rename = db.get(configPaths.settings.rename) const rename = db.get(configPaths.settings.rename)
@ -155,7 +152,7 @@ class Uploader {
return false return false
} finally { } finally {
if (filePath) { if (filePath) {
fse.unlink(filePath) fs.remove(filePath)
} }
} }
} }

View File

@ -1,12 +1,8 @@
const isDevelopment = process.env.NODE_ENV !== 'production' const isDevelopment = process.env.NODE_ENV !== 'production'
export const TRAY_WINDOW_URL = isDevelopment export const MANUAL_WINDOW_URL = process.env.NODE_ENV === 'development'
? (process.env.WEBPACK_DEV_SERVER_URL as string) ? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#documents`
: 'picgo://./index.html' : 'picgo://./index.html#documents'
export const SETTING_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#main-page/upload`
: 'picgo://./index.html#main-page/upload'
export const MINI_WINDOW_URL = isDevelopment export const MINI_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page` ? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page`
@ -16,10 +12,14 @@ export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page` ? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page`
: 'picgo://./index.html#rename-page' : 'picgo://./index.html#rename-page'
export const SETTING_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#main-page/upload`
: 'picgo://./index.html#main-page/upload'
export const TRAY_WINDOW_URL = isDevelopment
? (process.env.WEBPACK_DEV_SERVER_URL as string)
: 'picgo://./index.html'
export const TOOLBOX_WINDOW_URL = process.env.NODE_ENV === 'development' export const TOOLBOX_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#toolbox-page` ? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#toolbox-page`
: 'picgo://./index.html#toolbox-page' : 'picgo://./index.html#toolbox-page'
export const MANUAL_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#documents`
: 'picgo://./index.html#documents'

View File

@ -1,45 +1,37 @@
// External dependencies
import { app } from 'electron' import { app } from 'electron'
// Electron modules
// Custom utilities and modules
import bus from '@core/bus'
import db from '~/main/apis/core/datastore'
import picgo from '~/main/apis/core/picgo'
import { T } from '~/main/i18n'
import { import {
SETTING_WINDOW_URL, MANUAL_WINDOW_URL,
TRAY_WINDOW_URL,
MINI_WINDOW_URL, MINI_WINDOW_URL,
RENAME_WINDOW_URL, RENAME_WINDOW_URL,
TOOLBOX_WINDOW_URL, SETTING_WINDOW_URL,
MANUAL_WINDOW_URL TRAY_WINDOW_URL,
TOOLBOX_WINDOW_URL
} from './constants' } from './constants'
// Custom types/enums import bus from '@core/bus'
import { IWindowList } from '#/types/enum'
// External utility functions
import { CREATE_APP_MENU } from '@core/bus/constants' import { CREATE_APP_MENU } from '@core/bus/constants'
import db from '@core/datastore'
import { T } from '~/i18n'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants' import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { configPaths } from '~/universal/utils/configPaths' import { IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
const windowList = new Map<IWindowList, IWindowListItem>() const windowList = new Map<IWindowList, IWindowListItem>()
const handleWindowParams = (windowURL: string) => windowURL const handleWindowParams = (windowURL: string) => windowURL
const getDefaultWindowSizes = (): { width: number, height: number } => { const getDefaultWindowSizes = (): { width: number, height: number } => {
const mainWindowWidth = picgo.getConfig<any>(configPaths.settings.mainWindowWidth) const [mainWindowWidth, mainWindowHeight] = db.get([configPaths.settings.mainWindowWidth, configPaths.settings.mainWindowHeight])
const mainWindowHeight = picgo.getConfig<any>(configPaths.settings.mainWindowHeight)
return { return {
width: mainWindowWidth || 1200, width: mainWindowWidth || 1200,
height: mainWindowHeight || 800 height: mainWindowHeight || 800
} }
} }
const defaultWindowWidth = getDefaultWindowSizes().width const { width: defaultWindowWidth, height: defaultWindowHeight } = getDefaultWindowSizes()
const defaultWindowHeight = getDefaultWindowSizes().height
const trayWindowOptions = { const trayWindowOptions = {
height: 350, height: 350,

View File

@ -1,17 +1,12 @@
// External dependencies
import windowList from './windowList'
// Electron modules
import { BrowserWindow } from 'electron' import { BrowserWindow } from 'electron'
// Custom utilities and modules import windowList from 'apis/app/window/windowList'
// Custom types/enums
import { IWindowList } from '#/types/enum' import { IWindowList } from '#/types/enum'
class WindowManager implements IWindowManager { class WindowManager implements IWindowManager {
#windowMap: Map<IWindowList | string, BrowserWindow> = new Map() #windowMap: Map<IWindowList | string, BrowserWindow> = new Map()
#windowIdMap: Map<number, IWindowList | string> = new Map() #windowIdMap: Map<number, IWindowList | string> = new Map()
create (name: IWindowList) { create (name: IWindowList) {
const windowConfig: IWindowListItem = windowList.get(name)! const windowConfig: IWindowListItem = windowList.get(name)!
if (windowConfig.isValid) { if (windowConfig.isValid) {

View File

@ -1,14 +1,14 @@
import bus from '.' import bus from '@core/bus/index'
import { import {
UPLOAD_WITH_FILES, GET_SETTING_WINDOW_ID,
UPLOAD_WITH_FILES_RESPONSE, GET_SETTING_WINDOW_ID_RESPONSE,
UPLOAD_WITH_CLIPBOARD_FILES,
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
GET_WINDOW_ID, GET_WINDOW_ID,
GET_WINDOW_ID_REPONSE, GET_WINDOW_ID_REPONSE,
GET_SETTING_WINDOW_ID, UPLOAD_WITH_CLIPBOARD_FILES,
GET_SETTING_WINDOW_ID_RESPONSE UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
} from './constants' UPLOAD_WITH_FILES,
UPLOAD_WITH_FILES_RESPONSE
} from '@core/bus/constants'
export const uploadWithClipboardFiles = (): Promise<{ export const uploadWithClipboardFiles = (): Promise<{
success: boolean, success: boolean,

View File

@ -1,3 +1,4 @@
export const CREATE_APP_MENU = 'CREATE_APP_MENU'
export const GET_WINDOW_ID = 'GET_WINDOW_ID' // get a current window export const GET_WINDOW_ID = 'GET_WINDOW_ID' // get a current window
export const GET_WINDOW_ID_REPONSE = 'GET_WINDOW_ID_REPONSE' export const GET_WINDOW_ID_REPONSE = 'GET_WINDOW_ID_REPONSE'
export const GET_SETTING_WINDOW_ID = 'GET_SETTING_WINDOW_ID' // get setting window export const GET_SETTING_WINDOW_ID = 'GET_SETTING_WINDOW_ID' // get setting window
@ -6,4 +7,3 @@ export const UPLOAD_WITH_FILES = 'UPLOAD_WITH_FILES'
export const UPLOAD_WITH_FILES_RESPONSE = 'UPLOAD_WITH_FILES_RESPONSE' 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 = 'UPLOAD_WITH_CLIPBOARD_FILES'
export const UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE = 'UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE' export const UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE = 'UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE'
export const CREATE_APP_MENU = 'CREATE_APP_MENU'

View File

@ -1,28 +1,19 @@
// External dependencies import { app } from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import path from 'path' import path from 'path'
// Electron modules
import { app } from 'electron'
// Custom utilities and modules
import { getLogger } from '../utils/localLogger'
// Custom types/enums
// External utility functions
// External utility functions
import writeFile from 'write-file-atomic' import writeFile from 'write-file-atomic'
// Custom types/enums import { getLogger } from '@core/utils/localLogger'
import { T } from '~/main/i18n'
import { T } from '~/i18n'
const STORE_PATH = app.getPath('userData') const STORE_PATH = app.getPath('userData')
const configFilePath = path.join(STORE_PATH, 'data.json') const configFilePath = path.join(STORE_PATH, 'data.json')
const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json') const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json')
export const defaultConfigPath = configFilePath export const defaultConfigPath = configFilePath
let _configFilePath = '' let _configFilePath = ''
let hasCheckPath = false let hasCheckPath = false

View File

@ -1,19 +1,12 @@
// External dependencies
import fs from 'fs-extra' import fs from 'fs-extra'
// Electron modules
// Custom utilities and modules
import { dbPathChecker, dbPathDir, getGalleryDBPath } from './dbChecker'
// Custom types/enums
// External utility functions
import { DBStore, JSONStore } from '@picgo/store' import { DBStore, JSONStore } from '@picgo/store'
// External utility functions import { dbPathChecker, dbPathDir, getGalleryDBPath } from '@core/datastore/dbChecker'
import { T } from '~/main/i18n'
import { configPaths } from '~/universal/utils/configPaths' import { T } from '~/i18n'
import { configPaths } from '#/utils/configPaths'
import { IJSON } from '@picgo/store/dist/types'
import { IConfig } from 'piclist'
const STORE_PATH = dbPathDir() const STORE_PATH = dbPathDir()
@ -25,6 +18,7 @@ export const DB_PATH: string = getGalleryDBPath().dbPath
class ConfigStore { class ConfigStore {
#db: JSONStore #db: JSONStore
constructor () { constructor () {
this.#db = new JSONStore(CONFIG_PATH) this.#db = new JSONStore(CONFIG_PATH)
@ -49,34 +43,54 @@ class ConfigStore {
this.read() this.read()
} }
flush () { read (flush?: boolean): IJSON {
this.#db = new JSONStore(CONFIG_PATH) return this.#db.read(flush)
} }
read () { getSingle (key = ''): any {
this.#db.read()
return this.#db
}
get (key = ''): any {
if (key === '') { if (key === '') {
return this.#db.read() return this.#db.read(true)
} }
this.read(true)
return this.#db.get(key) return this.#db.get(key)
} }
get (key: string): any
get (key: string[]): any[]
get (key: string | string[] = ''): any {
if (Array.isArray(key)) {
return key.map(k => this.getSingle(k))
}
return this.getSingle(key)
}
set (key: string, value: any): void { set (key: string, value: any): void {
this.read(true)
return this.#db.set(key, value) return this.#db.set(key, value)
} }
has (key: string) { has (key: string) {
this.read(true)
return this.#db.has(key) return this.#db.has(key)
} }
unset (key: string, value: any): boolean { unset (key: string, value: any): boolean {
this.read(true)
return this.#db.unset(key, value) return this.#db.unset(key, value)
} }
saveConfig (config: Partial<IConfig>): void {
Object.keys(config).forEach((name: string) => {
this.set(name, config[name])
})
}
removeConfig (config: IConfig): void {
Object.keys(config).forEach((name: string) => {
this.unset(name, config[name])
})
}
getConfigPath () { getConfigPath () {
return CONFIG_PATH return CONFIG_PATH
} }

View File

@ -1,17 +1,10 @@
// External dependencies
import pkg from 'root/package.json'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
// Electron modules
// Custom utilities and modules
import { PicGo } from 'piclist' import { PicGo } from 'piclist'
import db from 'apis/core/datastore'
import { dbChecker, dbPathChecker } from 'apis/core/datastore/dbChecker'
// Custom types/enums import db from '@core/datastore'
import { dbChecker, dbPathChecker } from '@core/datastore/dbChecker'
// External utility functions import pkg from 'root/package.json'
const CONFIG_PATH = dbPathChecker() const CONFIG_PATH = dbPathChecker()
@ -29,7 +22,7 @@ picgo.GUI_VERSION = global.PICGO_GUI_VERSION
const originPicGoSaveConfig = picgo.saveConfig.bind(picgo) const originPicGoSaveConfig = picgo.saveConfig.bind(picgo)
function flushDB () { function flushDB () {
db.flush() db.read(true)
} }
const debounced = debounce(flushDB, 1000) const debounced = debounce(flushDB, 1000)

View File

@ -1,31 +1,27 @@
// External dependencies import {
BrowserWindow,
dialog,
ipcMain,
Notification
} from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
// Electron modules
import {
dialog,
BrowserWindow,
Notification,
ipcMain
} from 'electron'
// Custom utilities and modules
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 '~/main/utils/pasteTemplate'
import { handleCopyUrl } from '~/main/utils/common'
import { getWindowId, getSettingWindowId } from '@core/bus/apis'
import { SHOW_INPUT_BOX } from '~/universal/events/constants'
// Custom types/enums
// External utility functions
import { DBStore } from '@picgo/store' import { DBStore } from '@picgo/store'
import { T } from '~/main/i18n'
import { configPaths } from '~/universal/utils/configPaths' import { getWindowId, getSettingWindowId } from '@core/bus/apis'
import { IPasteStyle } from '~/universal/types/enum'
import db, { GalleryDB } from '@core/datastore'
import { dbPathChecker, defaultConfigPath, getGalleryDBPath } from '@core/datastore/dbChecker'
import uploader from 'apis/app/uploader'
import { T } from '~/i18n'
import { handleCopyUrl } from '~/utils/common'
import pasteTemplate from '~/utils/pasteTemplate'
import { SHOW_INPUT_BOX } from '#/events/constants'
import { IPasteStyle } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
// Cross-process support may be required in the future // Cross-process support may be required in the future
class GuiApi implements IGuiApi { class GuiApi implements IGuiApi {
@ -69,7 +65,7 @@ class GuiApi implements IGuiApi {
await this.showSettingWindow() await this.showSettingWindow()
this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options) this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
return new Promise<string>((resolve) => { return new Promise<string>((resolve) => {
ipcMain.once(SHOW_INPUT_BOX, (event: Event, value: string) => { ipcMain.once(SHOW_INPUT_BOX, (_: Event, value: string) => {
resolve(value) resolve(value)
}) })
}) })

View File

@ -1,34 +1,27 @@
// External dependencies
import bus from '@core/bus' import bus from '@core/bus'
// Electron modules
// Custom utilities and modules
import { import {
uploadClipboardFiles, CREATE_APP_MENU,
uploadChoosedFiles
} from 'apis/app/uploader/apis'
import {
createMenu
} from 'apis/app/system'
import windowManager from 'apis/app/window/windowManager'
// Custom types/enums
import { IWindowList } from '#/types/enum'
// External utility functions
import {
UPLOAD_WITH_FILES,
UPLOAD_WITH_FILES_RESPONSE,
UPLOAD_WITH_CLIPBOARD_FILES,
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
GET_WINDOW_ID, GET_WINDOW_ID,
GET_WINDOW_ID_REPONSE, GET_WINDOW_ID_REPONSE,
GET_SETTING_WINDOW_ID, GET_SETTING_WINDOW_ID,
GET_SETTING_WINDOW_ID_RESPONSE, GET_SETTING_WINDOW_ID_RESPONSE,
CREATE_APP_MENU UPLOAD_WITH_FILES,
UPLOAD_WITH_FILES_RESPONSE,
UPLOAD_WITH_CLIPBOARD_FILES,
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE
} from '@core/bus/constants' } from '@core/bus/constants'
import {
createMenu
} from 'apis/app/system'
import {
uploadChoosedFiles,
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum'
function initEventCenter () { function initEventCenter () {
const eventList: any = { const eventList: any = {
'picgo:upload': uploadClipboardFiles, 'picgo:upload': uploadClipboardFiles,

View File

@ -1,437 +0,0 @@
// Electron 相关
import {
app,
ipcMain,
shell,
Notification,
IpcMainEvent,
BrowserWindow,
screen,
IpcMainInvokeEvent
} from 'electron'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { ILogType, IPasteStyle, IWindowList } from '#/types/enum'
// 上传器
import uploader from 'apis/app/uploader'
// 粘贴模板函数
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 fs from 'fs-extra'
// 事件常量
import {
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,
RELOAD_APP,
SHOW_PLUGIN_PAGE_MENU,
SET_MINI_WINDOW_POS,
GET_PICBEDS,
HIDE_DOCK
} from '#/events/constants'
// 上传剪贴板文件和已选文件的函数
import {
uploadClipboardFiles,
uploadChoosedFiles
} from '~/main/apis/app/uploader/apis'
// 核心 IPC 模块
import picgoCoreIPC from './picgoCoreIPC'
// 处理复制的 URL 和生成短链接的函数
import { handleCopyUrl, generateShortUrl } from '~/main/utils/common'
// 构建主页面、迷你页面、插件页面、图片床列表的菜单函数
import { buildMainPageMenu, buildMiniPageMenu, buildPluginPageMenu, buildPicBedListMenu } from './remotes/menu'
// 路径处理库
import path from 'path'
// i18n 模块
import { T } from '~/main/i18n'
// 同步设置的上传和下载文件函数
import { uploadFile, downloadFile } from '../utils/syncSettings'
// SSH 客户端模块
import SSHClient from '../utils/sshClient'
// Sftp 配置类型声明
import { ISftpPlistConfig } from 'piclist'
import { removeFileFromS3InMain, removeFileFromDogeInMain, removeFileFromHuaweiInMain } from '~/main/utils/deleteFunc'
import webServer from '../server/webServer'
import { configPaths } from '~/universal/utils/configPaths'
import logger from '../apis/core/picgo/logger'
const STORE_PATH = app.getPath('userData')
const commonConfigList = ['data.json', 'data.bak.json']
const manageConfigList = ['manage.json', 'manage.bak.json']
export default {
listen () {
picgoCoreIPC.listen()
// Upload Related IPC
// from macOS tray
ipcMain.on('uploadClipboardFiles', async () => {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
// macOS use builtin clipboard is OK
const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard()
if (img !== false) {
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
handleCopyUrl(await (pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink))))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),
body: img[0].imgUrl!
// icon: file[0]
// icon: img[0].imgUrl
})
notification.show()
}
await GalleryDB.getInstance().insert(img[0])
trayWindow.webContents.send('clipboardFiles', [])
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
}
}
trayWindow.webContents.send('uploadFiles')
})
ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
uploadClipboardFiles()
})
ipcMain.on('uploadChoosedFiles', async (evt: IpcMainEvent, files: IFileWithPath[]) => {
return uploadChoosedFiles(evt.sender, files)
})
// ShortKey Related IPC
ipcMain.on('updateShortKey', (evt: IpcMainEvent, item: IShortKeyConfig, oldKey: string, from: string) => {
const result = shortKeyHandler.updateShortKey(item, oldKey, from)
evt.sender.send('updateShortKeyResponse', result)
if (result) {
const notification = new Notification({
title: T('OPERATION_SUCCEED'),
body: T('TIPS_SHORTCUT_MODIFIED_SUCCEED')
})
notification.show()
} else {
const notification = new Notification({
title: T('OPERATION_FAILED'),
body: T('TIPS_SHORTCUT_MODIFIED_CONFLICT')
})
notification.show()
}
})
ipcMain.on('bindOrUnbindShortKey', (_: IpcMainEvent, item: IShortKeyConfig, from: string) => {
const result = shortKeyHandler.bindOrUnbindShortKey(item, from)
if (result) {
const notification = new Notification({
title: T('OPERATION_SUCCEED'),
body: T('TIPS_SHORTCUT_MODIFIED_SUCCEED')
})
notification.show()
} else {
const notification = new Notification({
title: T('OPERATION_FAILED'),
body: T('TIPS_SHORTCUT_MODIFIED_CONFLICT')
})
notification.show()
}
})
// Gallery image cloud delete IPC
ipcMain.on('logDeleteMsg', async (_: IpcMainEvent, msg: string, logLevel: ILogType) => {
logger[logLevel](msg)
})
ipcMain.handle('delete-sftp-file', async (_: IpcMainInvokeEvent, config: ISftpPlistConfig, fileName: string) => {
try {
const client = SSHClient.instance
await client.connect(config)
const uploadPath = `/${(config.uploadPath || '')}/`.replace(/\/+/g, '/')
const remote = path.join(uploadPath, fileName)
const deleteResult = await client.deleteFileSFTP(config, remote)
client.close()
return deleteResult
} catch (err: any) {
logger.error(err)
return false
}
})
ipcMain.handle('delete-aws-s3-file', async (_: IpcMainInvokeEvent, configMap: IStringKeyMap) => {
const result = await removeFileFromS3InMain(configMap)
return result
})
ipcMain.handle('delete-doge-file', async (_: IpcMainInvokeEvent, configMap: IStringKeyMap) => {
const result = await removeFileFromDogeInMain(configMap)
return result
})
ipcMain.handle('delete-huaweicloud-file', async (_: IpcMainInvokeEvent, configMap: IStringKeyMap) => {
const result = await removeFileFromHuaweiInMain(configMap)
return result
})
// migrate from PicGo
ipcMain.handle('migrateFromPicGo', async () => {
const picGoConfigPath = STORE_PATH.replace('piclist', 'picgo')
const files = [
'data.json',
'data.bak.json',
'picgo.db',
'picgo.bak.db'
]
try {
await Promise.all(files.map(async file => {
const sourcePath = path.join(picGoConfigPath, file)
const targetPath = path.join(STORE_PATH, file.replace('picgo', 'piclist'))
await fs.remove(targetPath)
await fs.copy(sourcePath, targetPath, { overwrite: true })
}
))
return true
} catch (err: any) {
logger.error(err)
return false
}
})
// PicList Setting page IPC
ipcMain.on('updateCustomLink', () => {
const notification = new Notification({
title: T('OPERATION_SUCCEED'),
body: T('TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED')
})
notification.show()
})
ipcMain.on('autoStart', (_: IpcMainEvent, val: boolean) => {
app.setLoginItemSettings({
openAtLogin: val
})
})
ipcMain.handle('getShortUrl', async (_: IpcMainInvokeEvent, url: string) => {
const shortUrl = await generateShortUrl(url)
return shortUrl
})
ipcMain.handle('uploadCommonConfig', async () => {
return await uploadFile(commonConfigList)
})
ipcMain.handle('downloadCommonConfig', async () => {
return await downloadFile(commonConfigList)
})
ipcMain.handle('uploadManageConfig', async () => {
return await uploadFile(manageConfigList)
})
ipcMain.handle('downloadManageConfig', async () => {
return await downloadFile(manageConfigList)
})
ipcMain.handle('uploadAllConfig', async () => {
return await uploadFile([...commonConfigList, ...manageConfigList])
})
ipcMain.handle('downloadAllConfig', async () => {
return await downloadFile([...commonConfigList, ...manageConfigList])
})
ipcMain.on('toggleMainWindowAlwaysOnTop', () => {
const mainWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
const isAlwaysOnTop = mainWindow.isAlwaysOnTop()
mainWindow.setAlwaysOnTop(!isAlwaysOnTop)
})
// Window operation API
ipcMain.on('openSettingWindow', () => {
windowManager.get(IWindowList.SETTING_WINDOW)!.show()
const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false
if (autoCloseMiniWindow) {
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
})
ipcMain.on('openManualWindow', () => {
windowManager.get(IWindowList.MANUAL_WINDOW)!.show()
})
ipcMain.on('openMiniWindow', () => {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
miniWindow.removeAllListeners()
if (db.get(configPaths.settings.miniWindowOntop)) {
miniWindow.setAlwaysOnTop(true)
}
const { width, height } = screen.getPrimaryDisplay().workAreaSize
const lastPosition = db.get(configPaths.settings.miniWindowPosition)
if (lastPosition) {
miniWindow.setPosition(lastPosition[0], lastPosition[1])
} else {
miniWindow.setPosition(width - 100, height - 100)
}
const setPositionFunc = () => {
const position = miniWindow.getPosition()
db.set(configPaths.settings.miniWindowPosition, position)
}
miniWindow.on('close', setPositionFunc)
miniWindow.on('move', setPositionFunc)
miniWindow.show()
miniWindow.focus()
settingWindow.hide()
})
ipcMain.on('updateMiniIcon', (_: IpcMainEvent, iconPath: string) => {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
miniWindow.webContents.send('updateMiniIcon', iconPath)
})
ipcMain.on('miniWindowOntop', (_: IpcMainEvent, val: boolean) => {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
miniWindow.setAlwaysOnTop(val)
})
ipcMain.on('refreshSettingWindow', () => {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
settingWindow.webContents.reloadIgnoringCache()
})
// from mini window
ipcMain.on('syncPicBed', () => {
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
})
ipcMain.on(GET_PICBEDS, (evt: IpcMainEvent) => {
const picBeds = getPicBeds()
evt.sender.send(GET_PICBEDS, picBeds)
evt.returnValue = picBeds
})
ipcMain.on(TOGGLE_SHORTKEY_MODIFIED_MODE, (_: IpcMainEvent, val: boolean) => {
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, val)
})
ipcMain.on('updateServer', () => {
server.restart()
})
ipcMain.on('stopWebServer', () => {
webServer.stop()
})
ipcMain.on('restartWebServer', () => {
webServer.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, (_: 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, (_: IpcMainEvent, filePath: string) => {
const abFilePath = path.join(STORE_PATH, filePath)
shell.openPath(abFilePath)
})
ipcMain.on(OPEN_URL, (_: IpcMainEvent, url: string) => {
shell.openExternal(url)
})
ipcMain.on(RELOAD_APP, () => {
app.relaunch()
app.exit(0)
})
ipcMain.on(SET_MINI_WINDOW_POS, (_: IpcMainEvent, pos: IMiniWindowPos) => {
const window = BrowserWindow.getFocusedWindow()
window?.setBounds(pos)
})
ipcMain.on(HIDE_DOCK, (_: IpcMainEvent, val: boolean) => {
if (val) {
app.dock.hide()
} else {
app.dock.show()
}
})
},
dispose () {}
}

View File

@ -1,456 +0,0 @@
// External dependencies
import path from 'path'
import {
dialog,
shell,
IpcMainEvent,
ipcMain,
clipboard
} from 'electron'
import fs from 'fs-extra'
// Electron modules
// Custom utilities and modules
import GuiApi from 'apis/gui'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import picgo from '@core/picgo'
import { handleStreamlinePluginName, simpleClone } from '~/universal/utils/common'
import { IGuiMenuItem, PicGo as PicGoCore } from 'piclist'
import windowManager from 'apis/app/window/windowManager'
import { showNotification } from '~/main/utils/common'
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
import { GalleryDB } from 'apis/core/datastore'
import pasteTemplate from '../utils/pasteTemplate'
import { i18nManager, T } from '~/main/i18n'
import { rpcServer } from './rpc'
// Custom types/enums
import { IPasteStyle, IPicGoHelperType, IWindowList } from '#/types/enum'
import { IObject, IFilter } from '@picgo/store/dist/types'
// External utility functions
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,
PICGO_OPEN_DIRECTORY,
PASTE_TEXT,
OPEN_WINDOW,
GET_LANGUAGE_LIST,
SET_CURRENT_LANGUAGE,
GET_CURRENT_LANGUAGE
} from '#/events/constants'
import { configPaths } from '~/universal/utils/configPaths'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
// const PluginHandler = requireFunc('picgo/lib/PluginHandler').default
const STORE_PATH = path.dirname(dbPathChecker())
// const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
interface GuiMenuItem {
label: string
handle: (arg0: PicGoCore, arg1: GuiApi) => Promise<void>
}
// get uploader or transformer config
const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => {
let config: any[] = []
if (name === '') {
return config
} else {
const handler = ctx.helper[type].get(name)
if (handler) {
if (handler.config) {
config = handler.config(ctx)
}
}
return config
}
}
const handleConfigWithFunction = (config: any[]) => {
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()
}
}
return config
}
const getPluginList = (): IPicGoPlugin[] => {
const pluginList = picgo.pluginLoader.getFullList()
const list = []
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: Omit<IGuiMenuItem, 'handle'>[] = []
if (plugin.guiMenu) {
menu = plugin.guiMenu(picgo).map(item => ({
label: item.label
}))
}
let gui = false
if (pluginPKG.keywords && pluginPKG.keywords.length > 0) {
if (pluginPKG.keywords.includes('picgo-gui-plugin')) {
gui = true
}
}
const obj: IPicGoPlugin = {
name: handleStreamlinePluginName(pluginList[i]),
fullName: pluginList[i],
author: pluginPKG.author.name || pluginPKG.author,
description: pluginPKG.description,
logo: 'file://' + path.join(pluginPath, 'logo.png').split(path.sep).join('/'),
version: pluginPKG.version,
gui,
config: {
plugin: {
fullName: pluginList[i],
name: handleStreamlinePluginName(pluginList[i]),
config: plugin.config ? handleConfigWithFunction(plugin.config(picgo)) : []
},
uploader: {
name: uploaderName,
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.uploader, picgo))
},
transformer: {
name: transformerName,
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.transformer, picgo))
}
},
enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`),
homepage: pluginPKG.homepage ? pluginPKG.homepage : '',
guiMenu: menu,
ing: false
}
list.push(obj)
}
return list
}
const handleGetPluginList = () => {
ipcMain.on('getPluginList', (event: IpcMainEvent) => {
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)
}
})
}
const handlePluginInstall = () => {
ipcMain.on('installPlugin', async (event: IpcMainEvent, fullName: string) => {
const dispose = handleNPMError()
const res = await picgo.pluginHandler.install([fullName])
event.sender.send('installPlugin', {
success: res.success,
body: fullName,
errMsg: res.success ? '' : res.body
})
if (res.success) {
shortKeyHandler.registerPluginShortKey(res.body[0])
} else {
showNotification({
title: T('PLUGIN_INSTALL_FAILED'),
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 = async (fullName: string | string[]) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const dispose = handleNPMError()
const res = await picgo.pluginHandler.update(typeof fullName === 'string' ? [fullName] : 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 handleUpdateAllPlugin = () => {
ipcMain.on('updateAllPlugin', async (event: IpcMainEvent, list: string[]) => {
handlePluginUpdate(list)
})
}
const handleNPMError = (): IDispose => {
const handler = (msg: string) => {
if (msg === 'NPM is not installed') {
dialog.showMessageBox({
title: T('TIPS_ERROR'),
message: T('TIPS_INSTALL_NODE_AND_RELOAD_PICGO'),
buttons: ['Yes']
}).then((res) => {
if (res.response === 0) {
shell.openExternal('https://nodejs.org/')
}
})
}
}
picgo.once('failed', handler)
return () => picgo.off('failed', handler)
}
const handleGetPicBedConfig = () => {
ipcMain.on('getPicBedConfig', (event: IpcMainEvent, type: string) => {
const name = picgo.helper.uploader.get(type)?.name || type
if (picgo.helper.uploader.get(type)?.config) {
const _config = picgo.helper.uploader.get(type)!.config!(picgo)
const config = handleConfigWithFunction(_config)
event.sender.send('getPicBedConfig', config, name)
} else {
event.sender.send('getPicBedConfig', [], name)
}
})
}
// TODO: remove it
const handlePluginActions = () => {
ipcMain.on('pluginActions', (event: IpcMainEvent, name: string, label: string) => {
const plugin = picgo.pluginLoader.getPlugin(name)
if (plugin?.guiMenu?.(picgo)?.length) {
const menu: GuiMenuItem[] = plugin.guiMenu(picgo)
menu.forEach(item => {
if (item.label === label) {
item.handle(picgo, GuiApi.getInstance())
}
})
}
})
}
const handleRemoveFiles = () => {
ipcMain.on('removeFiles', (event: IpcMainEvent, files: ImgInfo[]) => {
setTimeout(() => {
picgo.emit('remove', files, GuiApi.getInstance())
}, 500)
})
}
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', async (event: IpcMainEvent) => {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
const res = await dialog.showOpenDialog(settingWindow, {
properties: ['openDirectory']
})
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: 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')
})
}
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>(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const customLink = picgo.getConfig<string>(configPaths.settings.customLink)
const txt = await 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)
if (!fs.existsSync(abFilePath)) {
fs.writeFileSync(abFilePath, '')
}
shell.openPath(abFilePath)
})
}
const handleOpenDirectory = () => {
ipcMain.on(PICGO_OPEN_DIRECTORY, (event: IpcMainEvent, dirPath?: string, inStorePath: boolean = true) => {
if (inStorePath) {
dirPath = path.join(STORE_PATH, dirPath || '')
}
if (!dirPath || !fs.existsSync(dirPath)) {
return
}
shell.openPath(dirPath)
})
}
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()
handleGetPicBedConfig()
handlePluginActions()
handleRemoveFiles()
handlePicGoSaveConfig()
handlePicGoGetConfig()
handlePicGoGalleryDB()
handleImportLocalPlugin()
handleUpdateAllPlugin()
handleOpenFile()
handleOpenDirectory()
handleOpenWindow()
handleI18n()
handleRPCActions()
},
// TODO: separate to single file
handlePluginUninstall,
handlePluginUpdate
}

View File

@ -1,36 +1,42 @@
// External dependencies import {
import pkg from 'root/package.json' app,
dialog,
BrowserWindow,
Menu,
shell,
MenuItemConstructorOptions,
MenuItem
} from 'electron'
import { PicGo as PicGoCore } from 'piclist'
// Electron modules import db from '@core/datastore'
import { Menu, BrowserWindow, app, dialog, shell } from 'electron'
// Custom utilities and modules
import windowManager from 'apis/app/window/windowManager'
import getPicBeds from '~/main/utils/getPicBeds'
import picgo from '@core/picgo' import picgo from '@core/picgo'
import GuiApi from 'apis/gui'
import picgoCoreIPC from '~/main/events/picgoCoreIPC'
import { changeCurrentUploader } from '~/main/utils/handleUploaderConfig'
import db from '~/main/apis/core/datastore'
import clipboardPoll from '~/main/utils/clipboardPoll'
// Custom types/enums
import { IWindowList } from '#/types/enum'
// External utility functions
import { import {
uploadClipboardFiles uploadClipboardFiles
} from '~/main/apis/app/uploader/apis' } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import GuiApi from 'apis/gui'
import { handlePluginUninstall, handlePluginUpdate } from '~/events/rpc/routes/plugin/utils'
import { T } from '~/i18n'
import clipboardPoll from '~/utils/clipboardPoll'
import { setTrayToolTip } from '~/utils/common'
import getPicBeds from '~/utils/getPicBeds'
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
import { import {
PICGO_CONFIG_PLUGIN, PICGO_CONFIG_PLUGIN,
PICGO_HANDLE_PLUGIN_DONE, PICGO_HANDLE_PLUGIN_DONE,
PICGO_HANDLE_PLUGIN_ING, PICGO_HANDLE_PLUGIN_ING,
PICGO_TOGGLE_PLUGIN, PICGO_TOGGLE_PLUGIN,
SHOW_MAIN_PAGE_QRCODE SHOW_MAIN_PAGE_QRCODE
} from '~/universal/events/constants' } from '#/events/constants'
import { PicGo as PicGoCore } from 'piclist' import { IWindowList } from '#/types/enum'
import { T } from '~/main/i18n' import { configPaths } from '#/utils/configPaths'
import { configPaths } from '~/universal/utils/configPaths'
import pkg from 'root/package.json'
import { openMainWindow } from '~/utils/windowHelper'
interface GuiMenuItem { interface GuiMenuItem {
label: string label: string
@ -41,18 +47,10 @@ const buildMiniPageMenu = () => {
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const ClipboardWatcher = clipboardPoll const ClipboardWatcher = clipboardPoll
const submenu = buildPicBedListMenu() const submenu = buildPicBedListMenu()
const template = [ const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
{ {
label: T('OPEN_MAIN_WINDOW'), label: T('OPEN_MAIN_WINDOW'),
click () { click: openMainWindow
windowManager.get(IWindowList.SETTING_WINDOW)!.show()
const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false
if (autoCloseMiniWindow) {
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
}
}, },
{ {
label: T('CHOOSE_DEFAULT_PICBED'), label: T('CHOOSE_DEFAULT_PICBED'),
@ -66,7 +64,7 @@ const buildMiniPageMenu = () => {
} }
}, },
{ {
label: T('HIDE_WINDOW'), label: T('HIDE_MINI_WINDOW'),
click () { click () {
BrowserWindow.getFocusedWindow()!.hide() BrowserWindow.getFocusedWindow()!.hide()
} }
@ -82,7 +80,7 @@ const buildMiniPageMenu = () => {
}) })
buildMiniPageMenu() buildMiniPageMenu()
}, },
enabled: !isListeningClipboard visible: !isListeningClipboard
}, },
{ {
label: T('STOP_WATCH_CLIPBOARD'), label: T('STOP_WATCH_CLIPBOARD'),
@ -92,7 +90,7 @@ const buildMiniPageMenu = () => {
ClipboardWatcher.removeAllListeners() ClipboardWatcher.removeAllListeners()
buildMiniPageMenu() buildMiniPageMenu()
}, },
enabled: isListeningClipboard visible: isListeningClipboard
}, },
{ {
label: T('RELOAD_APP'), label: T('RELOAD_APP'),
@ -106,7 +104,6 @@ const buildMiniPageMenu = () => {
label: T('QUIT') label: T('QUIT')
} }
] ]
// @ts-ignore
return Menu.buildFromTemplate(template) return Menu.buildFromTemplate(template)
} }
@ -185,6 +182,7 @@ const buildPicBedListMenu = () => {
if (windowManager.has(IWindowList.SETTING_WINDOW)) { if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed') windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
} }
setTrayToolTip(`${item.type} ${config._configName || 'Default'}`)
} }
} }
}) })
@ -198,6 +196,7 @@ const buildPicBedListMenu = () => {
if (windowManager.has(IWindowList.SETTING_WINDOW)) { if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed') windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
} }
setTrayToolTip(item.type)
} }
: undefined : undefined
} }
@ -264,14 +263,14 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
click () { click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName) window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
picgoCoreIPC.handlePluginUninstall(plugin.fullName) handlePluginUninstall(plugin.fullName)
} }
}, { }, {
label: T('UPDATE_PLUGIN'), label: T('UPDATE_PLUGIN'),
click () { click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName) window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
picgoCoreIPC.handlePluginUpdate(plugin.fullName) handlePluginUpdate(plugin.fullName)
} }
}] }]
for (const i in plugin.config) { for (const i in plugin.config) {
@ -325,7 +324,6 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
menu.push({ menu.push({
label: i.label, label: i.label,
click () { click () {
// ipcRenderer.send('pluginActions', plugin.fullName, i.label)
const picgPlugin = picgo.pluginLoader.getPlugin(plugin.fullName) const picgPlugin = picgo.pluginLoader.getPlugin(plugin.fullName)
if (picgPlugin?.guiMenu?.(picgo)?.length) { if (picgPlugin?.guiMenu?.(picgo)?.length) {
const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo) const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo)

View File

@ -1,52 +1,62 @@
// External dependencies import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from 'electron'
import { ipcMain, IpcMainEvent } from 'electron'
// Electron modules import logger from '@core/picgo/logger'
// Custom utilities and modules import { galleryRouter } from '~/events/rpc/routes/gallery'
import { configRouter } from './routes/config' import { picbedRouter } from '~/events/rpc/routes/picbed'
import { toolboxRouter } from './routes/toolbox' import { pluginRouter } from '~/events/rpc/routes/plugin'
import { systemRouter } from './routes/system' import { settingRouter } from '~/events/rpc/routes/setting'
import { systemRouter } from '~/events/rpc/routes/system'
import { toolboxRouter } from '~/events/rpc/routes/toolbox'
import { trayRouter } from '~/events/rpc/routes/tray'
import { uploadRouter } from '~/events/rpc/routes/upload'
// Custom types/enums import { IRPCActionType, IRPCType } from '#/types/enum'
import { IRPCActionType } from '~/universal/types/enum' import { RPC_ACTIONS, RPC_ACTIONS_INVOKE } from '#/events/constants'
// External utility functions const isDevelopment = process.env.NODE_ENV !== 'production'
import { RPC_ACTIONS } from '#/events/constants'
class RPCServer implements IRPCServer { class RPCServer implements IRPCServer {
private routes: IRPCRoutes = new Map() private routes: IRPCRoutes = new Map()
private routesWithResponse: IRPCRoutes = new Map()
private rpcEventHandler = async (event: IpcMainEvent, action: IRPCActionType, args: any[], callbackId: string) => { private rpcEventHandler = async (event: IpcMainEvent, action: IRPCActionType, args: any[]) => {
try { try {
const handler = this.routes.get(action) if (isDevelopment) {
if (!handler) { console.log(`action: ${action} args: ${JSON.stringify(args)}`)
return this.sendBack(event, action, null, callbackId)
} }
const res = await handler?.(args, event) const route = this.routes.get(action)
this.sendBack(event, action, res, callbackId) await route?.handler?.(event, args)
} catch (e) { } catch (e: any) {
this.sendBack(event, action, null, callbackId) logger.error(e)
} }
} }
/** private rpcEventHandlerWithResponse = async (event: IpcMainInvokeEvent, action: IRPCActionType, args: any[]) => {
* if sendback data is null, then it means that the action is not supported or error occurs try {
* if there is no callbackId, then do not send back if (isDevelopment) {
*/ console.log(`action: ${action} args: ${JSON.stringify(args)}`)
private sendBack (event: IpcMainEvent, action: IRPCActionType, data: any, callbackId: string) { }
if (callbackId) { const route = this.routesWithResponse.get(action)
event.sender.send(RPC_ACTIONS, data, action, callbackId) return await route?.handler?.(event, args)
} catch (e: any) {
logger.error(e)
return undefined
} }
} }
start () { start () {
ipcMain.on(RPC_ACTIONS, this.rpcEventHandler) ipcMain.on(RPC_ACTIONS, this.rpcEventHandler)
ipcMain.handle(RPC_ACTIONS_INVOKE, this.rpcEventHandlerWithResponse)
} }
use (routes: IRPCRoutes) { use (routes: IRPCRoutes) {
for (const [action, handler] of routes) { for (const [action, route] of routes) {
this.routes.set(action, handler) if (route.type === IRPCType.SEND) {
this.routes.set(action, route)
} else {
this.routesWithResponse.set(action, route)
}
} }
} }
@ -57,9 +67,20 @@ class RPCServer implements IRPCServer {
const rpcServer = new RPCServer() const rpcServer = new RPCServer()
rpcServer.use(configRouter.routes()) const routes = [
rpcServer.use(toolboxRouter.routes()) galleryRouter.routes(),
rpcServer.use(systemRouter.routes()) picbedRouter.routes(),
pluginRouter.routes(),
settingRouter.routes(),
systemRouter.routes(),
toolboxRouter.routes(),
trayRouter.routes(),
uploadRouter.routes()
]
for (const route of routes) {
rpcServer.use(route)
}
export { export {
rpcServer rpcServer

View File

@ -1,9 +1,22 @@
import { IRPCActionType } from '~/universal/types/enum' import { IRPCType, IRPCActionType } from '#/types/enum'
interface IBatchAddParams {
action: IRPCActionType
handler: IRPCHandler<any>
type?: IRPCType
}
export class RPCRouter implements IRPCRouter { export class RPCRouter implements IRPCRouter {
private routeMap: IRPCRoutes = new Map() private routeMap: IRPCRoutes = new Map()
add = <T>(action: IRPCActionType, handler: IRPCHandler<T>) => { add = <T>(action: IRPCActionType, handler: IRPCHandler<T>, type: IRPCType = IRPCType.SEND): this => {
this.routeMap.set(action, handler) this.routeMap.set(action, { handler, type })
return this
}
addBatch = (params: IBatchAddParams[]): this => {
for (const { action, handler, type = IRPCType.SEND } of params) {
this.routeMap.set(action, { handler, type })
}
return this return this
} }

View File

@ -1,42 +0,0 @@
import { IRPCActionType } from '~/universal/types/enum'
import { RPCRouter } from '../router'
import {
deleteUploaderConfig,
getUploaderConfigList,
selectUploaderConfig,
updateUploaderConfig,
resetUploaderConfig
} 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
})
.add(IRPCActionType.RESET_UPLOADER_CONFIG, async (args) => {
const [type, id] = args as IUpdateUploaderConfigArgs
resetUploaderConfig(type, id)
return true
})
export {
configRouter
}

View File

@ -0,0 +1,133 @@
import { clipboard } from 'electron'
import { GalleryDB } from '@core/datastore'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import { IFilter, IObject } from '@picgo/store/dist/types'
import GuiApi from 'apis/gui'
import { RPCRouter } from '~/events/rpc/router'
import { removeFileFromDogeInMain, removeFileFromHuaweiInMain, removeFileFromS3InMain, removeFileFromSFTPInMain } from '~/utils/deleteFunc'
import pasteTemplate from '~/utils/pasteTemplate'
import { ICOREBuildInEvent, IPasteStyle, IRPCActionType, IRPCType } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
const galleryRouter = new RPCRouter()
const galleryRoutes = [
{
action: IRPCActionType.GALLERY_PASTE_TEXT,
handler: async (_: IIPCEvent, args: [ item: ImgInfo, copy?: boolean]) => {
const [item, copy = true] = args
const pasteStyle = picgo.getConfig<IPasteStyle>(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const customLink = picgo.getConfig<string>(configPaths.settings.customLink)
const txt = await pasteTemplate(pasteStyle, item, customLink)
if (copy) {
clipboard.writeText(txt)
}
return txt
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.GALLERY_REMOVE_FILES,
handler: async (_: IIPCEvent, args: [files: ImgInfo[]]) => {
setTimeout(() => {
picgo.emit(ICOREBuildInEvent.REMOVE, args[0], GuiApi.getInstance())
}, 500)
}
},
{
action: IRPCActionType.GALLERY_GET_DB,
handler: async (_: IIPCEvent, args: [filter: IFilter]) => {
const dbStore = GalleryDB.getInstance()
return await dbStore.get(args[0])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.GALLERY_GET_BY_ID_DB,
handler: async (_: IIPCEvent, args: [id: string]) => {
const dbStore = GalleryDB.getInstance()
return await dbStore.getById(args[0])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.GALLERY_UPDATE_BY_ID_DB,
handler: async (_: IIPCEvent, args: [id: string, value: IObject]) => {
const dbStore = GalleryDB.getInstance()
return await dbStore.updateById(args[0], args[1])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.GALLERY_REMOVE_BY_ID_DB,
handler: async (_: IIPCEvent, args: [id: string]) => {
const dbStore = GalleryDB.getInstance()
return await dbStore.removeById(args[0])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.GALLERY_INSERT_DB,
handler: async (_: IIPCEvent, args: [value: IObject]) => {
const dbStore = GalleryDB.getInstance()
return await dbStore.insert(args[0])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.GALLERY_INSERT_DB_BATCH,
handler: async (_: IIPCEvent, args: [value: IObject[]]) => {
const dbStore = GalleryDB.getInstance()
return await dbStore.insertMany(args[0])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.GALLERY_LOG_DELETE_MSG,
handler: async (_: IIPCEvent, args: [msg: string, logLevel: ILogType]) => {
const [msg, logLevel] = args
console.log(msg, logLevel)
logger[logLevel](msg)
}
},
{
action: IRPCActionType.GALLERY_DELETE_SFTP_FILE,
handler: async (_: IIPCEvent, args: [config: ISftpPlistConfig, fileName: string]) => {
const [config, fileName] = args
return await removeFileFromSFTPInMain(config, fileName)
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.GALLERY_DELETE_AWS_S3_FILE,
handler: async (_: IIPCEvent, args: [configMap: IStringKeyMap]) => {
return await removeFileFromS3InMain(args[0])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.GALLERY_DELETE_DOGE_FILE,
handler: async (_: IIPCEvent, args: [configMap: IStringKeyMap]) => {
return await removeFileFromDogeInMain(args[0])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.GALLERY_DELETE_HUAWEI_OSS_FILE,
handler: async (_: IIPCEvent, args: [configMap: IStringKeyMap]) => {
return await removeFileFromHuaweiInMain(args[0])
},
type: IRPCType.INVOKE
}
]
galleryRouter.addBatch(galleryRoutes)
export {
galleryRouter
}

View File

@ -0,0 +1,99 @@
import picgo from '@core/picgo'
import { RPCRouter } from '~/events/rpc/router'
import {
deleteUploaderConfig,
getUploaderConfigList,
resetUploaderConfig,
selectUploaderConfig,
updateUploaderConfig
} from '~/utils/handleUploaderConfig'
import { IRPCActionType, IRPCType } from '#/types/enum'
const picbedRouter = new RPCRouter()
const handleConfigWithFunction = (config: any[]) => {
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()
}
}
return config
}
const picbedRoutes = [
{
action: IRPCActionType.PICBED_GET_CONFIG_LIST,
handler: async (_: IIPCEvent, args: [type: string]) => {
const config = getUploaderConfigList(args[0])
return config
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.PICBED_DELETE_CONFIG,
handler: async (_: IIPCEvent, args: [type: string, id: string]) => {
const [type, id] = args
const config = deleteUploaderConfig(type, id)
return config
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.UPLOADER_SELECT,
handler: async (_: IIPCEvent, args: [type: string, id: string]) => {
const [type, id] = args
selectUploaderConfig(type, id)
return true
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.UPLOADER_UPDATE_CONFIG,
handler: async (_: IIPCEvent, args: [type: string, id: string, config: IStringKeyMap]) => {
const [type, id, config] = args
updateUploaderConfig(type, id, config)
return true
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.UPLOADER_RESET_CONFIG,
handler: async (_: IIPCEvent, args: [type: string, id: string]) => {
const [type, id] = args
resetUploaderConfig(type, id)
return true
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.PICBED_GET_PICBED_CONFIG,
handler: async (_: IIPCEvent, args: [type: string]) => {
const type = args[0]
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
}
}
},
type: IRPCType.INVOKE
}
]
picbedRouter.addBatch(picbedRoutes)
export {
picbedRouter
}

View File

@ -0,0 +1,37 @@
import { RPCRouter } from '~/events/rpc/router'
import {
pluginImportLocalFunc,
pluginInstallFunc,
pluginGetListFunc,
pluginUpdateAllFunc
} from '~/events/rpc/routes/plugin/utils'
import { IRPCActionType } from '#/types/enum'
const pluginRouter = new RPCRouter()
const pluginRoutes = [
{
action: IRPCActionType.PLUGIN_GET_LIST,
handler: pluginGetListFunc
},
{
action: IRPCActionType.PLUGIN_INSTALL,
handler: pluginInstallFunc
},
{
action: IRPCActionType.PLUGIN_IMPORT_LOCAL,
handler: pluginImportLocalFunc
},
{
action: IRPCActionType.PLUGIN_UPDATE_ALL,
handler: pluginUpdateAllFunc
}
]
pluginRouter.addBatch(pluginRoutes)
export {
pluginRouter
}

View File

@ -0,0 +1,227 @@
import { dialog, shell } from 'electron'
import { IGuiMenuItem, PicGo as PicGoCore } from 'piclist'
import path from 'path'
import { dbPathDir } from '@core/datastore/dbChecker'
import picgo from '@core/picgo'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import windowManager from 'apis/app/window/windowManager'
import { T } from '~/i18n'
import { showNotification } from '~/utils/common'
import { handleStreamlinePluginName, simpleClone } from '#/utils/common'
import { ICOREBuildInEvent, IPicGoHelperType, IWindowList } from '#/types/enum'
const STORE_PATH = dbPathDir()
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
// get uploader or transformer config
const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => {
let config: any[] = []
if (name === '') {
return config
} else {
const handler = ctx.helper[type].get(name)
if (handler) {
if (handler.config) {
config = handler.config(ctx)
}
}
return config
}
}
const handleConfigWithFunction = (config: any[]) => {
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()
}
}
return config
}
const getPluginList = (): IPicGoPlugin[] => {
const pluginList = picgo.pluginLoader.getFullList()
const list = []
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: Omit<IGuiMenuItem, 'handle'>[] = []
if (plugin.guiMenu) {
menu = plugin.guiMenu(picgo).map(item => ({
label: item.label
}))
}
let gui = false
if (pluginPKG.keywords && pluginPKG.keywords.length > 0) {
if (pluginPKG.keywords.includes('picgo-gui-plugin')) {
gui = true
}
}
const obj: IPicGoPlugin = {
name: handleStreamlinePluginName(pluginList[i]),
fullName: pluginList[i],
author: pluginPKG.author.name || pluginPKG.author,
description: pluginPKG.description,
logo: 'file://' + path.join(pluginPath, 'logo.png').split(path.sep).join('/'),
version: pluginPKG.version,
gui,
config: {
plugin: {
fullName: pluginList[i],
name: handleStreamlinePluginName(pluginList[i]),
config: plugin.config ? handleConfigWithFunction(plugin.config(picgo)) : []
},
uploader: {
name: uploaderName,
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.uploader, picgo))
},
transformer: {
name: transformerName,
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.transformer, picgo))
}
},
enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`),
homepage: pluginPKG.homepage ? pluginPKG.homepage : '',
guiMenu: menu,
ing: false
}
list.push(obj)
}
return list
}
const handleNPMError = (): IDispose => {
const handler = (msg: string) => {
if (msg === 'NPM is not installed') {
dialog.showMessageBox({
title: T('TIPS_ERROR'),
message: T('TIPS_INSTALL_NODE_AND_RELOAD_PICGO'),
buttons: ['Yes']
}).then((res) => {
if (res.response === 0) {
shell.openExternal('https://nodejs.org/')
}
})
}
}
picgo.once(ICOREBuildInEvent.FAILED, handler)
return () => picgo.off(ICOREBuildInEvent.FAILED, handler)
}
export const handlePluginUpdate = async (fullName: string | string[]) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const dispose = handleNPMError()
const res = await picgo.pluginHandler.update(typeof fullName === 'string' ? [fullName] : 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()
}
export 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()
}
export const pluginGetListFunc = async (event: IIPCEvent) => {
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)
}
}
export const pluginInstallFunc = async (event: IIPCEvent, args: [fullName: string]) => {
const fullName = args[0]
const dispose = handleNPMError()
const res = await picgo.pluginHandler.install([fullName])
event.sender.send('installPlugin', {
success: res.success,
body: fullName,
errMsg: res.success ? '' : res.body
})
if (res.success) {
shortKeyHandler.registerPluginShortKey(res.body[0])
} else {
showNotification({
title: T('PLUGIN_INSTALL_FAILED'),
body: res.body as string
})
}
event.sender.send('hideLoading')
dispose()
}
export const pluginImportLocalFunc = async (event: IIPCEvent) => {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
const res = await dialog.showOpenDialog(settingWindow, {
properties: ['openDirectory']
})
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: 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')
}
export const pluginUpdateAllFunc = async (_: IIPCEvent, args: [list: string[]]) => {
handlePluginUpdate(args[0])
}

View File

@ -0,0 +1,25 @@
import { IRPCActionType } from '#/types/enum'
import server from '~/server'
import webServer from '~/server/webServer'
export default [
{
action: IRPCActionType.ADVANCED_UPDATE_SERVER,
handler: async () => {
server.restart()
}
},
{
action: IRPCActionType.ADVANCED_STOP_WEB_SERVER,
handler: async () => {
webServer.stop()
}
},
{
action: IRPCActionType.ADVANCED_RESTART_WEB_SERVER,
handler: async () => {
webServer.restart()
}
}
]

View File

@ -0,0 +1,82 @@
import { app } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import logger from '@core/picgo/logger'
import { downloadFile, uploadFile } from '~/utils/syncSettings'
import { IRPCActionType, IRPCType } from '#/types/enum'
const STORE_PATH = app.getPath('userData')
const commonConfigList = ['data.json', 'data.bak.json']
const manageConfigList = ['manage.json', 'manage.bak.json']
export default [
{
action: IRPCActionType.CONFIGURE_MIGRATE_FROM_PICGO,
handler: async () => {
const picGoConfigPath = STORE_PATH.replace('piclist', 'picgo')
const files = [
'data.json',
'data.bak.json',
'picgo.db',
'picgo.bak.db'
]
try {
await Promise.all(files.map(async file => {
const sourcePath = path.join(picGoConfigPath, file)
const targetPath = path.join(STORE_PATH, file.replace('picgo', 'piclist'))
await fs.copy(sourcePath, targetPath, { overwrite: true })
}
))
return true
} catch (err: any) {
logger.error(err)
throw new Error('Migrate failed')
}
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.CONFIGURE_UPLOAD_COMMON_CONFIG,
handler: async () => {
return await uploadFile(commonConfigList)
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.CONFIGURE_UPLOAD_MANAGE_CONFIG,
handler: async () => {
return await uploadFile(manageConfigList)
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.CONFIGURE_UPLOAD_ALL_CONFIG,
handler: async () => {
return await uploadFile([...commonConfigList, ...manageConfigList])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.CONFIGURE_DOWNLOAD_COMMON_CONFIG,
handler: async () => {
return await downloadFile(commonConfigList)
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.CONFIGURE_DOWNLOAD_MANAGE_CONFIG,
handler: async () => {
return await downloadFile(manageConfigList)
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.CONFIGURE_DOWNLOAD_ALL_CONFIG,
handler: async () => {
return await downloadFile([...commonConfigList, ...manageConfigList])
},
type: IRPCType.INVOKE
}
]

View File

@ -0,0 +1,21 @@
import { RPCRouter } from '~/events/rpc/router'
import advancedRoutes from '~/events/rpc/routes/setting/advanced'
import configureRoutes from '~/events/rpc/routes/setting/configure'
import mainAppRoutes from '~/events/rpc/routes/setting/mainApp'
import shortKeyRoutes from '~/events/rpc/routes/setting/shortKey'
const settingRouter = new RPCRouter()
const settingRoutes = [
...advancedRoutes,
...configureRoutes,
...mainAppRoutes,
...shortKeyRoutes
]
settingRouter.addBatch(settingRoutes)
export {
settingRouter
}

View File

@ -0,0 +1,65 @@
import { app, IpcMainEvent, shell } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import picgo from '@core/picgo'
import { dbPathDir } from '@core/datastore/dbChecker'
import { IRPCActionType, IRPCType } from '#/types/enum'
const STORE_PATH = dbPathDir()
export default [
{
action: IRPCActionType.PICLIST_GET_CONFIG,
handler: async (_: IIPCEvent, args: [key?: string]) => {
return picgo.getConfig(args[0])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.PICLIST_GET_CONFIG_SYNC,
handler: async (event: IIPCEvent, args: [key?: string]) => {
const result = picgo.getConfig(args[0])
const eventInstance = event as IpcMainEvent
eventInstance.returnValue = result
}
},
{
action: IRPCActionType.PICLIST_SAVE_CONFIG,
handler: async (_: IIPCEvent, args: [data: IObj]) => {
picgo.saveConfig(args[0])
}
},
{
action: IRPCActionType.PICLIST_OPEN_FILE,
handler: async (_: IIPCEvent, args: [fileName: string]) => {
const abFilePath = path.join(STORE_PATH, args[0])
if (!fs.existsSync(abFilePath)) {
fs.writeFileSync(abFilePath, '')
}
shell.openPath(abFilePath)
}
},
{
action: IRPCActionType.PICLIST_OPEN_DIRECTORY,
handler: async (_: IIPCEvent, args: [dirPath?: string, inStorePath?: boolean]) => {
let [dirPath, inStorePath = true] = args
if (inStorePath) {
dirPath = path.join(STORE_PATH, dirPath || '')
}
if (!dirPath || !fs.existsSync(dirPath)) {
return
}
shell.openPath(dirPath)
}
},
{
action: IRPCActionType.PICLIST_AUTO_START,
handler: async (_: IIPCEvent, args: [val: boolean]) => {
app.setLoginItemSettings({
openAtLogin: args[0]
})
}
}
]

View File

@ -0,0 +1,46 @@
import {
Notification
} from 'electron'
import bus from '@core/bus'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import { T } from '~/i18n'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
const notificationFunc = (result: boolean) => {
const notification = new Notification({
title: T(`OPERATION_${result ? 'SUCCEED' : 'FAILED'}`),
body: T(`TIPS_SHORTCUT_MODIFIED_${result ? 'SUCCEED' : 'CONFLICT'}`)
})
notification.show()
}
export default [
{
action: IRPCActionType.SHORTKEY_UPDATE,
handler: async (_: IIPCEvent, args: [item: IShortKeyConfig, oldKey: string, from: string]) => {
const [item, oldKey, from] = args
const result = shortKeyHandler.updateShortKey(item, oldKey, from)
notificationFunc(result)
return result
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.SHORTKEY_BIND_OR_UNBIND,
handler: async (_: IIPCEvent, args: [item: IShortKeyConfig, from: string]) => {
const [item, from] = args
const result = shortKeyHandler.bindOrUnbindShortKey(item, from)
notificationFunc(result)
}
},
{
action: IRPCActionType.SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE,
handler: async (_: IIPCEvent, args: [status: boolean]) => {
const [status] = args
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, status)
}
}
]

View File

@ -1,28 +0,0 @@
// External dependencies
import { app, clipboard, shell } from 'electron'
// Electron modules
// Custom utilities and modules
import { IRPCActionType } from '~/universal/types/enum'
import { RPCRouter } from '../router'
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)
})
export {
systemRouter
}

View File

@ -0,0 +1,62 @@
import { app, shell } from 'electron'
import picgo from '@core/picgo'
import windowManager from 'apis/app/window/windowManager'
import { i18nManager } from '~/i18n'
import { IRPCActionType, IWindowList } from '#/types/enum'
import { SET_CURRENT_LANGUAGE } from '#/events/constants'
export default [
{
action: IRPCActionType.RELOAD_APP,
handler: async () => {
app.relaunch()
app.exit(0)
}
},
{
action: IRPCActionType.OPEN_FILE,
handler: async (_: IIPCEvent, args: [filePath: string]) => {
shell.openPath(args[0])
}
},
{
action: IRPCActionType.OPEN_URL,
handler: async (_: IIPCEvent, args: [url: string]) => {
shell.openExternal(args[0])
}
},
{
action: IRPCActionType.GET_LANGUAGE_LIST,
handler: async (event: IIPCEvent) => {
event.returnValue = i18nManager.languageList
}
},
{
action: IRPCActionType.GET_CURRENT_LANGUAGE,
handler: async (event: IIPCEvent) => {
const { lang, locales } = i18nManager.getCurrentLocales()
event.returnValue = [lang, locales]
}
},
{
action: IRPCActionType.SET_CURRENT_LANGUAGE,
handler: async (_: IIPCEvent, args: [language: string]) => {
i18nManager.setCurrentLanguage(args[0])
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)
}
}
}
]

View File

@ -0,0 +1,16 @@
import { RPCRouter } from '~/events/rpc/router'
import appRoutes from '~/events/rpc/routes/system/app'
import windowRoutes from '~/events/rpc/routes/system/window'
const systemRouter = new RPCRouter()
const systemRoutes = [
...appRoutes,
...windowRoutes
]
systemRouter.addBatch(systemRoutes)
export {
systemRouter
}

View File

@ -0,0 +1,137 @@
import { app, BrowserWindow } from 'electron'
import windowManager from 'apis/app/window/windowManager'
import {
buildMainPageMenu,
buildMiniPageMenu,
buildPicBedListMenu,
buildPluginPageMenu
} from '~/events/remotes/menu'
import { openMiniWindow } from '~/utils/windowHelper'
import { IRPCActionType, IWindowList } from '#/types/enum'
export default [
{
action: IRPCActionType.HIDE_DOCK,
handler: async (_: IIPCEvent, args: [value: boolean]) => {
args[0] ? app.dock.hide() : app.dock.show()
}
},
{
action: IRPCActionType.OPEN_WINDOW,
handler: async (_: IIPCEvent, args: [windowName: IWindowList]) => {
const window = windowManager.get(args[0])
if (window) {
window.show()
}
}
},
{
action: IRPCActionType.OPEN_MANUAL_WINDOW,
handler: async () => {
windowManager.get(IWindowList.MANUAL_WINDOW)!.show()
}
},
{
action: IRPCActionType.OPEN_MINI_WINDOW,
handler: async () => {
openMiniWindow()
}
},
{
action: IRPCActionType.CLOSE_WINDOW,
handler: async () => {
const window = BrowserWindow.getFocusedWindow()
if (process.platform === 'linux') {
window?.hide()
} else {
window?.close()
}
}
},
{
action: IRPCActionType.MINIMIZE_WINDOW,
handler: async () => {
const window = BrowserWindow.getFocusedWindow()
window?.minimize()
}
},
{
action: IRPCActionType.SHOW_MINI_PAGE_MENU,
handler: async () => {
const window = windowManager.get(IWindowList.MINI_WINDOW)!
const menu = buildMiniPageMenu()
menu.popup({
window
})
}
},
{
action: IRPCActionType.SHOW_MAIN_PAGE_MENU,
handler: async () => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const menu = buildMainPageMenu(window)
menu.popup({
window
})
}
},
{
action: IRPCActionType.SHOW_UPLOAD_PAGE_MENU,
handler: async () => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const menu = buildPicBedListMenu()
menu.popup({
window
})
}
},
{
action: IRPCActionType.SHOW_PLUGIN_PAGE_MENU,
handler: async (_: IIPCEvent, args: [plugin: IPicGoPlugin]) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const menu = buildPluginPageMenu(args[0])
menu.popup({
window
})
}
},
{
action: IRPCActionType.SET_MINI_WINDOW_POS,
handler: async (_: IIPCEvent, args: [pos: IMiniWindowPos]) => {
const window = BrowserWindow.getFocusedWindow()
window?.setBounds(args[0])
}
},
{
action: IRPCActionType.MINI_WINDOW_ON_TOP,
handler: async (_: IIPCEvent, args: [isOnTop: boolean]) => {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
miniWindow.setAlwaysOnTop(args[0])
}
},
{
action: IRPCActionType.MAIN_WINDOW_ON_TOP,
handler: async () => {
const mainWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
const isAlwaysOnTop = mainWindow.isAlwaysOnTop()
mainWindow.setAlwaysOnTop(!isAlwaysOnTop)
}
},
{
action: IRPCActionType.UPDATE_MINI_WINDOW_ICON,
handler: async (_: IIPCEvent, args: [iconPath: string]) => {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
miniWindow.webContents.send('updateMiniIcon', args[0])
}
},
{
action: IRPCActionType.REFRESH_SETTING_WINDOW,
handler: async () => {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
settingWindow.webContents.reloadIgnoringCache()
}
}
]

View File

@ -1,19 +1,14 @@
// External dependencies
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path' import path from 'path'
// Electron modules import { dbPathChecker, defaultConfigPath } from '@core/datastore/dbChecker'
// Custom utilities and modules import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
import { dbPathChecker, defaultConfigPath } from '~/main/apis/core/datastore/dbChecker' import { T } from '~/i18n'
import { sendToolboxResWithType } from './utils'
// Custom types/enums import { IToolboxItemCheckStatus, IToolboxItemType } from '#/types/enum'
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
// External utility functions import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static'
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static'
import { T } from '~/main/i18n'
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD) const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD)

View File

@ -1,20 +1,14 @@
// External dependencies import { IpcMainEvent } from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path' import path from 'path'
// Electron modules import { dbPathChecker } from '@core/datastore/dbChecker'
import { IpcMainEvent } from 'electron' import { GalleryDB, DB_PATH } from '@core/datastore'
// Custom utilities and modules import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
import { dbPathChecker } from '~/main/apis/core/datastore/dbChecker' import { T } from '~/i18n'
import { GalleryDB, DB_PATH } from '~/main/apis/core/datastore'
import { sendToolboxResWithType } from './utils'
// Custom types/enums import { IToolboxItemCheckStatus, IToolboxItemType } from '#/types/enum'
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
// External utility functions
import { T } from '~/main/i18n'
export const checkFileMap: IToolboxCheckerMap< export const checkFileMap: IToolboxCheckerMap<
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN

View File

@ -1,24 +1,17 @@
// External dependencies
import fs from 'fs-extra'
import axios, { AxiosRequestConfig } from 'axios' import axios, { AxiosRequestConfig } from 'axios'
import fs from 'fs-extra'
import { IConfig } from 'piclist'
import tunnel from 'tunnel' import tunnel from 'tunnel'
// Electron modules import { dbPathChecker } from '@core/datastore/dbChecker'
// Custom utilities and modules import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
import { dbPathChecker } from '~/main/apis/core/datastore/dbChecker' import { T } from '~/i18n'
import { sendToolboxResWithType } from './utils'
// Custom types/enums import { IToolboxItemCheckStatus, IToolboxItemType } from '#/types/enum'
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
// External utility functions function getProxy (proxyStr: string): AxiosRequestConfig['proxy'] | null {
// Custom types/enums
import { IConfig } from 'piclist'
import { T } from '~/main/i18n'
const getProxy = (proxyStr: string): AxiosRequestConfig['proxy'] | false => {
if (proxyStr) { if (proxyStr) {
try { try {
const proxyOptions = new URL(proxyStr) const proxyOptions = new URL(proxyStr)
@ -27,10 +20,9 @@ const getProxy = (proxyStr: string): AxiosRequestConfig['proxy'] | false => {
port: parseInt(proxyOptions.port || '0', 10), port: parseInt(proxyOptions.port || '0', 10),
protocol: proxyOptions.protocol protocol: proxyOptions.protocol
} }
} catch (e) { } catch (e) {}
} }
} return null
return false
} }
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY) const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)

View File

@ -1,8 +1,10 @@
import { IRPCActionType, IToolboxItemType } from '~/universal/types/enum' import { checkClipboardUploadMap, fixClipboardUploadMap } from '~/events/rpc/routes/toolbox/checkClipboardUpload'
import { RPCRouter } from '../../router' import { checkFileMap, fixFileMap } from '~/events/rpc/routes/toolbox/checkFile'
import { checkFileMap, fixFileMap } from './checkFile' import { checkProxyMap } from '~/events/rpc/routes/toolbox/checkProxy'
import { checkClipboardUploadMap, fixClipboardUploadMap } from './checkClipboardUpload' import { RPCRouter } from '~/events/rpc/router'
import { checkProxyMap } from './checkProxy'
import { IRPCActionType, IRPCType, IToolboxItemType } from '#/types/enum'
import { IpcMainEvent } from 'electron'
const toolboxRouter = new RPCRouter() const toolboxRouter = new RPCRouter()
@ -18,30 +20,37 @@ const toolboxFixMap: Partial<IToolboxFixMap<IToolboxItemType>> = {
} }
toolboxRouter toolboxRouter
.add(IRPCActionType.TOOLBOX_CHECK, async (args, event) => { .add(
IRPCActionType.TOOLBOX_CHECK,
async (event, args) => {
const [type] = args as IToolboxCheckArgs const [type] = args as IToolboxCheckArgs
if (type) { if (type) {
const handler = toolboxCheckMap[type] const handler = toolboxCheckMap[type]
if (handler) { if (handler) {
handler(event) handler(event as IpcMainEvent)
} }
} else { } else {
// do check all // do check all
for (const key in toolboxCheckMap) { for (const key in toolboxCheckMap) {
const handler = toolboxCheckMap[key as IToolboxItemType] const handler = toolboxCheckMap[key as IToolboxItemType]
if (handler) { if (handler) {
handler(event) handler(event as IpcMainEvent)
} }
} }
} }
}) },
.add(IRPCActionType.TOOLBOX_CHECK_FIX, async (args, event) => { IRPCType.SEND
)
.add(
IRPCActionType.TOOLBOX_CHECK_FIX, async (event, args) => {
const [type] = args as IToolboxCheckArgs const [type] = args as IToolboxCheckArgs
const handler = toolboxFixMap[type] const handler = toolboxFixMap[type]
if (handler) { if (handler) {
return await handler(event) return await handler(event as IpcMainEvent)
} }
}) },
IRPCType.INVOKE
)
export { export {
toolboxRouter toolboxRouter

View File

@ -1,9 +1,11 @@
import { IpcMainEvent } from 'electron' import { IpcMainEvent } from 'electron'
import { IRPCActionType, IToolboxItemType } from '~/universal/types/enum' import { IRPCActionType, IToolboxItemType } from '#/types/enum'
export const sendToolboxResWithType = (type: IToolboxItemType) => (event: IpcMainEvent, res?: Omit<IToolboxCheckRes, 'type'>) => { export function sendToolboxResWithType (type: IToolboxItemType) {
return (event: IpcMainEvent, res?: Omit<IToolboxCheckRes, 'type'>) => {
return event.sender.send(IRPCActionType.TOOLBOX_CHECK_RES, { return event.sender.send(IRPCActionType.TOOLBOX_CHECK_RES, {
...res, ...res,
type type
}) })
} }
}

View File

@ -0,0 +1,71 @@
import {
Notification
} from 'electron'
import { RPCRouter } from '~/events/rpc/router'
import { generateShortUrl, setTrayToolTip, handleCopyUrl } from '~/utils/common'
import { IRPCActionType, IRPCType, IPasteStyle, IWindowList } from '#/types/enum'
import db, { GalleryDB } from '@core/datastore'
import uploader from 'apis/app/uploader'
import windowManager from 'apis/app/window/windowManager'
import { T } from '~/i18n'
import pasteTemplate from '~/utils/pasteTemplate'
import { configPaths } from '#/utils/configPaths'
const trayRouter = new RPCRouter()
const trayRoutes = [
{
action: IRPCActionType.TRAY_SET_TOOL_TIP,
handler: async (_: IIPCEvent, args: [text: string]) => {
setTrayToolTip(args[0])
}
},
{
action: IRPCActionType.TRAY_GET_SHORT_URL,
handler: async (_: IIPCEvent, args: [url: string]) => {
return await generateShortUrl(args[0])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.TRAY_UPLOAD_CLIPBOARD_FILES,
handler: async () => {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
// macOS use builtin clipboard is OK
const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard()
if (img !== false) {
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
handleCopyUrl(await (pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink))))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),
body: img[0].imgUrl!
// icon: file[0]
// icon: img[0].imgUrl
})
notification.show()
}
await GalleryDB.getInstance().insert(img[0])
trayWindow.webContents.send('clipboardFiles', [])
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
}
}
trayWindow.webContents.send('uploadFiles')
}
}
]
trayRouter.addBatch(trayRoutes)
export {
trayRouter
}

View File

@ -0,0 +1,35 @@
import { RPCRouter } from '~/events/rpc/router'
import getPicBeds from '~/utils/getPicBeds'
import { uploadChoosedFiles, uploadClipboardFiles } from '~/apis/app/uploader/apis'
import { IRPCActionType, IRPCType } from '#/types/enum'
const uploadRouter = new RPCRouter()
const uploadRoutes = [
{
action: IRPCActionType.MAIN_GET_PICBED,
handler: async () => {
return getPicBeds()
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE,
handler: async () => {
uploadClipboardFiles()
}
},
{
action: IRPCActionType.UPLOAD_CHOOSED_FILES,
handler: async (evt: IIPCEvent, args: [files: IFileWithPath[]]) => {
return uploadChoosedFiles(evt.sender, args[0])
}
}
]
uploadRouter.addBatch(uploadRoutes)
export {
uploadRouter
}

View File

@ -1,21 +1,19 @@
// External dependencies
import http from 'http' import http from 'http'
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path' import path from 'path'
// Electron modules
// Custom utilities and modules
import picgo from '@core/picgo' import picgo from '@core/picgo'
import logger from '../apis/core/picgo/logger' import logger from '@core/picgo/logger'
export const imgFilePath = path.join(picgo.baseDir, 'imgTemp') export const imgFilePath = path.join(picgo.baseDir, 'imgTemp')
fs.ensureDirSync(imgFilePath) fs.ensureDirSync(imgFilePath)
const serverPort = 36699 const serverPort = 36699
let server: http.Server
export function startFileServer () { export function startFileServer () {
const server = http.createServer((req, res) => { server = http.createServer((req, res) => {
const requestPath = req.url?.split('?')[0] const requestPath = req.url?.split('?')[0]
const filePath = path.join(imgFilePath, decodeURIComponent(requestPath as string)) const filePath = path.join(imgFilePath, decodeURIComponent(requestPath as string))
@ -35,3 +33,9 @@ export function startFileServer () {
logger.error(err) logger.error(err)
}) })
} }
export function stopFileServer () {
server.close(() => {
logger.info('File server is stopped')
})
}

View File

@ -1,16 +1,9 @@
// External dependencies
import yaml from 'js-yaml'
import { ObjectAdapter, I18n } from '@picgo/i18n'
import path from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import yaml from 'js-yaml'
import path from 'path'
// Electron modules import { ObjectAdapter, I18n } from '@picgo/i18n'
// Custom utilities and modules
// Custom types/enums
// External utility functions
import { builtinI18nList } from '#/i18n' import { builtinI18nList } from '#/i18n'
class I18nManager { class I18nManager {

View File

@ -1,6 +1,8 @@
import path from 'path' import path from 'path'
import { app } from 'electron' import { app } from 'electron'
import { getLogger } from 'apis/core/utils/localLogger'
import { getLogger } from '@core/utils/localLogger'
const STORE_PATH = app.getPath('userData') const STORE_PATH = app.getPath('userData')
const LOG_PATH = path.join(STORE_PATH, 'piclist-gui-local.log') const LOG_PATH = path.join(STORE_PATH, 'piclist-gui-local.log')

View File

@ -1,4 +1,5 @@
import './errorHandler' import axios from 'axios'
import fs from 'fs-extra'
import { import {
app, app,
globalShortcut, globalShortcut,
@ -8,46 +9,50 @@ import {
screen, screen,
shell shell
} from 'electron' } from 'electron'
import { UpdateInfo, autoUpdater } from 'electron-updater'
import path from 'path'
import { import {
createProtocol createProtocol
} from 'vue-cli-plugin-electron-builder/lib' } from 'vue-cli-plugin-electron-builder/lib'
import beforeOpen from '~/main/utils/beforeOpen'
import ipcList from '~/main/events/ipcList' import bus from '@core/bus'
import busEventList from '~/main/events/busEventList' import db from '@core/datastore'
import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '#/types/enum' import picgo from '@core/picgo'
import windowManager from 'apis/app/window/windowManager' import logger from '@core/picgo/logger'
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import {
createTray, setDockMenu
} from 'apis/app/system'
import { import {
uploadChoosedFiles, uploadChoosedFiles,
uploadClipboardFiles uploadClipboardFiles
} from 'apis/app/uploader/apis' } from 'apis/app/uploader/apis'
import { import windowManager from 'apis/app/window/windowManager'
createTray, setDockMenu
} from 'apis/app/system' import busEventList from '~/events/busEventList'
import server from '~/main/server/index' import { startFileServer, stopFileServer } from '~/fileServer'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' import { T } from '~/i18n'
import { getUploadFiles } from '~/main/utils/handleArgv' import '~/lifeCycle/errorHandler'
import db from '~/main/apis/core/datastore' import fixPath from '~/lifeCycle/fixPath'
import bus from '@core/bus' import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import logger from 'apis/core/picgo/logger' import { manageIpcList } from '~/manage/events/ipcList'
import picgo from 'apis/core/picgo' import getManageApi from '~/manage/Main'
import fixPath from './fixPath' import { clearTempFolder } from '~/manage/utils/common'
import { clearTempFolder } from '../manage/utils/common' import server from '~/server/index'
import { initI18n } from '~/main/utils/handleI18n' import webServer from '~/server/webServer'
import { remoteNoticeHandler } from 'apis/app/remoteNotice' import beforeOpen from '~/utils/beforeOpen'
import { manageIpcList } from '../manage/events/ipcList' import clipboardPoll from '~/utils/clipboardPoll'
import getManageApi from '../manage/Main' import { getUploadFiles } from '~/utils/handleArgv'
import UpDownTaskQueue from '../manage/datastore/upDownTaskQueue' import { initI18n } from '~/utils/handleI18n'
import { T } from '~/main/i18n' import updateChecker from '~/utils/updateChecker'
import { UpdateInfo, autoUpdater } from 'electron-updater'
import updateChecker from '../utils/updateChecker' import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '#/types/enum'
import clipboardPoll from '../utils/clipboardPoll' import { configPaths } from '#/utils/configPaths'
import path from 'path' import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static'
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static' import { rpcServer } from '~/events/rpc'
import fs from 'fs-extra'
import { startFileServer } from '../fileServer'
import webServer from '../server/webServer'
import axios from 'axios'
import { configPaths } from '~/universal/utils/configPaths'
const isDevelopment = process.env.NODE_ENV !== 'production' const isDevelopment = process.env.NODE_ENV !== 'production'
const handleStartUpFiles = (argv: string[], cwd: string) => { const handleStartUpFiles = (argv: string[], cwd: string) => {
@ -141,7 +146,7 @@ class LifeCycle {
fixPath() fixPath()
beforeOpen() beforeOpen()
initI18n() initI18n()
ipcList.listen() rpcServer.start()
getManageApi() getManageApi()
UpDownTaskQueue.getInstance() UpDownTaskQueue.getInstance()
manageIpcList.listen() manageIpcList.listen()
@ -167,11 +172,15 @@ class LifeCycle {
} }
const isHideDock = db.get(configPaths.settings.isHideDock) || false const isHideDock = db.get(configPaths.settings.isHideDock) || false
const startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET const startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET
const currentPicBed = db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms'
// @ts-ignore
const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default'
const tooltip = `${currentPicBed} ${currentPicBedConfig}`
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
isHideDock ? app.dock.hide() : setDockMenu() isHideDock ? app.dock.hide() : setDockMenu()
startMode !== ISartMode.NO_TRAY && createTray() startMode !== ISartMode.NO_TRAY && createTray(tooltip)
} else { } else {
createTray() createTray(tooltip)
} }
db.set(configPaths.needReload, false) db.set(configPaths.needReload, false)
updateChecker() updateChecker()
@ -223,7 +232,7 @@ class LifeCycle {
settingWindow.focus() settingWindow.focus()
} }
const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER) const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER)
fs.ensureDir(clipboardDir) fs.emptyDir(clipboardDir)
} }
app.whenReady().then(readyFunction) app.whenReady().then(readyFunction)
} }
@ -276,6 +285,8 @@ class LifeCycle {
globalShortcut.unregisterAll() globalShortcut.unregisterAll()
bus.removeAllListeners() bus.removeAllListeners()
server.shutdown() server.shutdown()
webServer.stop()
stopFileServer()
}) })
// Exit cleanly on request from parent process in development mode. // Exit cleanly on request from parent process in development mode.
if (isDevelopment) { if (isDevelopment) {

View File

@ -1,6 +1,5 @@
/* eslint-disable */ import { manageDbChecker } from '~/manage/datastore/dbChecker'
import { manageDbChecker } from './datastore/dbChecker' import { ManageApi } from '~/manage/manageApi'
import { ManageApi } from './manageApi'
manageDbChecker() manageDbChecker()
const getManageApi = (picBedName: string = 'placeholder'): ManageApi => { const getManageApi = (picBedName: string = 'placeholder'): ManageApi => {

View File

@ -1,38 +1,18 @@
// Axios
import axios from 'axios'
// 加密函数、获取文件 MIME 类型、错误格式化函数、新的下载器、并发异步任务池
import { hmacSha1Base64, getFileMimeType, formatError, NewDownloader, ConcurrencyPromisePool } from '../utils/common'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron'
// 快速 XML 解析器
import { XMLParser } from 'fast-xml-parser'
// 阿里云 OSS 客户端库
import OSS from 'ali-oss' import OSS from 'ali-oss'
import axios from 'axios'
// 路径处理库 import { ipcMain, IpcMainEvent } from 'electron'
import { XMLParser } from 'fast-xml-parser'
import path from 'path' import path from 'path'
// 是否为图片的判断函数
import { isImage } from '~/renderer/manage/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明 import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { IWindowList } from '#/types/enum' import { ManageLogger } from '~/manage/utils/logger'
import { hmacSha1Base64, getFileMimeType, formatError, NewDownloader, ConcurrencyPromisePool } from '~/manage/utils/common'
// 上传下载任务队列 import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus } from '../datastore/upDownTaskQueue' import { isImage } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
// 日志记录器
import { ManageLogger } from '../utils/logger'
// 取消下载任务的加载文件列表、刷新下载文件传输列表
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
// 坑爹阿里云 返回数据类型标注和实际各种不一致 // 坑爹阿里云 返回数据类型标注和实际各种不一致
class AliyunApi { class AliyunApi {
@ -53,9 +33,10 @@ class AliyunApi {
this.logger = logger this.logger = logger
} }
formatFolder (item: string, slicedPrefix: string) { formatFolder (item: string, slicedPrefix: string, urlPrefix: string): any {
return { return {
key: item, key: item,
url: `${urlPrefix}/${item}`,
fileSize: 0, fileSize: 0,
formatedTime: '', formatedTime: '',
fileName: item.replace(slicedPrefix, '').replace('/', ''), fileName: item.replace(slicedPrefix, '').replace('/', ''),
@ -288,7 +269,7 @@ class AliyunApi {
}) })
if (res?.res?.statusCode === 200) { if (res?.res?.statusCode === 200) {
res?.prefixes?.forEach((item: string) => { res?.prefixes?.forEach((item: string) => {
result.fullList.push(this.formatFolder(item, slicedPrefix)) result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
}) })
res?.objects?.forEach((item: OSS.ObjectMeta) => { res?.objects?.forEach((item: OSS.ObjectMeta) => {
item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
@ -348,7 +329,7 @@ class AliyunApi {
} }
} }
const fullList = [ const fullList = [
...(res.prefixes?.map((item: string) => this.formatFolder(item, slicedPrefix)) || []), ...(res.prefixes?.map((item: string) => this.formatFolder(item, slicedPrefix, urlPrefix)) || []),
...(res.objects?.filter((item: OSS.ObjectMeta) => item.size !== 0).map((item: OSS.ObjectMeta) => this.formatFile(item, slicedPrefix, urlPrefix)) || []) ...(res.objects?.filter((item: OSS.ObjectMeta) => item.size !== 0).map((item: OSS.ObjectMeta) => this.formatFile(item, slicedPrefix, urlPrefix)) || [])
] ]
return { return {

View File

@ -1,14 +1,14 @@
import AliyunApi from './aliyun' import AliyunApi from '~/manage/apis/aliyun'
import GithubApi from './github' import GithubApi from '~/manage/apis/github'
import ImgurApi from './imgur' import ImgurApi from '~/manage/apis/imgur'
import LocalApi from './local' import LocalApi from '~/manage/apis/local'
import QiniuApi from './qiniu' import QiniuApi from '~/manage/apis/qiniu'
import S3plistApi from './s3plist' import S3plistApi from '~/manage/apis/s3plist'
import SftpApi from './sftp' import SftpApi from '~/manage/apis/sftp'
import SmmsApi from './smms' import SmmsApi from '~/manage/apis/smms'
import TcyunApi from './tcyun' import TcyunApi from '~/manage/apis/tcyun'
import UpyunApi from './upyun' import UpyunApi from '~/manage/apis/upyun'
import WebdavplistApi from './webdavplist' import WebdavplistApi from '~/manage/apis/webdavplist'
export default { export default {
AliyunApi, AliyunApi,

View File

@ -1,35 +1,17 @@
// HTTP 请求库
import got from 'got'
// 日志记录器
import { ManageLogger } from '../utils/logger'
// HTTP 代理格式化函数、是否为图片的判断函数
import { formatHttpProxy, isImage } from '~/renderer/manage/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { IWindowList } from '#/types/enum'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
// got 上传函数、路径处理函数、新的下载器、获取请求代理、获取请求选项、并发异步任务池、错误格式化函数
import { gotUpload, trimPath, NewDownloader, getAgent, getOptions, ConcurrencyPromisePool, formatError } from '../utils/common'
// 上传下载任务队列
import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue'
// 文件系统库
import fs from 'fs-extra' import fs from 'fs-extra'
import got from 'got'
// 路径处理库
import path from 'path' import path from 'path'
// 取消下载任务的加载文件列表、刷新下载文件传输列表 import windowManager from 'apis/app/window/windowManager'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { gotUpload, NewDownloader, getAgent, getOptions, ConcurrencyPromisePool, formatError } from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, IWindowList } from '#/types/enum'
import { formatHttpProxy, isImage, trimPath } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
class GithubApi { class GithubApi {
token: string token: string
@ -52,11 +34,23 @@ class GithubApi {
} }
} }
formatFolder (item: any, slicedPrefix: string) { formatFolder (item: any, slicedPrefix: string, branch: string, repo: string, cdnUrl: string | undefined) {
const key = `${slicedPrefix ? `${slicedPrefix}/` : ''}${item.path}/` const key = `${slicedPrefix ? `${slicedPrefix}/` : ''}${item.path}/`
let rawUrl = ''
const placeholders = ['{username}', '{repo}', '{branch}', '{path}']
rawUrl = cdnUrl
? placeholders.some(item => cdnUrl.includes(item))
? placeholders.reduce((url, ph) => {
const value = ph === '{username}' ? this.username : ph === '{repo}' ? repo : ph === '{branch}' ? branch : ph === '{path}' ? key : ''
return url.replaceAll(ph, value)
}, cdnUrl)
: `${cdnUrl}/${key}`
: `https://raw.githubusercontent.com/${this.username}/${repo}/${branch}/${key}`
rawUrl = rawUrl.replace(/(?<!https?:)\/{2,}/g, '/')
return { return {
...item, ...item,
Key: key, Key: key,
url: rawUrl,
key, key,
fileSize: 0, fileSize: 0,
formatedTime: '', formatedTime: '',
@ -224,7 +218,7 @@ class GithubApi {
if (res && res.statusCode === 200) { if (res && res.statusCode === 200) {
res.body.tree.forEach((item: any) => { res.body.tree.forEach((item: any) => {
if (item.type === 'tree') { if (item.type === 'tree') {
result.fullList.push(this.formatFolder(item, slicedPrefix)) result.fullList.push(this.formatFolder(item, slicedPrefix, branch, repo, cdnUrl))
} else { } else {
result.fullList.push(this.formatFile(item, slicedPrefix, branch, repo, cdnUrl)) result.fullList.push(this.formatFile(item, slicedPrefix, branch, repo, cdnUrl))
} }

View File

@ -1,14 +1,12 @@
// External dependencies import { ipcMain, IpcMainEvent } from 'electron'
import fs from 'fs-extra'
import FormData from 'form-data' import FormData from 'form-data'
import fs from 'fs-extra'
import got from 'got' import got from 'got'
import path from 'path' import path from 'path'
// Electron modules import windowManager from 'apis/app/window/windowManager'
import { ipcMain, IpcMainEvent } from 'electron'
// Custom utilities and modules import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { IWindowList } from '#/types/enum'
import { import {
ConcurrencyPromisePool, ConcurrencyPromisePool,
formatError, formatError,
@ -17,11 +15,11 @@ import {
getAgent, getAgent,
gotUpload, gotUpload,
NewDownloader NewDownloader
} from '../utils/common' } from '~/manage/utils/common'
import ManageLogger from '../utils/logger' import ManageLogger from '~/manage/utils/logger'
import windowManager from 'apis/app/window/windowManager'
import { formatHttpProxy, isImage } from '~/renderer/manage/utils/common' import { commonTaskStatus, IWindowList } from '#/types/enum'
import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue' import { formatHttpProxy, isImage } from '#/utils/common'
class ImgurApi { class ImgurApi {
userName: string userName: string

View File

@ -1,33 +1,16 @@
// 日志记录器
import ManageLogger from '../utils/logger'
// 错误格式化函数、端点地址格式化函数、获取内部代理、新的下载器、并发异步任务池
import { formatError } from '../utils/common'
// HTTP 代理格式化函数、是否为图片的判断函数
import { isImage } from '@/manage/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { IWindowList } from '#/types/enum'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
// 上传下载任务队列
import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus, downloadTaskSpecialStatus } from '../datastore/upDownTaskQueue'
// 文件系统库
import fs from 'fs-extra' import fs from 'fs-extra'
// 路径处理库
import path from 'path' import path from 'path'
import * as fsWalk from '@nodelib/fs.walk' import * as fsWalk from '@nodelib/fs.walk'
// 取消下载任务的加载文件列表、刷新下载文件传输列表 import windowManager from 'apis/app/window/windowManager'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static' import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { formatError } from '~/manage/utils/common'
import ManageLogger from '~/manage/utils/logger'
import { commonTaskStatus, downloadTaskSpecialStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { isImage } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
class LocalApi { class LocalApi {
logger: ManageLogger logger: ManageLogger

View File

@ -1,35 +1,18 @@
// Axios
import axios from 'axios' import axios from 'axios'
import { ipcMain, IpcMainEvent } from 'electron'
// 加密函数、获取文件 MIME 类型、新的下载器、错误格式化函数、并发异步任务池 import path from 'path'
import { hmacSha1Base64, getFileMimeType, NewDownloader, formatError, ConcurrencyPromisePool } from '../utils/common'
// 七牛云客户端库
import qiniu from 'qiniu/index' import qiniu from 'qiniu/index'
// 路径处理库
import path from 'path'
// 是否为图片的判断函数
import { isImage } from '~/renderer/manage/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明 import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { IWindowList } from '#/types/enum' import { hmacSha1Base64, getFileMimeType, NewDownloader, formatError, ConcurrencyPromisePool } from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
// Electron 相关 import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { ipcMain, IpcMainEvent } from 'electron' import { isImage } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
// 上传下载任务队列
import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus } from '../datastore/upDownTaskQueue'
// 日志记录器
import { ManageLogger } from '../utils/logger'
// 取消下载任务的加载文件列表、刷新下载文件传输列表
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
class QiniuApi { class QiniuApi {
mac: qiniu.auth.digest.Mac mac: qiniu.auth.digest.Mac
@ -52,10 +35,11 @@ class QiniuApi {
this.logger = logger this.logger = logger
} }
formatFolder (item: string, slicedPrefix: string) { formatFolder (item: string, slicedPrefix: string, urlPrefix: string) {
return { return {
Key: item, Key: item,
key: item, key: item,
url: `${urlPrefix}/${item}`,
fileSize: 0, fileSize: 0,
fileName: item.replace(slicedPrefix, '').replace('/', ''), fileName: item.replace(slicedPrefix, '').replace('/', ''),
isDir: true, isDir: true,
@ -342,7 +326,7 @@ class QiniuApi {
}) })
if (res && res.respInfo.statusCode === 200) { if (res && res.respInfo.statusCode === 200) {
res.respBody && res.respBody.commonPrefixes && res.respBody.commonPrefixes.forEach((item: any) => { res.respBody && res.respBody.commonPrefixes && res.respBody.commonPrefixes.forEach((item: any) => {
result.fullList.push(this.formatFolder(item, slicedPrefix)) result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
}) })
res.respBody && res.respBody.items && res.respBody.items.forEach((item: any) => { res.respBody && res.respBody.items && res.respBody.items.forEach((item: any) => {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
@ -409,7 +393,7 @@ class QiniuApi {
if (res?.respInfo?.statusCode === 200) { if (res?.respInfo?.statusCode === 200) {
if (res.respBody?.commonPrefixes) { if (res.respBody?.commonPrefixes) {
res.respBody.commonPrefixes.forEach((item: string) => { res.respBody.commonPrefixes.forEach((item: string) => {
result.fullList.push(this.formatFolder(item, slicedPrefix)) result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
}) })
} }
if (res.respBody?.items) { if (res.respBody?.items) {

View File

@ -1,4 +1,3 @@
// AWS S3 相关
import { import {
S3Client, S3Client,
ListBucketsCommand, ListBucketsCommand,
@ -12,52 +11,30 @@ import {
DeleteObjectCommand, DeleteObjectCommand,
DeleteObjectsCommand, DeleteObjectsCommand,
PutObjectCommand, PutObjectCommand,
S3ClientConfig S3ClientConfig,
CreateBucketCommand,
PutPublicAccessBlockCommand,
PutBucketAclCommand
} from '@aws-sdk/client-s3' } from '@aws-sdk/client-s3'
// AWS S3 上传和进度
import { Upload, Progress } from '@aws-sdk/lib-storage' import { Upload, Progress } from '@aws-sdk/lib-storage'
// AWS S3 请求签名
import { getSignedUrl } from '@aws-sdk/s3-request-presigner' import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
// HTTP 和 HTTPS 模块
import https from 'https'
import http, { AgentOptions } from 'http'
import { NodeHttpHandler } from '@smithy/node-http-handler' import { NodeHttpHandler } from '@smithy/node-http-handler'
// 日志记录器
import { ManageLogger } from '../utils/logger'
// 端点地址格式化函数、错误格式化函数、获取请求代理、获取文件 MIME 类型、新的下载器、并发异步任务池
import { formatEndpoint, formatError, getAgent, getFileMimeType, NewDownloader, ConcurrencyPromisePool } from '../utils/common'
// 是否为图片的判断函数、HTTP 代理格式化函数
import { isImage, formatHttpProxy } from '@/manage/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { IWindowList } from '#/types/enum'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
// 上传下载任务队列
import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus } from '../datastore/upDownTaskQueue'
// 文件系统库
import fs from 'fs-extra' import fs from 'fs-extra'
import http, { AgentOptions } from 'http'
// 路径处理库 import https from 'https'
import path from 'path' import path from 'path'
// 取消下载任务的加载文件列表、刷新下载文件传输列表 import windowManager from 'apis/app/window/windowManager'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
// dogecloudApi import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { dogecloudApi, DogecloudToken, getTempToken } from '../utils/dogeAPI' import { formatError, getAgent, getFileMimeType, NewDownloader, ConcurrencyPromisePool } from '~/manage/utils/common'
import { dogecloudApi, DogecloudToken, getTempToken } from '~/manage/utils/dogeAPI'
import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { isImage, formatEndpoint, formatHttpProxy } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
class S3plistApi { class S3plistApi {
baseOptions: S3ClientConfig baseOptions: S3ClientConfig
@ -141,9 +118,10 @@ class S3plistApi {
logParam = (error:any, method: string) => logParam = (error:any, method: string) =>
this.logger.error(formatError(error, { class: 'S3plistApi', method })) this.logger.error(formatError(error, { class: 'S3plistApi', method }))
formatFolder (item: CommonPrefix, slicedPrefix: string): any { formatFolder (item: CommonPrefix, slicedPrefix: string, urlPrefix: string): any {
return { return {
Key: item.Prefix, Key: item.Prefix,
url: `${urlPrefix}/${item.Prefix}`,
fileSize: 0, fileSize: 0,
formatedTime: '', formatedTime: '',
fileName: item.Prefix?.replace(slicedPrefix, '').replace('/', ''), fileName: item.Prefix?.replace(slicedPrefix, '').replace('/', ''),
@ -171,6 +149,89 @@ class S3plistApi {
} }
} }
async putPublicAccess (bucketName: string, client: S3Client) {
const input = {
Bucket: bucketName,
PublicAccessBlockConfiguration: {
BlockPublicAcls: false,
IgnorePublicAcls: false,
BlockPublicPolicy: false,
RestrictPublicBuckets: false
}
}
const command = new PutPublicAccessBlockCommand(input)
const data = await client.send(command)
if (data.$metadata.httpStatusCode !== 200) {
this.logParam(data, 'putPublicAccess')
throw new Error('manage.setting.putPublicAccessError')
}
}
/**
*
* @param {Object} configMap
* configMap = {
* BucketName: string,
* region: string,
* acl: string
* }
*/
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
const { BucketName, region, acl, endpoint } = configMap
try {
await this.getDogeCloudToken()
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
options.region = String(region) || 'us-east-1'
const client = new S3Client(options)
const command = new ListBucketsCommand({})
const data = await client.send(command)
if (data.$metadata.httpStatusCode === 200) {
const bucketList = data.Buckets?.map((item) => item.Name)
if (bucketList?.includes(BucketName)) {
return true
}
}
if (endpoint === '' || endpoint.includes('amazonaws')) {
const createCommand = new CreateBucketCommand({
Bucket: BucketName,
ObjectOwnership: 'BucketOwnerPreferred'
})
const createData = await client.send(createCommand)
if (createData.$metadata.httpStatusCode === 200) {
if (acl !== 'private') {
await this.putPublicAccess(BucketName, client)
const putACLCommand = new PutBucketAclCommand({
Bucket: BucketName,
ACL: acl
})
const putACLData = await client.send(putACLCommand)
if (putACLData.$metadata.httpStatusCode !== 200) {
this.logParam(putACLData, 'createBucket')
return false
}
}
return true
} else {
this.logParam(createData, 'createBucket')
}
} else {
const createCommand = new CreateBucketCommand({
Bucket: BucketName,
ACL: acl
})
const createData = await client.send(createCommand)
if (createData.$metadata.httpStatusCode === 200) {
return true
} else {
this.logParam(createData, 'createBucket')
}
}
} catch (error) {
this.logParam(error, 'createBucket')
}
return false
}
/** /**
* *
*/ */
@ -333,7 +394,7 @@ class S3plistApi {
res = await client.send(command) res = await client.send(command)
if (res.$metadata.httpStatusCode === 200) { if (res.$metadata.httpStatusCode === 200) {
res.CommonPrefixes && res.CommonPrefixes.forEach((item: CommonPrefix) => { res.CommonPrefixes && res.CommonPrefixes.forEach((item: CommonPrefix) => {
result.fullList.push(this.formatFolder(item, slicedPrefix)) result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
}) })
res.Contents && res.Contents.forEach((item: _Object) => { res.Contents && res.Contents.forEach((item: _Object) => {
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
@ -385,7 +446,7 @@ class S3plistApi {
const data = await client.send(command) const data = await client.send(command)
if (data.$metadata.httpStatusCode === 200) { if (data.$metadata.httpStatusCode === 200) {
result.fullList = [ result.fullList = [
...(data.CommonPrefixes?.map(item => this.formatFolder(item, slicedPrefix)) || []), ...(data.CommonPrefixes?.map(item => this.formatFolder(item, slicedPrefix, urlPrefix)) || []),
...(data.Contents?.map(item => this.formatFile(item, slicedPrefix, urlPrefix)) || []) ...(data.Contents?.map(item => this.formatFile(item, slicedPrefix, urlPrefix)) || [])
] ]
result.isTruncated = data.IsTruncated || false result.isTruncated = data.IsTruncated || false

View File

@ -1,33 +1,18 @@
// 日志记录器
import ManageLogger from '../utils/logger'
// SSH 客户端
import SSHClient from '~/main/utils/sshClient'
// 错误格式化函数、新的下载器、并发异步任务池
import { formatError } from '../utils/common'
// 是否为图片的判断函数
import { isImage } from '@/manage/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { IWindowList } from '#/types/enum'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
// 上传下载任务队列
import UpDownTaskQueue, { commonTaskStatus, downloadTaskSpecialStatus, uploadTaskSpecialStatus } from '../datastore/upDownTaskQueue'
// 路径处理库
import path from 'path' import path from 'path'
// 取消下载任务的加载文件列表、刷新下载文件传输列表 import windowManager from 'apis/app/window/windowManager'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
import { Undefinable } from '~/universal/types/manage' import SSHClient from '~/utils/sshClient'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { formatError } from '~/manage/utils/common'
import ManageLogger from '~/manage/utils/logger'
import { commonTaskStatus, downloadTaskSpecialStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { Undefinable } from '#/types/manage'
import { isImage } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
interface listDirResult { interface listDirResult {
permissions: string permissions: string

View File

@ -1,35 +1,17 @@
// 是否为图片的判断函数
import { isImage } from '@/manage/utils/common'
// Axios 和 Axios 实例类型声明
import axios, { AxiosInstance } from 'axios' import axios, { AxiosInstance } from 'axios'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { IWindowList } from '#/types/enum'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
// 表单数据库
import FormData from 'form-data' import FormData from 'form-data'
// 文件系统库
import fs from 'fs-extra' import fs from 'fs-extra'
// 获取文件 MIME 类型、got 上传函数、新的下载器、并发异步任务池、错误格式化函数
import { getFileMimeType, gotUpload, NewDownloader, ConcurrencyPromisePool, formatError } from '../utils/common'
// 路径处理库
import path from 'path' import path from 'path'
// 上传下载任务队列 import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue'
// 日志记录器 import { getFileMimeType, gotUpload, NewDownloader, ConcurrencyPromisePool, formatError } from '~/manage/utils/common'
import { ManageLogger } from '../utils/logger' import { ManageLogger } from '~/manage/utils/logger'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { commonTaskStatus, IWindowList } from '#/types/enum'
import { isImage } from '#/utils/common'
class SmmsApi { class SmmsApi {
baseUrl = 'https://smms.app/api/v2' baseUrl = 'https://smms.app/api/v2'

View File

@ -1,38 +1,18 @@
// 腾讯云 COS SDK
import COS from 'cos-nodejs-sdk-v5' import COS from 'cos-nodejs-sdk-v5'
import { ipcMain, IpcMainEvent } from 'electron'
// 文件系统库
import fs from 'fs-extra' import fs from 'fs-extra'
// 路径处理库
import path from 'path' import path from 'path'
// 是否为图片的判断函数
import { isImage } from '~/renderer/manage/utils/common'
// URL 编码处理函数
import { handleUrlEncode } from '~/universal/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明 import { formatError, getFileMimeType } from '~/manage/utils/common'
import { IWindowList } from '#/types/enum' import { ManageLogger } from '~/manage/utils/logger'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
// Electron 相关 import { handleUrlEncode, isImage } from '#/utils/common'
import { ipcMain, IpcMainEvent } from 'electron' import { commonTaskStatus, downloadTaskSpecialStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
// 错误格式化函数、获取文件 MIME 类型
import { formatError, getFileMimeType } from '../utils/common'
// 上传下载任务队列
import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus, downloadTaskSpecialStatus } from '../datastore/upDownTaskQueue'
// 日志记录器
import { ManageLogger } from '../utils/logger'
// 取消下载任务的加载文件列表、刷新下载文件传输列表
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
class TcyunApi { class TcyunApi {
ctx: COS ctx: COS
@ -46,11 +26,12 @@ class TcyunApi {
this.logger = logger this.logger = logger
} }
formatFolder (item: {Prefix: string}, slicedPrefix: string): any { formatFolder (item: {Prefix: string}, slicedPrefix: string, urlPrefix: string) {
return { return {
...item, ...item,
key: item.Prefix, key: item.Prefix,
fileSize: 0, fileSize: 0,
url: `${urlPrefix}/${item.Prefix}`,
formatedTime: '', formatedTime: '',
fileName: item.Prefix.replace(slicedPrefix, '').replace('/', ''), fileName: item.Prefix.replace(slicedPrefix, '').replace('/', ''),
isDir: true, isDir: true,
@ -108,13 +89,8 @@ class TcyunApi {
* acl: private | publicRead | publicReadWrite * acl: private | publicRead | publicReadWrite
*/ */
async createBucket (configMap: IStringKeyMap): Promise < boolean > { async createBucket (configMap: IStringKeyMap): Promise < boolean > {
const aclTransMap: IStringKeyMap = {
private: 'private',
publicRead: 'public-read',
publicReadWrite: 'public-read-write'
}
const res = await this.ctx.putBucket({ const res = await this.ctx.putBucket({
ACL: aclTransMap[configMap.acl], ACL: configMap.acl,
Bucket: configMap.BucketName, Bucket: configMap.BucketName,
Region: configMap.region Region: configMap.region
}) })
@ -196,7 +172,7 @@ class TcyunApi {
}) })
if (res?.statusCode === 200) { if (res?.statusCode === 200) {
result.fullList.push( result.fullList.push(
...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix)), ...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix, urlPrefix)),
...res.Contents.filter(item => parseInt(item.Size) !== 0) ...res.Contents.filter(item => parseInt(item.Size) !== 0)
.map(item => this.formatFile(item, slicedPrefix, urlPrefix)) .map(item => this.formatFile(item, slicedPrefix, urlPrefix))
) )
@ -252,7 +228,7 @@ class TcyunApi {
} }
const result = { const result = {
fullList: [ fullList: [
...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix)), ...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix, urlPrefix)),
...res.Contents.filter(item => parseInt(item.Size) !== 0) ...res.Contents.filter(item => parseInt(item.Size) !== 0)
.map(item => this.formatFile(item, slicedPrefix, urlPrefix)) .map(item => this.formatFile(item, slicedPrefix, urlPrefix))
], ],

View File

@ -1,40 +1,19 @@
import axios from 'axios'
import { ipcMain, IpcMainEvent } from 'electron'
import FormData from 'form-data'
import fs from 'fs-extra'
import path from 'path'
import Upyun from 'upyun' import Upyun from 'upyun'
// 加密函数、获取文件 MIME 类型、新的下载器、got 上传函数、并发异步任务池、错误格式化函数
import { md5, hmacSha1Base64, getFileMimeType, NewDownloader, gotUpload, ConcurrencyPromisePool, formatError } from '../utils/common'
// 是否为图片的判断函数
import { isImage } from '~/renderer/manage/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明 import { md5, hmacSha1Base64, getFileMimeType, NewDownloader, gotUpload, ConcurrencyPromisePool, formatError } from '~/manage/utils/common'
import { IWindowList } from '#/types/enum' import { ManageLogger } from '~/manage/utils/logger'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
// Electron 相关 import { commonTaskStatus, IWindowList } from '#/types/enum'
import { ipcMain, IpcMainEvent } from 'electron' import { isImage } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
// Axios
import axios from 'axios'
// 表单数据库
import FormData from 'form-data'
// 文件系统库
import fs from 'fs-extra'
// 路径处理库
import path from 'path'
// 上传下载任务队列
import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue'
// 日志记录器
import { ManageLogger } from '../utils/logger'
// 取消下载任务的加载文件列表、刷新下载文件传输列表
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
class UpyunApi { class UpyunApi {
ser: Upyun.Service ser: Upyun.Service
@ -67,11 +46,16 @@ class UpyunApi {
return `_upt=${upt}` return `_upt=${upt}`
} }
formatFolder (item: any, slicedPrefix: string) { formatFolder (item: any, slicedPrefix: string, urlPrefix: string) {
const key = `${slicedPrefix}${item.name}/` const key = `${slicedPrefix}${item.name}/`
let url = `${urlPrefix}/${key}`
if (this.antiLeechToken) {
url = `${url}?${this.getAntiLeechParam(key)}`
}
return { return {
...item, ...item,
key, key,
url,
fileSize: 0, fileSize: 0,
formatedTime: '', formatedTime: '',
fileName: item.name, fileName: item.name,
@ -202,7 +186,7 @@ class UpyunApi {
if (res) { if (res) {
res.files?.forEach((item: any) => { res.files?.forEach((item: any) => {
item.type === 'N' && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) item.type === 'N' && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
item.type === 'F' && result.fullList.push(this.formatFolder(item, slicedPrefix)) item.type === 'F' && result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
}) })
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
} else { } else {
@ -252,7 +236,7 @@ class UpyunApi {
if (res) { if (res) {
res.files?.forEach((item: any) => { res.files?.forEach((item: any) => {
item.type === 'N' && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) item.type === 'N' && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
item.type === 'F' && result.fullList.push(this.formatFolder(item, slicedPrefix)) item.type === 'F' && result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
}) })
result.isTruncated = res.next !== this.stopMarker result.isTruncated = res.next !== this.stopMarker
result.nextMarker = res.next result.nextMarker = res.next

View File

@ -1,41 +1,22 @@
// 日志记录器 import { ipcMain, IpcMainEvent } from 'electron'
import ManageLogger from '../utils/logger' import fs from 'fs-extra'
// WebDAV 客户端库
import { createClient, WebDAVClient, FileStat, ProgressEvent, AuthType, WebDAVClientOptions } from 'webdav'
// 错误格式化函数、端点地址格式化函数、获取内部代理、新的下载器、并发异步任务池
import { formatError, formatEndpoint, getInnerAgent, NewDownloader, ConcurrencyPromisePool } from '../utils/common'
// HTTP 代理格式化函数、是否为图片的判断函数
import { formatHttpProxy, isImage } from '@/manage/utils/common'
// HTTP 和 HTTPS 模块
import http from 'http' import http from 'http'
import https from 'https' import https from 'https'
import path from 'path'
import { createClient, WebDAVClient, FileStat, ProgressEvent, AuthType, WebDAVClientOptions } from 'webdav'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明 import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { IWindowList } from '#/types/enum' import { formatError, getInnerAgent, NewDownloader, ConcurrencyPromisePool } from '~/manage/utils/common'
import ManageLogger from '~/manage/utils/logger'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron'
// 上传下载任务队列
import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus } from '../datastore/upDownTaskQueue'
// 文件系统库
import fs from 'fs-extra'
// 路径处理库
import path from 'path'
// 取消下载任务的加载文件列表、刷新下载文件传输列表
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
import { getAuthHeader } from '@/manage/utils/digestAuth' import { getAuthHeader } from '@/manage/utils/digestAuth'
import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { isImage, formatEndpoint, formatHttpProxy } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
class WebdavplistApi { class WebdavplistApi {
endpoint: string endpoint: string
username: string username: string

View File

@ -1,19 +1,19 @@
/* eslint-disable */
import { JSONStore } from '@picgo/store' import { JSONStore } from '@picgo/store'
import { IJSON } from '@picgo/store/dist/types' import { IJSON } from '@picgo/store/dist/types'
import { ManageApiType, ManageConfigType } from '~/universal/types/manage'
import { IManageApiType, IManageConfigType } from '#/types/manage'
class ManageDB { class ManageDB {
readonly #ctx: ManageApiType readonly #ctx: IManageApiType
readonly #db: JSONStore readonly #db: JSONStore
constructor (ctx: ManageApiType) { constructor (ctx: IManageApiType) {
this.#ctx = ctx this.#ctx = ctx
this.#db = new JSONStore(this.#ctx.configPath) this.#db = new JSONStore(this.#ctx.configPath)
let initParams: IStringKeyMap = { const initParams: IStringKeyMap = {
picBed: {}, picBed: {},
settings: {} settings: {}
} }
for (let key in initParams) { for (const key in initParams) {
if (!this.#db.has(key)) { if (!this.#db.has(key)) {
try { try {
this.#db.set(key, initParams[key]) this.#db.set(key, initParams[key])
@ -49,13 +49,13 @@ class ManageDB {
return this.#db.unset(key, value) return this.#db.unset(key, value)
} }
saveConfig (config: Partial<ManageConfigType>): void { saveConfig (config: Partial<IManageConfigType>): void {
Object.keys(config).forEach((name: string) => { Object.keys(config).forEach((name: string) => {
this.set(name, config[name]) this.set(name, config[name])
}) })
} }
removeConfig (config: ManageConfigType): void { removeConfig (config: IManageConfigType): void {
Object.keys(config).forEach((name: string) => { Object.keys(config).forEach((name: string) => {
this.unset(name, config[name]) this.unset(name, config[name])
}) })

View File

@ -1,10 +1,12 @@
import fs from 'fs-extra'
import writeFile from 'write-file-atomic'
import path from 'path'
import { app } from 'electron'
import { getLogger } from '@core/utils/localLogger'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { T } from '~/main/i18n' import { app } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import writeFile from 'write-file-atomic'
import { getLogger } from '@core/utils/localLogger'
import { T } from '~/i18n'
const STORE_PATH = app.getPath('userData') const STORE_PATH = app.getPath('userData')
const manageConfigFilePath = path.join(STORE_PATH, 'manage.json') const manageConfigFilePath = path.join(STORE_PATH, 'manage.json')

View File

@ -1,59 +1,12 @@
// a singleton class to manage the up/down task queue // a singleton class to manage the up/down task queue
// qiniu tcyun aliyun smms imgur github upyun // qiniu tcyun aliyun smms imgur github upyun
import path from 'path'
import { app } from 'electron' import { app } from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
export enum commonTaskStatus { import path from 'path'
queuing = 'queuing',
failed = 'failed',
canceled = 'canceled',
paused = 'paused'
}
export enum uploadTaskSpecialStatus { import { commonTaskStatus, downloadTaskSpecialStatus, uploadTaskSpecialStatus } from '#/types/enum'
uploading = 'uploading', import { IDownloadTask, IUploadTask } from '#/types/manage'
uploaded = 'uploaded'
}
export enum downloadTaskSpecialStatus {
downloading = 'downloading',
downloaded = 'downloaded',
}
export type uploadTaskStatus = commonTaskStatus | uploadTaskSpecialStatus
type downloadTaskStatus = commonTaskStatus | downloadTaskSpecialStatus
export interface IUploadTask {
id: string
progress: number
status: uploadTaskStatus
sourceFilePath: string
sourceFileName: string
targetFilePath: string
targetFileBucket?: string
response?: any
cancelToken?: string
timeConsuming?: number
alias?: string
[other: string]: any
}
export interface IDownloadTask {
id: string
progress: number
status: downloadTaskStatus
sourceFileUrl?: string
sourceFileName?: string
sourceConfig?: IStringKeyMap
targetFilePath?: string
response?: any
cancelToken?: string
timeConsuming?: number
reseumConfig?: IStringKeyMap
alias?: string
[other: string]: any
}
class UpDownTaskQueue { class UpDownTaskQueue {
/* eslint-disable */ /* eslint-disable */

View File

@ -1,3 +1,3 @@
export const PICLIST_MANAGE_GET_CONFIG = 'PICLIST_MANAGE_GET_CONFIG' export const PICLIST_MANAGE_GET_CONFIG = 'PICLIST_MANAGE_GET_CONFIG'
export const PICLIST_MANAGE_SAVE_CONFIG = 'PICLIST_MANAGE_SAVE_CONFIG'
export const PICLIST_MANAGE_REMOVE_CONFIG = 'PICLIST_MANAGE_REMOVE_CONFIG' export const PICLIST_MANAGE_REMOVE_CONFIG = 'PICLIST_MANAGE_REMOVE_CONFIG'
export const PICLIST_MANAGE_SAVE_CONFIG = 'PICLIST_MANAGE_SAVE_CONFIG'

View File

@ -1,11 +1,13 @@
import manageCoreIPC from './manageCoreIPC'
import { ManageApi } from '../manageApi'
import { ipcMain, IpcMainInvokeEvent, dialog, app, shell } from 'electron' import { ipcMain, IpcMainInvokeEvent, dialog, app, shell } from 'electron'
import UpDownTaskQueue from '../datastore/upDownTaskQueue'
import { downloadFileFromUrl } from '../utils/common'
import path from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import { selectDownloadFolder } from '@/manage/utils/static' import path from 'path'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import manageCoreIPC from '~/manage/events/manageCoreIPC'
import { ManageApi } from '~/manage/manageApi'
import { downloadFileFromUrl } from '~/manage/utils/common'
import { selectDownloadFolder } from '#/utils/static'
export const manageIpcList = { export const manageIpcList = {
listen () { listen () {

View File

@ -1,27 +1,28 @@
import { import {
IpcMainEvent, IpcMainEvent,
IpcMainInvokeEvent,
ipcMain ipcMain
} from 'electron' } from 'electron'
import getManageApi from '../Main'
import { PICLIST_MANAGE_GET_CONFIG, PICLIST_MANAGE_SAVE_CONFIG, PICLIST_MANAGE_REMOVE_CONFIG } from './constants' import getManageApi from '~/manage/Main'
import { PICLIST_MANAGE_GET_CONFIG, PICLIST_MANAGE_SAVE_CONFIG, PICLIST_MANAGE_REMOVE_CONFIG } from '~/manage/events/constants'
const manageApi = getManageApi() const manageApi = getManageApi()
const handleManageGetConfig = () => { const handleManageGetConfig = () => {
ipcMain.on(PICLIST_MANAGE_GET_CONFIG, (event: IpcMainEvent, key: string | undefined, callbackId: string) => { ipcMain.handle(PICLIST_MANAGE_GET_CONFIG, (_: IpcMainInvokeEvent, key: string | undefined) => {
const result = manageApi.getConfig(key) return manageApi.getConfig(key)
event.sender.send(PICLIST_MANAGE_GET_CONFIG, result, callbackId)
}) })
} }
const handleManageSaveConfig = () => { const handleManageSaveConfig = () => {
ipcMain.on(PICLIST_MANAGE_SAVE_CONFIG, (_event: IpcMainEvent, data: any) => { ipcMain.on(PICLIST_MANAGE_SAVE_CONFIG, (_: IpcMainEvent, data: any) => {
manageApi.saveConfig(data) manageApi.saveConfig(data)
}) })
} }
const handleManageRemoveConfig = () => { const handleManageRemoveConfig = () => {
ipcMain.on(PICLIST_MANAGE_REMOVE_CONFIG, (_event: IpcMainEvent, key: string, propName: string) => { ipcMain.on(PICLIST_MANAGE_REMOVE_CONFIG, (_: IpcMainEvent, key: string, propName: string) => {
manageApi.removeConfig(key, propName) manageApi.removeConfig(key, propName)
}) })
} }

View File

@ -1,33 +1,36 @@
import fs from 'fs-extra' import { ipcMain } from 'electron'
import path from 'path'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { managePathChecker } from './datastore/dbChecker' import fs from 'fs-extra'
import {
ManageApiType,
ManageConfigType,
ManageError,
PicBedMangeConfig
} from '~/universal/types/manage'
import ManageDB from './datastore/db'
import { ManageLogger } from './utils/logger'
import { get, set, unset } from 'lodash' import { get, set, unset } from 'lodash'
import { homedir } from 'os' import { homedir } from 'os'
import { isInputConfigValid, formatError } from './utils/common' import path from 'path'
import API from './apis/api'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum'
import { ipcMain } from 'electron'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
export class ManageApi extends EventEmitter implements ManageApiType { import windowManager from 'apis/app/window/windowManager'
private _config!: Partial<ManageConfigType>
import API from '~/manage/apis/api'
import ManageDB from '~/manage/datastore/db'
import { managePathChecker } from '~/manage/datastore/dbChecker'
import { isInputConfigValid, formatError } from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { IWindowList } from '#/types/enum'
import {
IManageApiType,
IManageConfigType,
IManageError,
IPicBedMangeConfig
} from '#/types/manage'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
export class ManageApi extends EventEmitter implements IManageApiType {
private _config!: Partial<IManageConfigType>
private db!: ManageDB private db!: ManageDB
currentPicBed: string currentPicBed: string
configPath: string configPath: string
baseDir!: string baseDir!: string
logger: ManageLogger logger: ManageLogger
currentPicBedConfig: PicBedMangeConfig currentPicBedConfig: IPicBedMangeConfig
constructor (currentPicBed: string = '') { constructor (currentPicBed: string = '') {
super() super()
@ -81,8 +84,8 @@ export class ManageApi extends EventEmitter implements ManageApiType {
} }
} }
private getPicBedConfig (picBedName: string): PicBedMangeConfig { private getPicBedConfig (picBedName: string): IPicBedMangeConfig {
return this.getConfig<PicBedMangeConfig>(`picBed.${picBedName}`) return this.getConfig<IPicBedMangeConfig>(`picBed.${picBedName}`)
} }
private initConfigPath (): void { private initConfigPath (): void {
@ -102,7 +105,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
private initconfig (): void { private initconfig (): void {
this.db = new ManageDB(this) this.db = new ManageDB(this)
this._config = this.db.read(true) as ManageConfigType this._config = this.db.read(true) as IManageConfigType
} }
getConfig<T> (name?: string): T { getConfig<T> (name?: string): T {
@ -190,14 +193,14 @@ export class ManageApi extends EventEmitter implements ManageApiType {
async getBucketInfo ( async getBucketInfo (
param?: IStringKeyMap | undefined param?: IStringKeyMap | undefined
): Promise<IStringKeyMap | ManageError> { ): Promise<IStringKeyMap | IManageError> {
console.log(param) console.log(param)
return {} return {}
} }
async getBucketDomain ( async getBucketDomain (
param: IStringKeyMap param: IStringKeyMap
): Promise<IStringKeyMap | ManageError> { ): Promise<IStringKeyMap | IManageError> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@ -230,6 +233,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
case 'tcyun': case 'tcyun':
case 'aliyun': case 'aliyun':
case 'qiniu': case 'qiniu':
case 's3plist':
try { try {
client = this.createClient() as any client = this.createClient() as any
return await client.createBucket(param!) return await client.createBucket(param!)
@ -251,7 +255,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
async getOperatorList ( async getOperatorList (
param?: IStringKeyMap param?: IStringKeyMap
): Promise<string[] | ManageError> { ): Promise<string[] | IManageError> {
console.log(param) console.log(param)
return [] return []
} }
@ -272,7 +276,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
async getBucketAclPolicy ( async getBucketAclPolicy (
param?: IStringKeyMap param?: IStringKeyMap
): Promise<IStringKeyMap | ManageError> { ): Promise<IStringKeyMap | IManageError> {
console.log(param) console.log(param)
return {} return {}
} }
@ -297,7 +301,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
async getBucketListRecursively ( async getBucketListRecursively (
param?: IStringKeyMap param?: IStringKeyMap
): Promise<IStringKeyMap | ManageError> { ): Promise<IStringKeyMap | IManageError> {
let client let client
let window let window
const defaultResult = { const defaultResult = {
@ -342,7 +346,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
*/ */
async getBucketListBackstage ( async getBucketListBackstage (
param?: IStringKeyMap param?: IStringKeyMap
): Promise<IStringKeyMap | ManageError> { ): Promise<IStringKeyMap | IManageError> {
let client let client
let window let window
const defaultResult = { const defaultResult = {
@ -391,7 +395,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
**/ **/
async getBucketFileList ( async getBucketFileList (
param?: IStringKeyMap param?: IStringKeyMap
): Promise<IStringKeyMap | ManageError> { ): Promise<IStringKeyMap | IManageError> {
const defaultResponse = { const defaultResponse = {
fullList: <any>[], fullList: <any>[],
isTruncated: false, isTruncated: false,

View File

@ -1,24 +1,22 @@
import fs from 'fs-extra'
import path from 'path'
import mime from 'mime-types'
import axios from 'axios' import axios from 'axios'
import { app } from 'electron'
import crypto from 'crypto' import crypto from 'crypto'
import { app } from 'electron'
import fs from 'fs-extra'
import got, { OptionsOfTextResponseBody, RequestError } from 'got' import got, { OptionsOfTextResponseBody, RequestError } from 'got'
import { Stream } from 'stream'
import { promisify } from 'util'
import UpDownTaskQueue,
{
uploadTaskSpecialStatus,
commonTaskStatus,
downloadTaskSpecialStatus
} from '../datastore/upDownTaskQueue'
import { ManageLogger } from '../utils/logger'
import { formatHttpProxy, IHTTPProxy } from '@/manage/utils/common'
import { HttpsProxyAgent, HttpProxyAgent } from 'hpagent' import { HttpsProxyAgent, HttpProxyAgent } from 'hpagent'
import http from 'http' import http from 'http'
import https from 'https' import https from 'https'
import mime from 'mime-types'
import Downloader from 'nodejs-file-downloader' import Downloader from 'nodejs-file-downloader'
import path from 'path'
import { Stream } from 'stream'
import { promisify } from 'util'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, downloadTaskSpecialStatus, uploadTaskSpecialStatus } from '#/types/enum'
import { formatHttpProxy } from '#/utils/common'
export const getFSFile = async ( export const getFSFile = async (
filePath: string, filePath: string,
@ -216,8 +214,6 @@ export const formatError = (err: any, params:IStringKeyMap) => {
return `${String(err)}${JSON.stringify(params)}` return `${String(err)}${JSON.stringify(params)}`
} }
export const trimPath = (path: string) => path.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/')
const commonOptions = { const commonOptions = {
keepAlive: true, keepAlive: true,
keepAliveMsecs: 1000, keepAliveMsecs: 1000,
@ -307,13 +303,6 @@ export function getOptions (
} }
} }
export const formatEndpoint = (endpoint: string, sslEnabled: boolean): string =>
!/^https?:\/\//.test(endpoint)
? `${sslEnabled ? 'https' : 'http'}://${endpoint}`
: sslEnabled
? endpoint.replace('http://', 'https://')
: endpoint.replace('https://', 'http://')
export class ConcurrencyPromisePool { export class ConcurrencyPromisePool {
limit: number limit: number
queue: any[] queue: any[]

View File

@ -1,6 +1,7 @@
import axios from 'axios' import axios from 'axios'
import crypto from 'crypto' import crypto from 'crypto'
import querystring from 'querystring' import querystring from 'querystring'
import picgo from '@core/picgo' import picgo from '@core/picgo'
export interface DogecloudToken { export interface DogecloudToken {

View File

@ -2,12 +2,13 @@ import chalk from 'chalk'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path' import path from 'path'
import util from 'util'
import { ILogType } from '#/types/enum'
import { ILogColor, ILogger } from 'piclist/dist/types' import { ILogColor, ILogger } from 'piclist/dist/types'
import { ManageApiType, Undefinable } from '~/universal/types/manage' import util from 'util'
import { ILogType } from '#/types/enum'
import { IManageApiType, Undefinable } from '#/types/manage'
import { enforceNumber, isDev } from '#/utils/common' import { enforceNumber, isDev } from '#/utils/common'
import { configPaths } from '~/universal/utils/configPaths' import { configPaths } from '#/utils/configPaths'
export class ManageLogger implements ILogger { export class ManageLogger implements ILogger {
readonly #level = { readonly #level = {
@ -17,11 +18,11 @@ export class ManageLogger implements ILogger {
[ILogType.error]: 'red' [ILogType.error]: 'red'
} }
readonly #ctx: ManageApiType readonly #ctx: IManageApiType
#logLevel!: string #logLevel!: string
#logPath!: string #logPath!: string
constructor (ctx: ManageApiType) { constructor (ctx: IManageApiType) {
this.#ctx = ctx this.#ctx = ctx
} }

View File

@ -1,17 +1,20 @@
import axios from 'axios'
import { app } from 'electron'
import fs from 'fs-extra'
import http from 'http' import http from 'http'
import routers from './routerManager' import multer from 'multer'
import path from 'path'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import routers from '~/server/routerManager'
import { import {
handleResponse, handleResponse,
ensureHTTPLink ensureHTTPLink
} from './utils' } from '~/server/utils'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger' import { configPaths } from '#/utils/configPaths'
import axios from 'axios'
import multer from 'multer'
import { app } from 'electron'
import path from 'path'
import fs from 'fs-extra'
import { configPaths } from '~/universal/utils/configPaths'
const DEFAULT_PORT = 36677 const DEFAULT_PORT = 36677
const DEFAULT_HOST = '0.0.0.0' const DEFAULT_HOST = '0.0.0.0'

View File

@ -1,21 +1,27 @@
import router from './router'
import {
handleResponse
} from './utils'
import logger from '@core/picgo/logger'
import windowManager from 'apis/app/window/windowManager'
import { uploadChoosedFiles, uploadClipboardFiles, deleteChoosedFiles } from 'apis/app/uploader/apis'
import path from 'path'
import { dbPathDir } from 'apis/core/datastore/dbChecker'
import picgo from '@core/picgo'
import { changeCurrentUploader } from '../utils/handleUploaderConfig'
import { app } from 'electron' import { app } from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import { AESHelper } from '../utils/aesHelper'
import { marked } from 'marked'
import { markdownContent } from './apiDoc'
import http from 'http' import http from 'http'
import { configPaths } from '~/universal/utils/configPaths' import { marked } from 'marked'
import path from 'path'
import { dbPathDir } from '@core/datastore/dbChecker'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import { AESHelper } from '~/utils/aesHelper'
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import { markdownContent } from '~/server/apiDoc'
import router from '~/server/router'
import {
deleteChoosedFiles,
handleResponse
} from '~/server/utils'
import { configPaths } from '#/utils/configPaths'
const appPath = app.getPath('userData') const appPath = app.getPath('userData')
const serverTempDir = path.join(appPath, 'serverTemp') const serverTempDir = path.join(appPath, 'serverTemp')

View File

@ -1,4 +1,20 @@
import {
Notification
} from 'electron'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger' import logger from '@core/picgo/logger'
import db, { GalleryDB } from '@core/datastore'
import windowManager from 'apis/app/window/windowManager'
import GuiApi from '~/apis/gui'
import { T } from '~/i18n/index'
import { configPaths } from '#/utils/configPaths'
import { picBedsCanbeDeleted } from '#/utils/static'
import { ICOREBuildInEvent, IWindowList } from '#/types/enum'
import ALLApi from '@/apis/allApi'
export const handleResponse = ({ export const handleResponse = ({
response, response,
@ -31,3 +47,43 @@ export const ensureHTTPLink = (url: string): string => {
? url ? url
: `http://${url}` : `http://${url}`
} }
export const deleteChoosedFiles = async (list: ImgInfo[]): Promise<boolean[]> => {
const result = []
for (const item of list) {
if (item.id) {
try {
const dbStore = GalleryDB.getInstance()
const file = await dbStore.getById(item.id)
await dbStore.removeById(item.id)
if (await db.get(configPaths.settings.deleteCloudFile)) {
if (item.type !== undefined && picBedsCanbeDeleted.includes(item.type)) {
const noteFunc = (value: boolean) => {
const notification = new Notification({
title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'),
body: T(value
? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED'
: 'GALLERY_SYNC_DELETE_NOTICE_FAILED'
)
})
notification.show()
}
setTimeout(() => {
ALLApi.delete(item).then(noteFunc)
}, 0)
}
}
setTimeout(() => {
picgo.emit(ICOREBuildInEvent.REMOVE, [file], GuiApi.getInstance())
}, 500)
result.push(true)
} catch (e) {
result.push(false)
}
}
}
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
}
return result
}

View File

@ -1,10 +1,12 @@
import http from 'http'
import fs from 'fs-extra' import fs from 'fs-extra'
import http from 'http'
import path from 'path' import path from 'path'
import picgo from '@core/picgo' import picgo from '@core/picgo'
import logger from '../../apis/core/picgo/logger' import logger from '@core/picgo/logger'
import { encodeFilePath } from '~/universal/utils/common'
import { configPaths } from '~/universal/utils/configPaths' import { encodeFilePath } from '#/utils/common'
import { configPaths } from '#/utils/configPaths'
const defaultPath = process.platform === 'win32' ? 'C:\\Users' : '/' const defaultPath = process.platform === 'win32' ? 'C:\\Users' : '/'

View File

@ -1,7 +1,9 @@
import crypto from 'crypto' import crypto from 'crypto'
import picgo from '@core/picgo' import picgo from '@core/picgo'
import { DEFAULT_AES_PASSWORD } from '~/universal/utils/static'
import { configPaths } from '~/universal/utils/configPaths' import { configPaths } from '#/utils/configPaths'
import { DEFAULT_AES_PASSWORD } from '#/utils/static'
export class AESHelper { export class AESHelper {
key: Buffer key: Buffer

View File

@ -1,9 +1,11 @@
import fs from 'fs-extra' import fs from 'fs-extra'
import yaml from 'js-yaml'
import path from 'path' import path from 'path'
import os from 'os' import os from 'os'
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
import yaml from 'js-yaml' import { dbPathChecker } from '@core/datastore/dbChecker'
import { i18nManager } from '~/main/i18n'
import { i18nManager } from '~/i18n'
const configPath = dbPathChecker() const configPath = dbPathChecker()
const CONFIG_DIR = path.dirname(configPath) const CONFIG_DIR = path.dirname(configPath)

View File

@ -1,7 +1,8 @@
import crypto from 'crypto'
import { clipboard } from 'electron' import { clipboard } from 'electron'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import crypto from 'crypto'
import logger from '../apis/core/picgo/logger' import logger from '@core/picgo/logger'
class ClipboardWatcher extends EventEmitter { class ClipboardWatcher extends EventEmitter {
timer: NodeJS.Timeout | null timer: NodeJS.Timeout | null

View File

@ -1,12 +1,26 @@
import fs from 'fs-extra'
import db from '~/main/apis/core/datastore'
import { clipboard, Notification, dialog } from 'electron'
import { handleUrlEncode } from '~/universal/utils/common'
import axios from 'axios' import axios from 'axios'
import { clipboard, Notification, dialog, Tray } from 'electron'
import FormData from 'form-data' import FormData from 'form-data'
import logger from '../apis/core/picgo/logger' import fs from 'fs-extra'
import { configPaths } from '~/universal/utils/configPaths'
import { IShortUrlServer } from '~/universal/types/enum' import db from '@core/datastore'
import logger from '@core/picgo/logger'
import { IShortUrlServer } from '#/types/enum'
import { handleUrlEncode } from '#/utils/common'
import { configPaths } from '#/utils/configPaths'
export let tray: Tray
export const setTray = (t: Tray) => { tray = t }
export const getTray = () => tray
export function setTrayToolTip (title: string): void {
if (tray) {
tray.setToolTip(title)
}
}
export const handleCopyUrl = (str: string): void => { export const handleCopyUrl = (str: string): void => {
if (db.get(configPaths.settings.autoCopy) !== false) { if (db.get(configPaths.settings.autoCopy) !== false) {

View File

@ -1,11 +1,15 @@
import { S3Client, DeleteObjectCommand, S3ClientConfig } from '@aws-sdk/client-s3'
import { NodeHttpHandler } from '@smithy/node-http-handler'
import http, { AgentOptions } from 'http'
import https from 'https'
import { getAgent } from '../manage/utils/common'
import axios from 'axios' import axios from 'axios'
import crypto from 'crypto' import crypto from 'crypto'
import http, { AgentOptions } from 'http'
import https from 'https'
import path from 'path'
import { ISftpPlistConfig } from 'piclist'
import querystring from 'querystring' import querystring from 'querystring'
import { S3Client, DeleteObjectCommand, S3ClientConfig } from '@aws-sdk/client-s3'
import { NodeHttpHandler } from '@smithy/node-http-handler'
import SSHClient from '~/utils/sshClient'
import { getAgent } from '~/manage/utils/common'
interface DogecloudTokenFull { interface DogecloudTokenFull {
Credentials: { Credentials: {
@ -218,3 +222,18 @@ export async function removeFileFromHuaweiInMain (configMap: IStringKeyMap) {
return false return false
} }
} }
export async function removeFileFromSFTPInMain (config: ISftpPlistConfig, fileName: string) {
try {
const client = SSHClient.instance
await client.connect(config)
const uploadPath = `/${(config.uploadPath || '')}/`.replace(/\/+/g, '/')
const remote = path.join(uploadPath, fileName)
const deleteResult = await client.deleteFileSFTP(config, remote)
client.close()
return deleteResult
} catch (err: any) {
console.log(err)
return false
}
}

View File

@ -1,7 +1,8 @@
// fork from https://github.com/sindresorhus/macos-version // fork from https://github.com/sindresorhus/macos-version
// cause I can't change it to common-js module // cause I can't change it to common-js module
import process from 'process'
import fs from 'fs' import fs from 'fs'
import process from 'process'
import semver from 'semver' import semver from 'semver'
export const isMacOS = process.platform === 'darwin' export const isMacOS = process.platform === 'darwin'

View File

@ -1,5 +1,5 @@
import picgo from '@core/picgo' import picgo from '@core/picgo'
import { configPaths } from '~/universal/utils/configPaths' import { configPaths } from '#/utils/configPaths'
const getPicBeds = () => { const getPicBeds = () => {
const picBedTypes = picgo.helper.uploader.getIdList() const picBedTypes = picgo.helper.uploader.getIdList()

View File

@ -1,7 +1,9 @@
import path from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path'
import { Logger } from 'piclist' import { Logger } from 'piclist'
import { isUrl } from '~/universal/utils/common'
import { isUrl } from '#/utils/common'
interface IResultFileObject { interface IResultFileObject {
path: string path: string
} }

View File

@ -1,7 +1,9 @@
import db from '~/main/apis/core/datastore' import db from '@core/datastore'
import { i18nManager } from '~/main/i18n'
import { II18nLanguage } from '~/universal/types/enum' import { i18nManager } from '~/i18n'
import { configPaths } from '~/universal/utils/configPaths'
import { II18nLanguage } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
export const initI18n = () => { export const initI18n = () => {
const currentLanguage = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN const currentLanguage = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN

View File

@ -1,7 +1,11 @@
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { trimValues } from '#/utils/common'
import picgo from '@core/picgo' import picgo from '@core/picgo'
import { configPaths } from '~/universal/utils/configPaths'
import { setTrayToolTip } from '~/utils/common'
import { trimValues } from '#/utils/common'
import { configPaths } from '#/utils/configPaths'
export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IPicGoPluginConfig[] => { export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IPicGoPluginConfig[] => {
for (const i in config) { for (const i in config) {
@ -65,6 +69,7 @@ export const changeCurrentUploader = (type: string, config?: IStringKeyMap, id?:
[configPaths.picBed.current]: type, [configPaths.picBed.current]: type,
[configPaths.picBed.uploader]: type [configPaths.picBed.uploader]: type
}) })
setTrayToolTip(`${type} ${config?._configName || ''}`)
} }
export const selectUploaderConfig = (type: string, id: string) => { export const selectUploaderConfig = (type: string, id: string) => {

View File

@ -1,8 +1,9 @@
import db from '@core/datastore'
import { generateShortUrl, handleUrlEncodeWithSetting } from '~/utils/common'
import { IPasteStyle } from '#/types/enum' import { IPasteStyle } from '#/types/enum'
import { generateShortUrl } from '~/main/utils/common' import { configPaths } from '#/utils/configPaths'
import db from '~/main/apis/core/datastore'
import { handleUrlEncodeWithSetting } from './common'
import { configPaths } from '~/universal/utils/configPaths'
export const formatCustomLink = (customLink: string, item: ImgInfo) => { export const formatCustomLink = (customLink: string, item: ImgInfo) => {
const fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '') const fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')

View File

@ -1,9 +1,9 @@
// @ts-nocheck // @ts-nocheck
import fs from 'fs-extra'
import { NodeSSH, Config, SSHExecCommandResponse } from 'node-ssh-no-cpu-features' import { NodeSSH, Config, SSHExecCommandResponse } from 'node-ssh-no-cpu-features'
import path from 'path' import path from 'path'
import { ISftpPlistConfig } from 'piclist/dist/types' import { ISftpPlistConfig } from 'piclist/dist/types'
import { Client } from 'ssh2-no-cpu-features' import { Client } from 'ssh2-no-cpu-features'
import fs from 'fs-extra'
class SSHClient { class SSHClient {
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define

View File

@ -1,12 +1,14 @@
import axios from 'axios'
import { app } from 'electron' import { app } from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path'
import axios from 'axios'
import db from '~/main/apis/core/datastore'
import { HttpsProxyAgent } from 'hpagent' import { HttpsProxyAgent } from 'hpagent'
import path from 'path'
import { Octokit } from '@octokit/rest' import { Octokit } from '@octokit/rest'
import logger from 'apis/core/picgo/logger'
import { configPaths } from '~/universal/utils/configPaths' import db from '@core/datastore'
import logger from '@core/picgo/logger'
import { configPaths } from '#/utils/configPaths'
const STORE_PATH = app.getPath('userData') const STORE_PATH = app.getPath('userData')

View File

@ -1,6 +1,8 @@
import db from '~/main/apis/core/datastore'
import { autoUpdater } from 'electron-updater' import { autoUpdater } from 'electron-updater'
import { configPaths } from '~/universal/utils/configPaths'
import db from '@core/datastore'
import { configPaths } from '#/utils/configPaths'
const updateChecker = async () => { const updateChecker = async () => {
let showTip = db.get(configPaths.settings.showUpdateTip) let showTip = db.get(configPaths.settings.showUpdateTip)

View File

@ -0,0 +1,56 @@
import { screen } from 'electron'
import db from '@core/datastore'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
export function openMiniWindow (hideSettingWindow:boolean = true) {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
miniWindow.removeAllListeners()
if (db.get(configPaths.settings.miniWindowOntop)) {
miniWindow.setAlwaysOnTop(true)
}
const { width, height } = screen.getPrimaryDisplay().workAreaSize
const lastPosition = db.get(configPaths.settings.miniWindowPosition)
if (lastPosition) {
miniWindow.setPosition(lastPosition[0], lastPosition[1])
} else {
miniWindow.setPosition(width - 100, height - 100)
}
const setPositionFunc = () => {
const position = miniWindow.getPosition()
db.set(configPaths.settings.miniWindowPosition, position)
}
miniWindow.on('close', setPositionFunc)
miniWindow.on('move', setPositionFunc)
miniWindow.show()
miniWindow.focus()
if (hideSettingWindow) {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
settingWindow.hide()
} else {
const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false
if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) {
windowManager.get(IWindowList.SETTING_WINDOW)!.hide()
}
}
}
export const openMainWindow = () => {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false
settingWindow!.show()
settingWindow!.focus()
if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
export const hideMiniWindow = () => {
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}

View File

@ -1,29 +1,24 @@
<template> <template>
<div id="app"> <div
id="app"
:key="pageReloadCount"
>
<router-view /> <router-view />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
//
import { useStore } from '@/hooks/useStore'
// Vue
import { onBeforeMount, onMounted, onUnmounted } from 'vue'
//
import { getConfig } from './utils/dataSender'
//
import type { IConfig } from 'piclist' import type { IConfig } from 'piclist'
import { onBeforeMount } from 'vue'
// import { useStore } from '@/hooks/useStore'
import bus from './utils/bus' import { useATagClick } from '@/hooks/useATagClick'
import { FORCE_UPDATE } from '~/universal/events/constants' import { getConfig } from '@/utils/dataSender'
import { useATagClick } from './hooks/useATagClick' import { pageReloadCount } from '@/utils/global'
useATagClick() useATagClick()
const store = useStore() const store = useStore()
onBeforeMount(async () => { onBeforeMount(async () => {
const config = await getConfig<IConfig>() const config = await getConfig<IConfig>()
if (config) { if (config) {
@ -31,16 +26,6 @@ onBeforeMount(async () => {
} }
}) })
onMounted(() => {
bus.on(FORCE_UPDATE, () => {
store?.updateForceUpdateTime()
})
})
onUnmounted(() => {
bus.off(FORCE_UPDATE)
})
</script> </script>
<script lang="ts"> <script lang="ts">

View File

@ -1,7 +1,8 @@
import { deleteFailedLog, deleteLog } from '@/utils/common'
import axios from 'axios' import axios from 'axios'
import path from 'path' import path from 'path'
import { deleteFailedLog, deleteLog } from '#/utils/deleteLog'
interface IConfigMap { interface IConfigMap {
fileName: string fileName: string
config: { config: {

View File

@ -1,6 +1,7 @@
import { deleteFailedLog, deleteLog } from '@/utils/common'
import OSS from 'ali-oss' import OSS from 'ali-oss'
import { deleteFailedLog, deleteLog } from '#/utils/deleteLog'
interface IConfigMap { interface IConfigMap {
fileName: string fileName: string
config: PartialKeys<IAliYunConfig, 'path'> config: PartialKeys<IAliYunConfig, 'path'>

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