mirror of
https://github.com/Molunerfinn/PicGo.git
synced 2024-04-21 10:31:33 +00:00
Compare commits
81
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3ec0cb4be | ||
|
|
5dd6e72b50 | ||
|
|
5cb8151df7 | ||
|
|
1848bff091 | ||
|
|
56e814a96a | ||
|
|
b93b4cffe2 | ||
|
|
f2a4197ca2 | ||
|
|
34b3656605 | ||
|
|
ea20d3b971 | ||
|
|
459953f391 | ||
|
|
75e3edcd87 | ||
|
|
5895889059 | ||
|
|
58420c8c3b | ||
|
|
6c6f84779a | ||
|
|
a676c083fe | ||
|
|
2d3e779e0b | ||
|
|
20d3cf987c | ||
|
|
ae692632a5 | ||
|
|
019efd354c | ||
|
|
7030f7a764 | ||
|
|
96a63ea11a | ||
|
|
f1eb7f4d70 | ||
|
|
6ddd660d89 | ||
|
|
c70c3aff78 | ||
|
|
76964ff1a5 | ||
|
|
12cecc27e7 | ||
|
|
bdf523a060 | ||
|
|
49e5f343f4 | ||
|
|
1657542144 | ||
|
|
8e5e9ec59a | ||
|
|
06b67e50b9 | ||
|
|
cf895deb8d | ||
|
|
ab762ef465 | ||
|
|
92022a6e34 | ||
|
|
a312d06b62 | ||
|
|
a1b70b4cb1 | ||
|
|
2d9e9c0a00 | ||
|
|
a657c51bb5 | ||
|
|
321e33967c | ||
|
|
0a986c8933 | ||
|
|
69e1dc8128 | ||
|
|
b05139fc9a | ||
|
|
992ff35936 | ||
|
|
06d40ef7f6 | ||
|
|
f53639153e | ||
|
|
3fd6e4e4c8 | ||
|
|
ce8f41516c | ||
|
|
f0e1fa12d7 | ||
|
|
3cad5f378d | ||
|
|
f9d6191d5e | ||
|
|
51d80a6a1d | ||
|
|
0fe3ade5c3 | ||
|
|
2129f86266 | ||
|
|
064f37d1a6 | ||
|
|
48592ec9b5 | ||
|
|
33fdb16ded | ||
|
|
bd2311bf50 | ||
|
|
e793599360 | ||
|
|
ce2b5cdeab | ||
|
|
2151857c89 | ||
|
|
32b8b976be | ||
|
|
eab014dc88 | ||
|
|
bb90e174f3 | ||
|
|
7b5e5ef2ed | ||
|
|
92ff282d2f | ||
|
|
2d42381f47 | ||
|
|
6e855384e0 | ||
|
|
21d3942d2b | ||
|
|
4f0809e6b3 | ||
|
|
f40a1bb981 | ||
|
|
5f87018af8 | ||
|
|
7fabc47bd2 | ||
|
|
db71139f5f | ||
|
|
1c5880ab7c | ||
|
|
b93dd05fd3 | ||
|
|
59d3eba86d | ||
|
|
a693544291 | ||
|
|
2c57a27284 | ||
|
|
b6e3adb7af | ||
|
|
2adff1e788 | ||
|
|
18ad54251e |
+19
-5
@@ -6,18 +6,32 @@ module.exports = {
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
parser: "vue-eslint-parser",
|
||||
'extends': [
|
||||
parser: 'vue-eslint-parser',
|
||||
extends: [
|
||||
'plugin:vue/essential',
|
||||
'@vue/standard',
|
||||
'@vue/typescript'
|
||||
],
|
||||
'plugins': ['@typescript-eslint'],
|
||||
plugins: ['@typescript-eslint'],
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
indent: 'off',
|
||||
'no-async-promise-executor': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'@typescript-eslint/indent': ['error', 2]
|
||||
},
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.vue'],
|
||||
rules: {
|
||||
'no-undef': 'off' // https://typescript-eslint.io/docs/linting/troubleshooting/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
}
|
||||
}
|
||||
],
|
||||
ignorePatterns: ['src/**/*.d.ts']
|
||||
}
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
<!--
|
||||
PicGo Issue 模板
|
||||
请依照该模板来提交,否则将会被关闭。
|
||||
**提问之前请注意你看过FAQ、配置手册以及那些被关闭的issues。否则同样的提问也会被关闭!**
|
||||
**提问之前请注意你看过 FAQ、配置手册以及那些被关闭的 issues。否则同样的提问也会被关闭!**
|
||||
-->
|
||||
|
||||
**声明:我已经仔细看过 [文档](https://picgo.github.io/PicGo-Doc/)、[FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md)、和搜索过已经关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed) 后依然没有找到答案,所以才发了一个新的 issue。**
|
||||
|
||||
## 问题类型
|
||||
|
||||
<!-- 你要提交的是Bug Report还是Feature Request?-->
|
||||
<!-- 你要提交的是 Bug Report 还是 Feature Request?-->
|
||||
|
||||
## PicGo的相关信息
|
||||
## PicGo 的相关信息
|
||||
|
||||
<!-- 版本、所在平台(Mac或者Windows) -->
|
||||
<!-- PicGo 的版本、所在平台(Mac 或者 Windows 或者 Linux) -->
|
||||
|
||||
## 问题重现
|
||||
|
||||
<!-- 如果是Bug Report请填写本项 -->
|
||||
<!-- 如果是 Bug Report 请填写本项 -->
|
||||
<!-- 请附上相关截图 -->
|
||||
<!-- 请附上 PicGo 的相关报错日志(用文本的形式)否则无法判断原因-->
|
||||
<!-- 报错日志可以在 PicGo 设置 -> 设置日志文件 -> 点击打开 后找到 -->
|
||||
|
||||
## 功能请求
|
||||
|
||||
<!-- 如果是Feature Request请填写本项 -->
|
||||
<!-- 如果是 Feature Request 请填写本项 -->
|
||||
<!-- 详细描述你所预想的功能或者是现有功能的改进。 -->
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
最后,喜欢PicGo的话不妨给它点个star~
|
||||
最后,喜欢 PicGo 的话不妨给它点个 star~
|
||||
如果可以的话,请我喝杯咖啡?首页有赞助二维码,谢谢你的支持!
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# main.yml
|
||||
|
||||
# Workflow's name
|
||||
name: Build
|
||||
|
||||
# Workflow's trigger
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
# Workflow's jobs
|
||||
jobs:
|
||||
# job's id
|
||||
release:
|
||||
# job's name
|
||||
name: build and release electron app
|
||||
|
||||
# the type of machine to run the job on
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# create a build matrix for jobs
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-11]
|
||||
|
||||
# create steps
|
||||
steps:
|
||||
# step1: check out repository
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# step2: install node env
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: Install system deps
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils
|
||||
|
||||
# step3: yarn
|
||||
- name: Yarn install
|
||||
run: |
|
||||
yarn
|
||||
yarn global add xvfb-maybe
|
||||
|
||||
- name: Build & release app
|
||||
run: |
|
||||
yarn release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
@@ -0,0 +1,52 @@
|
||||
# main.yml
|
||||
|
||||
# Workflow's name
|
||||
name: Build
|
||||
|
||||
# Workflow's trigger
|
||||
on: workflow_dispatch
|
||||
|
||||
# Workflow's jobs
|
||||
jobs:
|
||||
# job's id
|
||||
release:
|
||||
# job's name
|
||||
name: build and release electron app
|
||||
|
||||
# the type of machine to run the job on
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# create a build matrix for jobs
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-11]
|
||||
|
||||
# create steps
|
||||
steps:
|
||||
# step1: check out repository
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# step2: install node env
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: Install system deps
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils
|
||||
|
||||
# step3: yarn
|
||||
- name: Yarn install
|
||||
run: |
|
||||
yarn
|
||||
yarn global add xvfb-maybe
|
||||
|
||||
- name: Build & release app
|
||||
run: |
|
||||
yarn release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
+209
@@ -1,3 +1,212 @@
|
||||
## :tada: 2.3.1-beta.2 (2022-01-06)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* electron builder actions bug ([5dd6e72](https://github.com/Molunerfinn/PicGo/commit/5dd6e72))
|
||||
* linux github actions workflow bug ([5cb8151](https://github.com/Molunerfinn/PicGo/commit/5cb8151))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.3.1-beta.1 (2022-01-05)
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* update ci build scripts ([56e814a](https://github.com/Molunerfinn/PicGo/commit/56e814a))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.3.1-beta.0 (2022-01-05)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* mini window drag bug ([34b3656](https://github.com/Molunerfinn/PicGo/commit/34b3656))
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* add mac-arm64 build support ([f2a4197](https://github.com/Molunerfinn/PicGo/commit/f2a4197))
|
||||
* update electron from v6 -> v16 ([ea20d3b](https://github.com/Molunerfinn/PicGo/commit/ea20d3b))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.3.0 (2021-09-11)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add open devtool option ([75e3edc](https://github.com/Molunerfinn/PicGo/commit/75e3edc))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* shift key function in gallery page ([5895889](https://github.com/Molunerfinn/PicGo/commit/5895889))
|
||||
* some bugs ([a676c08](https://github.com/Molunerfinn/PicGo/commit/a676c08)), closes [#722](https://github.com/Molunerfinn/PicGo/issues/722)
|
||||
* urlEncode bug when copy ([6c6f847](https://github.com/Molunerfinn/PicGo/commit/6c6f847)), closes [#731](https://github.com/Molunerfinn/PicGo/issues/731)
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* update FAQ ([58420c8](https://github.com/Molunerfinn/PicGo/commit/58420c8))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.3.0-beta.8 (2021-08-13)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* settings bug ([20d3cf9](https://github.com/Molunerfinn/PicGo/commit/20d3cf9)), closes [#710](https://github.com/Molunerfinn/PicGo/issues/710)
|
||||
* upload clipboard images via http should return list ([ae69263](https://github.com/Molunerfinn/PicGo/commit/ae69263)), closes [#721](https://github.com/Molunerfinn/PicGo/issues/721)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.3.0-beta.7 (2021-08-01)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add gallery db ([6ddd660](https://github.com/Molunerfinn/PicGo/commit/6ddd660))
|
||||
* add win32 support ([1657542](https://github.com/Molunerfinn/PicGo/commit/1657542)), closes [#632](https://github.com/Molunerfinn/PicGo/issues/632)
|
||||
* finish custom config path ([7030f7a](https://github.com/Molunerfinn/PicGo/commit/7030f7a)), closes [#255](https://github.com/Molunerfinn/PicGo/issues/255)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* bug of gallery db for plugin ([96a63ea](https://github.com/Molunerfinn/PicGo/commit/96a63ea))
|
||||
* enable plugin should reload ([49e5f34](https://github.com/Molunerfinn/PicGo/commit/49e5f34)), closes [#659](https://github.com/Molunerfinn/PicGo/issues/659)
|
||||
* gallery db bug ([f1eb7f4](https://github.com/Molunerfinn/PicGo/commit/f1eb7f4))
|
||||
* multiple uploading in the same time will cause output conflict ([06b67e5](https://github.com/Molunerfinn/PicGo/commit/06b67e5)), closes [#666](https://github.com/Molunerfinn/PicGo/issues/666)
|
||||
* multiple uploading in the same time will cause rename failed ([12cecc2](https://github.com/Molunerfinn/PicGo/commit/12cecc2))
|
||||
* uploader error in linux ([ab762ef](https://github.com/Molunerfinn/PicGo/commit/ab762ef)), closes [#627](https://github.com/Molunerfinn/PicGo/issues/627)
|
||||
* use uploader first ([92022a6](https://github.com/Molunerfinn/PicGo/commit/92022a6))
|
||||
* windows ia32 && x64 build options ([bdf523a](https://github.com/Molunerfinn/PicGo/commit/bdf523a))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.3.0-beta.6 (2021-04-24)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add baidu tongji for analytics ([f536391](https://github.com/Molunerfinn/PicGo/commit/f536391))
|
||||
* add logs for picgo-server ([2d9e9c0](https://github.com/Molunerfinn/PicGo/commit/2d9e9c0)), closes [#627](https://github.com/Molunerfinn/PicGo/issues/627)
|
||||
* add privacy policy ([992ff35](https://github.com/Molunerfinn/PicGo/commit/992ff35))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* add plugin install failed notice ([b05139f](https://github.com/Molunerfinn/PicGo/commit/b05139f))
|
||||
* default picBed using picBed.uploader instead of picBed.current ([0a986c8](https://github.com/Molunerfinn/PicGo/commit/0a986c8))
|
||||
* dev error with install vue-devtools ([a657c51](https://github.com/Molunerfinn/PicGo/commit/a657c51)), closes [#653](https://github.com/Molunerfinn/PicGo/issues/653) [#658](https://github.com/Molunerfinn/PicGo/issues/658)
|
||||
* disable plugin need reload app ([a1b70b4](https://github.com/Molunerfinn/PicGo/commit/a1b70b4))
|
||||
* fix analytics value ([06d40ef](https://github.com/Molunerfinn/PicGo/commit/06d40ef))
|
||||
* windows cli uploading bug ([321e339](https://github.com/Molunerfinn/PicGo/commit/321e339)), closes [#657](https://github.com/Molunerfinn/PicGo/issues/657)
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* add main process hot reload ([3fd6e4e](https://github.com/Molunerfinn/PicGo/commit/3fd6e4e))
|
||||
* fix eslint error notice in indent ([69e1dc8](https://github.com/Molunerfinn/PicGo/commit/69e1dc8))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.3.0-beta.5 (2021-04-04)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add local plugin support && npm registry/proxy support ([f0e1fa1](https://github.com/Molunerfinn/PicGo/commit/f0e1fa1))
|
||||
* 为Linux系统适配桌面图标栏(Tray) ([#603](https://github.com/Molunerfinn/PicGo/issues/603)) ([0fe3ade](https://github.com/Molunerfinn/PicGo/commit/0fe3ade))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* default github placeholder ([51d80a6](https://github.com/Molunerfinn/PicGo/commit/51d80a6))
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* change travis-ci -> GitHub Actions ([064f37d](https://github.com/Molunerfinn/PicGo/commit/064f37d))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.3.0-beta.4 (2020-12-19)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add global value for PicGo get GUI_VERSION & CORE_VERSION ([eab014d](https://github.com/Molunerfinn/PicGo/commit/eab014d))
|
||||
* **config:** auto configuration backup & fallback to avoid main process crash ([32b8b97](https://github.com/Molunerfinn/PicGo/commit/32b8b97)), closes [#568](https://github.com/Molunerfinn/PicGo/issues/568)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* disabled plugin won't be shown in plugin page ([33fdb16](https://github.com/Molunerfinn/PicGo/commit/33fdb16))
|
||||
* shortKeyConfig maybe undefined ([7b5e5ef](https://github.com/Molunerfinn/PicGo/commit/7b5e5ef)), closes [#557](https://github.com/Molunerfinn/PicGo/issues/557)
|
||||
* url encode before uploading by url ([ce2b5cd](https://github.com/Molunerfinn/PicGo/commit/ce2b5cd)), closes [#581](https://github.com/Molunerfinn/PicGo/issues/581)
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* add flutter-picgo ([92ff282](https://github.com/Molunerfinn/PicGo/commit/92ff282))
|
||||
* add gitads ([2d42381](https://github.com/Molunerfinn/PicGo/commit/2d42381))
|
||||
* rm gitads ([bb90e17](https://github.com/Molunerfinn/PicGo/commit/bb90e17))
|
||||
* update discussions in doc ([e793599](https://github.com/Molunerfinn/PicGo/commit/e793599))
|
||||
* update install command by Homebrew ([#599](https://github.com/Molunerfinn/PicGo/issues/599)) ([bd2311b](https://github.com/Molunerfinn/PicGo/commit/bd2311b))
|
||||
* update README.md ([#555](https://github.com/Molunerfinn/PicGo/issues/555)) ([2151857](https://github.com/Molunerfinn/PicGo/commit/2151857))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.3.0-beta.3 (2020-07-12)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* choose default picBed failure ([21d3942](https://github.com/Molunerfinn/PicGo/commit/21d3942)), closes [#537](https://github.com/Molunerfinn/PicGo/issues/537)
|
||||
* shortkey disabled failure ([4f0809e](https://github.com/Molunerfinn/PicGo/commit/4f0809e)), closes [#534](https://github.com/Molunerfinn/PicGo/issues/534)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.3.0-beta.2 (2020-07-12)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add qrcode for picbeds' config ([7fabc47](https://github.com/Molunerfinn/PicGo/commit/7fabc47))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* encoding the result of picgo-server ([db71139](https://github.com/Molunerfinn/PicGo/commit/db71139))
|
||||
* initialize db bugs ([5f87018](https://github.com/Molunerfinn/PicGo/commit/5f87018))
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* update readme ([1c5880a](https://github.com/Molunerfinn/PicGo/commit/1c5880a))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.3.0-beta.1 (2020-06-28)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* auto-copy option && copy style ([b6e3adb](https://github.com/Molunerfinn/PicGo/commit/b6e3adb))
|
||||
* beta version update bug ([18ad542](https://github.com/Molunerfinn/PicGo/commit/18ad542))
|
||||
* paste url encoding bug ([59d3eba](https://github.com/Molunerfinn/PicGo/commit/59d3eba)), closes [#454](https://github.com/Molunerfinn/PicGo/issues/454)
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* update FAQ && ISSUE_TEMPLATE ([2c57a27](https://github.com/Molunerfinn/PicGo/commit/2c57a27))
|
||||
* update readme ([2adff1e](https://github.com/Molunerfinn/PicGo/commit/2adff1e))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.3.0-beta.0 (2020-04-30)
|
||||
|
||||
|
||||
|
||||
@@ -1,58 +1,63 @@
|
||||
## 常见问题
|
||||
|
||||
> 在使用PicGo期间你会遇到很多问题,不过很多问题其实之前就有人提问过,也被解决,所以你可以先看看[使用文档](https://github.com/Molunerfinn/PicGo/wiki/%E8%AF%A6%E7%BB%86%E7%AA%97%E5%8F%A3%E7%9A%84%E4%BD%BF%E7%94%A8#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%BF%AB%E6%8D%B7%E9%94%AE),这份FAQ,以及那些被关闭的[issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed),应该能找到答案。
|
||||
> 在使用 PicGo 期间你会遇到很多问题,不过很多问题其实之前就有人提问过,也被解决,所以你可以先看看 [使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B),这份 FAQ,以及那些被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed),应该能找到答案。
|
||||
|
||||
## 1.七牛图床上传图片成功后无法显示或图片无`http://`前缀
|
||||
## 1. 七牛图床上传图片成功后,相册里无法显示或图片无`http://`前缀
|
||||
|
||||
通常是你的七牛图床配置里的`设定访问网址`没有加上`http://`或者`https//`头。
|
||||
|
||||
参考:[issue#79](https://github.com/Molunerfinn/PicGo/issues/79)
|
||||
|
||||
## 2.能否支持图床远端同步删除
|
||||
## 2. 能否支持图床远端同步删除
|
||||
|
||||
不能。有些图床(比如微博图床、SM.MS、Imgur等)不支持后台管理,为了架构统一不支持远端删除。
|
||||
不能。有些图床(比如微博图床、SM.MS、Imgur 等)不支持后台管理,为了架构统一不支持远端删除。
|
||||
|
||||
## 3.能否支持上传视频文件
|
||||
## 3. 能否支持上传视频文件
|
||||
|
||||
目前不能。2.0版本出来后将会支持插件系统,届时如果有人开发了相应的插件理论可以支持任意文件上传。
|
||||
目前不能。如果有人开发了相应的插件理论可以支持任意文件上传。
|
||||
|
||||
## 4.微博图床上传之后无法显示预览图
|
||||
## 4. 微博图床上传之后无法显示预览图
|
||||
|
||||
通常是挂了全局代理导致的。
|
||||
|
||||
参考:[issue36](https://github.com/Molunerfinn/PicGo/issues/36)
|
||||
|
||||
## 5.能否支持某某某图床
|
||||
## 5. 能否支持某某某图床
|
||||
|
||||
截止v1.6,PicGo支持了如下图床:
|
||||
截止 v1.6,PicGo 支持了如下图床:
|
||||
|
||||
- `微博图床` v1.0
|
||||
- `七牛图床` v1.0
|
||||
- `腾讯云COS v4\v5版本` v1.1 & v1.5.0
|
||||
- `腾讯云 COS v4\v5 版本` v1.1 & v1.5.0
|
||||
- `又拍云` v1.2.0
|
||||
- `GitHub` v1.5.0
|
||||
- `SM.MS` v1.5.1
|
||||
- `阿里云OSS` v1.6.0
|
||||
- `阿里云 OSS` v1.6.0
|
||||
- `Imgur` v1.6.0
|
||||
|
||||
所以本体内将不会再支持其他图床。待2.0版本出来支持插件系统后可以由其他开发者开发相应的图床插件用以支持其他图床。
|
||||
所以本体内将不会再支持其他图床。需要其他图床支持可以参考目前已有的三方 [插件](https://github.com/PicGo/Awesome-PicGo),如果还是没有你所需要的图床欢迎开发一个插件供大家使用。
|
||||
|
||||
## 6.一个图床设置多个信息
|
||||
## 6. 一个图床设置多个信息
|
||||
|
||||
不能。因为目前的架构只支持一个图床一份信息。
|
||||
|
||||
## 7.v2.0版本有什么新功能
|
||||
## 7. GitHub 图床有时能上传,有时上传失败
|
||||
|
||||
在不改变PicGo现有使用界面和使用习惯的基础上,将会重构整个底层`传输->转换->上传`的架构,使其能够支持插件系统,方便其他开发者基于PicGo的底层进行二次开发。
|
||||
1. GitHub 图床不支持上传同名文件,如果有同名文件上传,会报错。建议开启 `时间戳重命名` 避免同名文件。
|
||||
2. GitHub 服务器和国内 GFW 的问题会导致有时上传成功,有时上传失败,无解。想要稳定请使用付费云存储,如阿里云、腾讯云等,价格也不会贵。
|
||||
|
||||
理论上有了插件系统之后,其他开发者能够支持如下功能:
|
||||
## 8. Mac 上无法打开 PicGo 的主窗口界面
|
||||
|
||||
1. 能够实现上传前图片压缩
|
||||
2. 能够通过图片url实现上传图片 -> 更深入地可以批量地实现图床的迁移
|
||||
3. 能够支持其他图床甚至是自己的图床
|
||||
PicGo 在 Mac 上是一个顶部栏应用,在 dock 栏是不会有图标的。要打开主窗口,请右键或者双指点按顶部栏 PicGo 图标,选择「打开详细窗口」即可打开主窗口。
|
||||
|
||||
等等。
|
||||
## 9. 上传失败,或者是服务器出错
|
||||
|
||||
届时底层架构将会独立出来成为一个独立的npm包,能够直接在命令行里调用。
|
||||
1. PicGo 自带的图床都经过测试,上传出错一般都不是 PicGo 自身的原因。如果你用的是 GitHub 图床请参考上面的第 7 点。
|
||||
2. 检查 PicGo 的日志(报错日志可以在 PicGo 设置 -> 设置日志文件 -> 点击打开 后找到),看看 `[PicGo Error]` 的报错信息里有什么关键信息
|
||||
1. 先自行搜索 error 里的报错信息,往往你能百度或者谷歌出问题原因,不必开 issue。
|
||||
2. 如果有带有 `401` 、`403` 等 `40X` 状态码字样的,不用怀疑,就是你配置写错了,仔细检查配置,看看是否多了空格之类的。
|
||||
3. 如果带有 `HttpError`、`RequestError` 、 `socket hang up` 等字样的说明这是网络问题,我无法帮你解决网络问题,请检查你自己的网络,是否有代理,DNS 设置是否正常等。
|
||||
|
||||
目前开发进度可以参考[PicGo-Core](https://github.com/Molunerfinn/PicGo-Core)。
|
||||
## 10. macOS版本安装完之后没有主界面
|
||||
|
||||
请找到PicGo在顶部栏的图标,然后右键(触摸板双指点按,或者鼠标右键),即可找到「打开详细窗口」的菜单。
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<img src="https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/New%20LOGO-150.png" alt="">
|
||||
<h1>PicGo</h1>
|
||||
<blockquote>图片上传+管理新体验 </blockquote>
|
||||
<a href="https://github.com/feross/standard">
|
||||
<a href="https://github.com/Molunerfinn/PicGo/actions">
|
||||
<img src="https://img.shields.io/badge/code%20style-standard-green.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://travis-ci.org/Molunerfinn/PicGo/builds">
|
||||
<img src="https://img.shields.io/travis/Molunerfinn/PicGo.svg?style=flat-square" alt="">
|
||||
<a href="https://github.com/Molunerfinn/PicGo/actions">
|
||||
<img src="https://github.com/Molunerfinn/PicGo/workflows/Build/badge.svg" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/releases">
|
||||
<img src="https://img.shields.io/github/downloads/Molunerfinn/PicGo/total.svg?style=flat-square" alt="">
|
||||
@@ -24,43 +24,40 @@
|
||||
|
||||
## 应用概述
|
||||
|
||||
**PicGo: 一个用于快速上传图片并获取图片URL链接的工具**
|
||||
**PicGo: 一个用于快速上传图片并获取图片 URL 链接的工具**
|
||||
|
||||
PicGo 本体支持如下图床:
|
||||
|
||||
- ~~`微博图床` v1.0~~ **微博图床从 2019 年 4 月开始进行防盗链,不建议继续使用**
|
||||
- `七牛图床` v1.0
|
||||
- `腾讯云 COS v4\v5版本` v1.1 & v1.5.0
|
||||
- `腾讯云 COS v4\v5 版本` v1.1 & v1.5.0
|
||||
- `又拍云` v1.2.0
|
||||
- `GitHub` v1.5.0
|
||||
- ~~`SM.MS` v1.5.1~~ **由于官方不再支持V1版本,暂时请使用[smms-user](https://github.com/xlzy520/picgo-plugin-smms-user)插件**
|
||||
- `SM.MS V2` v2.3.0-beta.0
|
||||
- `阿里云 OSS` v1.6.0
|
||||
- `Imgur` v1.6.0
|
||||
|
||||
**本体不再增加默认的图床支持。你可以自行开发第三方图床插件。详见 [PicGo-Core](https://picgo.github.io/PicGo-Core-Doc/)**。
|
||||
|
||||
|
||||
## 特色功能
|
||||
|
||||
- 支持拖拽图片上传
|
||||
- 支持快捷键上传剪贴板里第一张图片
|
||||
- Windows和macOS支持右键图片文件通过菜单上传 (v2.1.0+)
|
||||
- Windows 和 macOS 支持右键图片文件通过菜单上传 (v2.1.0+)
|
||||
- 上传图片后自动复制链接到剪贴板
|
||||
- 支持自定义复制到剪贴板的链接格式
|
||||
- 支持修改快捷键,默认快速上传快捷键:`command+shift+p`(macOS)| `control+shift+p`(Windows\Linux)
|
||||
- 支持插件系统,已有插件支持Gitee、青云等第三方图床
|
||||
- 支持插件系统,已有插件支持 Gitee、青云等第三方图床
|
||||
- 更多第三方插件以及使用了 PicGo 底层的应用可以在 [Awesome-PicGo](https://github.com/PicGo/Awesome-PicGo) 找到。欢迎贡献!
|
||||
- 支持通过发送HTTP请求调用 PicGo 上传(v2.2.0+)
|
||||
- 支持通过发送 HTTP 请求调用 PicGo 上传(v2.2.0+)
|
||||
- 更多功能等你自己去发现,同时也会不断开发新功能
|
||||
- 开发进度可以查看 [Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度
|
||||
- 欢迎加入[官方Gitter频道](https://gitter.im/picgo-all/PicGo?utm_source=share-link&utm_medium=link&utm_campaign=share-link)与我交流
|
||||
- 欢迎加入 [官方讨论区](https://github.com/Molunerfinn/PicGo/discussions) 与我交流
|
||||
|
||||
|
||||
**如果第一次使用,请参考应用[使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html)。遇到问题了还可以看看 [FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md) 以及被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed)。**
|
||||
**如果第一次使用,请参考应用 [使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html)。遇到问题了还可以看看 [FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md) 以及被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed)。**
|
||||
|
||||
## 下载安装
|
||||
|
||||
点击此处下载[应用](https://github.com/Molunerfinn/PicGo/releases)。
|
||||
点击此处下载 [应用](https://github.com/Molunerfinn/PicGo/releases)。
|
||||
|
||||
### Windows
|
||||
|
||||
@@ -74,14 +71,13 @@ Windows 用户请下载最新版本的 `exe` 文件。
|
||||
|
||||
macOS 用户请下载最新版本的 `dmg` 文件。
|
||||
|
||||
**还可以使用 `brew cask` 来安装 PicGo: `brew cask install picgo`。感谢 @womeimingzi11 的贡献!**
|
||||
**还可以使用 [Homebrew](https://brew.sh/) 来安装 PicGo: `brew install picgo --cask`。感谢 @womeimingzi11 的贡献!**
|
||||
|
||||
### Linux
|
||||
|
||||
Linux用户请下载 `AppImage` 文件。
|
||||
|
||||
**如果你是 Arch 类的 Linux 用户,可以直接通过 `aurman -S picgo-appimage` 来安装 PicGo。感谢 @houbaron 的贡献!**
|
||||
Linux 用户请下载 `AppImage` 文件。
|
||||
|
||||
**如果你是 Arch 类的 Linux 用户,可以直接通过 `yay -S picgo-appimage` 来安装 PicGo (若是没有则先安装 yay ,是 aur 仓库助手)。感谢 @houbaron 的贡献!**
|
||||
|
||||
## 应用截图
|
||||
|
||||
@@ -97,9 +93,9 @@ Linux用户请下载 `AppImage` 文件。
|
||||
|
||||
> 如果想学习 Electron-vue 的开发,可以查看我写的系列教程——[Electron-vue 开发实战](https://molunerfinn.com/tags/Electron-vue/)
|
||||
|
||||
1. 你需要有 Node、Git环境,了解 npm 的相关知识。
|
||||
2. `git clone https://github.com/Molunerfinn/PicGo.git` 并进入项目
|
||||
3. `npm install` 下载依赖
|
||||
1. 你需要有 Node、Git 环境,了解 npm 的相关知识。
|
||||
2. `git clone https://github.com/Molunerfinn/PicGo.git` 并进入项目。
|
||||
3. `yarn` 下载依赖。注意如果你没有 `yarn`,请去 [官网](https://classic.yarnpkg.com/en/docs/install) 下载安装后再使用。 **用 `npm install` 将导致未知错误!**
|
||||
4. Mac 需要有 Xcode 环境,Windows 需要有 VS 环境。
|
||||
|
||||
### 开发模式
|
||||
@@ -111,7 +107,7 @@ ctrl+c # 退出开发模式
|
||||
npm run electron:serve # 重新进入开发模式
|
||||
```
|
||||
|
||||
**注:Windows开发模式运行之后会在底部任务栏的右下角应用区出现PicGo的应用图标。**
|
||||
**注:Windows 开发模式运行之后会在底部任务栏的右下角应用区出现 PicGo 的应用图标。**
|
||||
|
||||
### 生产模式
|
||||
|
||||
@@ -129,7 +125,8 @@ npm run electron:build
|
||||
|
||||
## 其他相关
|
||||
|
||||
- [vs-picgo](https://github.com/Spades-S/vs-picgo):PicGo 的 VS Code 版。
|
||||
- [vs-picgo](https://github.com/PicGo/vs-picgo):PicGo 的 VS Code 版。
|
||||
- [flutter-picgo](https://github.com/PicGo/flutter-picgo):PicGo 的手机版(支持 Android 和 iOS )。
|
||||
|
||||
## 赞助
|
||||
|
||||
@@ -147,4 +144,4 @@ npm run electron:build
|
||||
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
||||
|
||||
Copyright (c) 2017 - 2019 Molunerfinn
|
||||
Copyright (c) 2017 - 2019 Molunerfinn
|
||||
|
||||
+3
-3
@@ -19,13 +19,13 @@ init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 12 x64
|
||||
- ps: Install-Product node 16 x64
|
||||
- git reset --hard HEAD
|
||||
- yarn
|
||||
- node --version
|
||||
|
||||
build_script:
|
||||
#- yarn test
|
||||
- npm run release
|
||||
- yarn release
|
||||
|
||||
test: off
|
||||
test: false
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
],
|
||||
plugins: ['@babel/plugin-proposal-optional-chaining']
|
||||
}
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@
|
||||
.col-xs-10.col-xs-offset-1.col-md-7.col-md-offset-0
|
||||
img(:src="item.url")
|
||||
.col-xs-10.col-xs-offset-1.col-md-5.col-md-offset-0.display-list__content
|
||||
.display-list__title {{ item.title }}
|
||||
.display-list__title {{ item.title }}
|
||||
.display-list__desc {{ item.desc }}
|
||||
.row.ex-width.info
|
||||
.col-xs-10.col-xs-offset-1
|
||||
@@ -94,7 +94,7 @@ html,
|
||||
h1
|
||||
margin 0
|
||||
padding 0
|
||||
font-family "Source Sans Pro","Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif
|
||||
font-family "Source Sans Pro","Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif
|
||||
#app
|
||||
position relative
|
||||
.mask
|
||||
@@ -213,4 +213,4 @@ h1
|
||||
font-size 25px
|
||||
&__desc
|
||||
margin-top 12px
|
||||
</style>
|
||||
</style>
|
||||
|
||||
+28
-17
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "picgo",
|
||||
"version": "2.3.0-beta.0",
|
||||
"version": "2.3.1-beta.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service electron:serve",
|
||||
"build": "vue-cli-service electron:build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
|
||||
"electron:build": "vue-cli-service electron:build",
|
||||
"electron:serve": "vue-cli-service electron:serve",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
@@ -34,50 +35,60 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@picgo/store": "^1.0.3",
|
||||
"axios": "^0.19.0",
|
||||
"core-js": "^3.3.2",
|
||||
"element-ui": "^2.13.0",
|
||||
"fix-path": "^2.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"keycode": "^2.2.0",
|
||||
"lodash-id": "^0.14.0",
|
||||
"lowdb": "^1.0.0",
|
||||
"picgo": "^1.4.7",
|
||||
"picgo": "^1.4.24",
|
||||
"qrcode.vue": "^1.7.0",
|
||||
"uuidv4": "^6.2.11",
|
||||
"vue": "^2.6.10",
|
||||
"vue-gallery": "^2.0.1",
|
||||
"vue-lazyload": "^1.2.6",
|
||||
"vue-router": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^8.2.0",
|
||||
"@picgo/bump-version": "^1.0.3",
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
|
||||
"@picgo/bump-version": "^1.1.2",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/lowdb": "^1.0.9",
|
||||
"@types/node": "10.17.6",
|
||||
"@types/node": "^16.10.2",
|
||||
"@types/request-promise-native": "^1.0.17",
|
||||
"@types/semver": "^7.3.8",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@vue/cli-plugin-babel": "^4.0.0",
|
||||
"@vue/cli-plugin-eslint": "^4.0.0",
|
||||
"@vue/cli-plugin-router": "^4.0.0",
|
||||
"@vue/cli-plugin-typescript": "^4.0.0",
|
||||
"@vue/cli-plugin-typescript": "^4.5.13",
|
||||
"@vue/cli-service": "^4.0.0",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"@vue/eslint-config-typescript": "^4.0.0",
|
||||
"commitizen": "^4.0.3",
|
||||
"@vue/eslint-config-standard": "^6.1.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"conventional-changelog": "^3.1.18",
|
||||
"cz-customizable": "^6.2.0",
|
||||
"electron": "^6.0.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-vue": "^5.0.0",
|
||||
"electron": "^16.0.6",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-standard": ">=16.0.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"husky": "^3.1.0",
|
||||
"stylus": "^0.54.7",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"typescript": "~3.7.3",
|
||||
"vue-cli-plugin-electron-builder": "^1.4.2",
|
||||
"typescript": "^4.4.3",
|
||||
"vue-cli-plugin-electron-builder": "^2.1.1",
|
||||
"vue-property-decorator": "^8.3.0",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "12.0.2"
|
||||
"@types/node": "^16.10.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
# grab the paths
|
||||
scriptPath=$(echo $0 | awk '{ print substr( $0, 1, length($0)-6 ) }')"windows10.ps1"
|
||||
imagePath=$(echo $1 | awk '{ print substr( $0, 1, length($0)-18 ) }')
|
||||
imageName=$(echo $1 | awk '{ print substr( $0, length($0)-17, length($0) ) }')
|
||||
|
||||
# run the powershell script
|
||||
res=$(powershell.exe -noprofile -noninteractive -nologo -sta -executionpolicy unrestricted -file $(wslpath -w $scriptPath) $(wslpath -w $imagePath)"\\"$imageName)
|
||||
|
||||
# note that there is a return symbol in powershell result
|
||||
noImage=$(echo "no image\r")
|
||||
|
||||
# check whether image exists
|
||||
if [ "$res" = "$noImage" ] ;then
|
||||
echo "no image"
|
||||
else
|
||||
echo $(wslpath -u $res)
|
||||
fi
|
||||
+5
-4
@@ -1,7 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import App from './renderer/App.vue'
|
||||
import router from './renderer/router'
|
||||
import db from '#/datastore/index'
|
||||
import ElementUI from 'element-ui'
|
||||
import { webFrame } from 'electron'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
@@ -9,14 +8,14 @@ import VueLazyLoad from 'vue-lazyload'
|
||||
import axios from 'axios'
|
||||
import mainMixin from './renderer/utils/mainMixin'
|
||||
import bus from '@/utils/bus'
|
||||
import { initTalkingData } from './renderer/utils/analytics'
|
||||
import db from './renderer/utils/db'
|
||||
|
||||
webFrame.setVisualZoomLevelLimits(1, 1)
|
||||
webFrame.setLayoutZoomLevelLimits(0, 0)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$builtInPicBed = [
|
||||
'smms',
|
||||
'weibo',
|
||||
'imgur',
|
||||
'qiniu',
|
||||
'tcyun',
|
||||
@@ -24,7 +23,7 @@ Vue.prototype.$builtInPicBed = [
|
||||
'aliyun',
|
||||
'github'
|
||||
]
|
||||
Vue.prototype.$db = db
|
||||
Vue.prototype.$$db = db
|
||||
Vue.prototype.$http = axios
|
||||
Vue.prototype.$bus = bus
|
||||
|
||||
@@ -36,3 +35,5 @@ new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
|
||||
initTalkingData()
|
||||
|
||||
@@ -6,7 +6,7 @@ The lowest level APIs that are not dependent on each other. The upper APIs depen
|
||||
|
||||
## app
|
||||
|
||||
Provide key API interfaces for PicGo application, including uploader, window management, shortcut key system, etc
|
||||
Provide key API interfaces for PicGo application, including uploader, window management, shortcut key system, remotes handler, etc
|
||||
|
||||
## gui
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
} from 'electron'
|
||||
import logger from '@core/picgo/logger'
|
||||
import GuiApi from '../../gui'
|
||||
import db from '#/datastore'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import shortKeyService from './shortKeyService'
|
||||
import picgo from '@core/picgo'
|
||||
@@ -16,24 +16,31 @@ class ShortKeyHandler {
|
||||
this.isInModifiedMode = flag
|
||||
})
|
||||
}
|
||||
|
||||
init () {
|
||||
this.initBuiltInShortKey()
|
||||
this.initPluginsShortKey()
|
||||
}
|
||||
|
||||
private initBuiltInShortKey () {
|
||||
const commands = db.get('settings.shortKey') as IShortKeyConfigs
|
||||
Object.keys(commands)
|
||||
.filter(item => item.includes('picgo:'))
|
||||
.map(command => {
|
||||
.forEach(command => {
|
||||
const config = commands[command]
|
||||
globalShortcut.register(config.key, () => {
|
||||
this.handler(command)
|
||||
})
|
||||
// if disabled, don't register #534
|
||||
if (config.enable) {
|
||||
globalShortcut.register(config.key, () => {
|
||||
this.handler(command)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private initPluginsShortKey () {
|
||||
// get enabled plugin
|
||||
const pluginList = picgo.pluginLoader.getList()
|
||||
for (let item of pluginList) {
|
||||
for (const item of pluginList) {
|
||||
const plugin = picgo.pluginLoader.getPlugin(item)
|
||||
// if a plugin has commands
|
||||
if (plugin && plugin.commands) {
|
||||
@@ -42,11 +49,14 @@ class ShortKeyHandler {
|
||||
continue
|
||||
}
|
||||
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
|
||||
for (let cmd of commands) {
|
||||
for (const cmd of commands) {
|
||||
const command = `${item}:${cmd.name}`
|
||||
if (db.has(`settings.shortKey[${command}]`)) {
|
||||
const commandConfig = db.get(`settings.shortKey.${command}`) as IShortKeyConfig
|
||||
this.registerShortKey(commandConfig, command, cmd.handle, false)
|
||||
// if disabled, don't register #534
|
||||
if (commandConfig.enable) {
|
||||
this.registerShortKey(commandConfig, command, cmd.handle, false)
|
||||
}
|
||||
} else {
|
||||
this.registerShortKey(cmd, command, cmd.handle, true)
|
||||
}
|
||||
@@ -56,6 +66,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private registerShortKey (config: IShortKeyConfig | IPluginShortKeyConfig, command: string, handler: IShortKeyHandler, writeFlag: boolean) {
|
||||
shortKeyService.registerCommand(command, handler)
|
||||
if (config.key) {
|
||||
@@ -65,6 +76,8 @@ class ShortKeyHandler {
|
||||
} else {
|
||||
logger.warn(`${command} do not provide a key to bind`)
|
||||
}
|
||||
// if the configfile already had this command
|
||||
// then writeFlag -> false
|
||||
if (writeFlag) {
|
||||
picgo.saveConfig({
|
||||
[`settings.shortKey.${command}`]: {
|
||||
@@ -76,6 +89,7 @@ class ShortKeyHandler {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// enable or disable shortKey
|
||||
bindOrUnbindShortKey (item: IShortKeyConfig, from: string): boolean {
|
||||
const command = `${from}:${item.name}`
|
||||
@@ -99,6 +113,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update shortKey bindings
|
||||
updateShortKey (item: IShortKeyConfig, oldKey: string, from: string): boolean {
|
||||
const command = `${from}:${item.name}`
|
||||
@@ -112,6 +127,7 @@ class ShortKeyHandler {
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
private async handler (command: string) {
|
||||
if (this.isInModifiedMode) {
|
||||
return
|
||||
@@ -121,13 +137,13 @@ class ShortKeyHandler {
|
||||
} else if (command.includes('picgo-plugin-')) {
|
||||
const handler = shortKeyService.getShortKeyHandler(command)
|
||||
if (handler) {
|
||||
const guiApi = new GuiApi()
|
||||
return handler(picgo, guiApi)
|
||||
return handler(picgo, GuiApi.getInstance())
|
||||
}
|
||||
} else {
|
||||
logger.warn(`can not find command: ${command}`)
|
||||
}
|
||||
}
|
||||
|
||||
registerPluginShortKey (pluginName: string) {
|
||||
const plugin = picgo.pluginLoader.getPlugin(pluginName)
|
||||
if (plugin && plugin.commands) {
|
||||
@@ -136,7 +152,7 @@ class ShortKeyHandler {
|
||||
return
|
||||
}
|
||||
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
|
||||
for (let cmd of commands) {
|
||||
for (const cmd of commands) {
|
||||
const command = `${pluginName}:${cmd.name}`
|
||||
if (db.has(`settings.shortKey[${command}]`)) {
|
||||
const commandConfig = db.get(`settings.shortKey[${command}]`) as IShortKeyConfig
|
||||
@@ -147,6 +163,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unregisterPluginShortKey (pluginName: string) {
|
||||
const commands = db.get('settings.shortKey') as IShortKeyConfigs
|
||||
const keyList = Object.keys(commands)
|
||||
|
||||
@@ -4,15 +4,18 @@ class ShortKeyService {
|
||||
registerCommand (command: string, handler: IShortKeyHandler) {
|
||||
this.commandList.set(command, handler)
|
||||
}
|
||||
|
||||
unregisterCommand (command: string) {
|
||||
this.commandList.delete(command)
|
||||
}
|
||||
|
||||
getShortKeyHandler (command: string): IShortKeyHandler | null {
|
||||
const handler = this.commandList.get(command)
|
||||
if (handler) return handler
|
||||
logger.warn(`cannot find command: ${command}`)
|
||||
return null
|
||||
}
|
||||
|
||||
getCommandList () {
|
||||
return [...this.commandList.keys()]
|
||||
}
|
||||
|
||||
+201
-134
@@ -9,47 +9,177 @@ import {
|
||||
} from 'electron'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import getPicBeds from '~/main/utils/getPicBeds'
|
||||
import db from '#/datastore'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import picgo from '@core/picgo'
|
||||
import pasteTemplate from '#/utils/pasteTemplate'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import pkg from 'root/package.json'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
import { privacyManager } from '~/main/utils/privacyManager'
|
||||
let contextMenu: Menu | null
|
||||
let menu: Menu | null
|
||||
let tray: Tray | null
|
||||
export function createContextMenu () {
|
||||
const picBeds = getPicBeds()
|
||||
const submenu = picBeds.filter(item => item.visible).map(item => {
|
||||
return {
|
||||
label: item.name,
|
||||
type: 'radio',
|
||||
checked: db.get('picBed.current') === item.type,
|
||||
click () {
|
||||
picgo.saveConfig({
|
||||
'picBed.current': item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
if (process.platform === 'darwin' || process.platform === 'win32') {
|
||||
const submenu = picBeds.filter(item => item.visible).map(item => {
|
||||
return {
|
||||
label: item.name,
|
||||
type: 'radio',
|
||||
checked: db.get('picBed.current') === item.type,
|
||||
click () {
|
||||
picgo.saveConfig({
|
||||
'picBed.current': item.type,
|
||||
'picBed.uploader': item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: '关于',
|
||||
click () {
|
||||
dialog.showMessageBox({
|
||||
title: 'PicGo',
|
||||
message: 'PicGo',
|
||||
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
|
||||
})
|
||||
})
|
||||
contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: '关于',
|
||||
click () {
|
||||
dialog.showMessageBox({
|
||||
title: 'PicGo',
|
||||
message: 'PicGo',
|
||||
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '打开详细窗口',
|
||||
click () {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
|
||||
settingWindow!.show()
|
||||
settingWindow!.focus()
|
||||
if (windowManager.has(IWindowList.MINI_WINDOW)) {
|
||||
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '选择默认图床',
|
||||
type: 'submenu',
|
||||
// @ts-ignore
|
||||
submenu
|
||||
},
|
||||
// @ts-ignore
|
||||
{
|
||||
label: '打开更新助手',
|
||||
type: 'checkbox',
|
||||
checked: db.get('settings.showUpdateTip'),
|
||||
click () {
|
||||
const value = db.get('settings.showUpdateTip')
|
||||
db.set('settings.showUpdateTip', !value)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '隐私协议',
|
||||
click () {
|
||||
privacyManager.show(false)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '重启应用',
|
||||
click () {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
}
|
||||
},
|
||||
// @ts-ignore
|
||||
{
|
||||
role: 'quit',
|
||||
label: '退出'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '打开详细窗口',
|
||||
click () {
|
||||
])
|
||||
} else if (process.platform === 'linux') {
|
||||
// TODO 图床选择功能
|
||||
// 由于在Linux难以像在Mac和Windows上那样在点击时构造ContextMenu,
|
||||
// 暂时取消这个选单,避免引起和设置中启用的图床不一致
|
||||
|
||||
// TODO 重启应用功能
|
||||
// 目前的实现无法正常工作
|
||||
|
||||
contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: '打开详细窗口',
|
||||
click () {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
|
||||
settingWindow!.show()
|
||||
settingWindow!.focus()
|
||||
if (windowManager.has(IWindowList.MINI_WINDOW)) {
|
||||
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
|
||||
}
|
||||
}
|
||||
},
|
||||
// @ts-ignore
|
||||
{
|
||||
label: '打开更新助手',
|
||||
type: 'checkbox',
|
||||
checked: db.get('settings.showUpdateTip'),
|
||||
click () {
|
||||
const value = db.get('settings.showUpdateTip')
|
||||
db.set('settings.showUpdateTip', !value)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '关于应用',
|
||||
click () {
|
||||
dialog.showMessageBox({
|
||||
title: 'PicGo',
|
||||
message: 'PicGo',
|
||||
buttons: ['Ok'],
|
||||
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
|
||||
})
|
||||
}
|
||||
},
|
||||
// @ts-ignore
|
||||
{
|
||||
role: 'quit',
|
||||
label: '退出'
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
export function createTray () {
|
||||
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
|
||||
tray = new Tray(menubarPic)
|
||||
// click事件在Mac和Windows上可以触发(在Ubuntu上无法触发,Unity不支持)
|
||||
if (process.platform === 'darwin' || process.platform === 'win32') {
|
||||
tray.on('right-click', () => {
|
||||
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
|
||||
}
|
||||
createContextMenu()
|
||||
tray!.popUpContextMenu(contextMenu!)
|
||||
})
|
||||
tray.on('click', (event, bounds) => {
|
||||
if (process.platform === 'darwin') {
|
||||
toggleWindow(bounds)
|
||||
setTimeout(() => {
|
||||
const img = clipboard.readImage()
|
||||
const obj: ImgInfo[] = []
|
||||
if (!img.isEmpty()) {
|
||||
// 从剪贴板来的图片默认转为png
|
||||
// @ts-ignore
|
||||
const imgUrl = 'data:image/png;base64,' + Buffer.from(img.toPNG(), 'binary').toString('base64')
|
||||
obj.push({
|
||||
width: img.getSize().width,
|
||||
height: img.getSize().height,
|
||||
imgUrl
|
||||
})
|
||||
}
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('clipboardFiles', obj)
|
||||
}, 0)
|
||||
} else {
|
||||
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
|
||||
}
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
|
||||
settingWindow!.show()
|
||||
settingWindow!.focus()
|
||||
@@ -57,116 +187,53 @@ export function createContextMenu () {
|
||||
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '选择默认图床',
|
||||
type: 'submenu',
|
||||
// @ts-ignore
|
||||
submenu
|
||||
},
|
||||
// @ts-ignore
|
||||
{
|
||||
label: '打开更新助手',
|
||||
type: 'checkbox',
|
||||
checked: db.get('settings.showUpdateTip'),
|
||||
click () {
|
||||
const value = db.get('settings.showUpdateTip')
|
||||
db.set('settings.showUpdateTip', !value)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '重启应用',
|
||||
click () {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
}
|
||||
},
|
||||
// @ts-ignore
|
||||
{
|
||||
role: 'quit',
|
||||
label: '退出'
|
||||
}
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
export function createTray () {
|
||||
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
|
||||
tray = new Tray(menubarPic)
|
||||
tray.on('right-click', () => {
|
||||
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
|
||||
}
|
||||
createContextMenu()
|
||||
tray!.popUpContextMenu(contextMenu!)
|
||||
})
|
||||
tray.on('click', (event, bounds) => {
|
||||
if (process.platform === 'darwin') {
|
||||
toggleWindow(bounds)
|
||||
setTimeout(() => {
|
||||
let img = clipboard.readImage()
|
||||
let obj: ImgInfo[] = []
|
||||
if (!img.isEmpty()) {
|
||||
// 从剪贴板来的图片默认转为png
|
||||
// @ts-ignore
|
||||
const imgUrl = 'data:image/png;base64,' + Buffer.from(img.toPNG(), 'binary').toString('base64')
|
||||
obj.push({
|
||||
width: img.getSize().width,
|
||||
height: img.getSize().height,
|
||||
imgUrl
|
||||
tray.on('drag-enter', () => {
|
||||
if (systemPreferences.isDarkMode()) {
|
||||
tray!.setImage(`${__static}/upload-dark.png`)
|
||||
} else {
|
||||
tray!.setImage(`${__static}/upload.png`)
|
||||
}
|
||||
})
|
||||
|
||||
tray.on('drag-end', () => {
|
||||
tray!.setImage(`${__static}/menubar.png`)
|
||||
})
|
||||
|
||||
// drop-files only be supported in macOS
|
||||
// so the tray window must be available
|
||||
tray.on('drop-files', async (event: Event, files: string[]) => {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
|
||||
const imgs = await uploader
|
||||
.setWebContents(trayWindow.webContents)
|
||||
.upload(files)
|
||||
if (imgs !== false) {
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: '上传成功',
|
||||
body: imgs[i].imgUrl!,
|
||||
icon: files[i]
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, i * 100)
|
||||
await GalleryDB.getInstance().insert(imgs[i])
|
||||
}
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('clipboardFiles', obj)
|
||||
}, 0)
|
||||
} else {
|
||||
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
trayWindow.webContents.send('dragFiles', imgs)
|
||||
}
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
|
||||
settingWindow!.show()
|
||||
settingWindow!.focus()
|
||||
if (windowManager.has(IWindowList.MINI_WINDOW)) {
|
||||
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
tray.on('drag-enter', () => {
|
||||
if (systemPreferences.isDarkMode()) {
|
||||
tray!.setImage(`${__static}/upload-dark.png`)
|
||||
} else {
|
||||
tray!.setImage(`${__static}/upload.png`)
|
||||
}
|
||||
})
|
||||
|
||||
tray.on('drag-end', () => {
|
||||
tray!.setImage(`${__static}/menubar.png`)
|
||||
})
|
||||
|
||||
tray.on('drop-files', async (event: Event, files: string[]) => {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
|
||||
const imgs = await uploader
|
||||
.setWebContents(trayWindow.webContents)
|
||||
.upload(files)
|
||||
if (imgs !== false) {
|
||||
let pasteText = ''
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
pasteText += pasteTemplate(pasteStyle, imgs[i]) + '\r\n'
|
||||
const notification = new Notification({
|
||||
title: '上传成功',
|
||||
body: imgs[i].imgUrl!,
|
||||
icon: files[i]
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, i * 100)
|
||||
db.insert('uploaded', imgs[i])
|
||||
}
|
||||
handleCopyUrl(pasteText)
|
||||
trayWindow.webContents.send('dragFiles', imgs)
|
||||
}
|
||||
})
|
||||
// toggleWindow()
|
||||
})
|
||||
// toggleWindow()
|
||||
} else if (process.platform === 'linux') {
|
||||
// click事件在Ubuntu上无法触发,Unity不支持(在Mac和Windows上可以触发)
|
||||
// 需要使用 setContextMenu 设置菜单
|
||||
createContextMenu()
|
||||
tray!.setContextMenu(contextMenu)
|
||||
}
|
||||
}
|
||||
|
||||
export function createMenu () {
|
||||
|
||||
@@ -5,30 +5,32 @@ import {
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import uploader from '.'
|
||||
import pasteTemplate from '#/utils/pasteTemplate'
|
||||
import db from '#/datastore'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
import { handleUrlEncode } from '#/utils/common'
|
||||
export const uploadClipboardFiles = async (): Promise<string> => {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
let img = await uploader.setWebContents(win!.webContents).upload()
|
||||
const img = await uploader.setWebContents(win!.webContents).upload()
|
||||
if (img !== false) {
|
||||
if (img.length > 0) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0]))
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: '上传成功',
|
||||
body: img[0].imgUrl!,
|
||||
icon: img[0].imgUrl
|
||||
})
|
||||
notification.show()
|
||||
db.insert('uploaded', img[0])
|
||||
trayWindow.webContents.send('clipboardFiles', [])
|
||||
trayWindow.webContents.send('uploadFiles', img)
|
||||
await GalleryDB.getInstance().insert(img[0])
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', img)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
|
||||
}
|
||||
return img[0].imgUrl as string
|
||||
return handleUrlEncode(img[0].imgUrl as string)
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
title: '上传不成功',
|
||||
@@ -48,9 +50,9 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
|
||||
const result = []
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
let pasteText = ''
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
pasteText += pasteTemplate(pasteStyle, imgs[i]) + '\r\n'
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: '上传成功',
|
||||
body: imgs[i].imgUrl!,
|
||||
@@ -59,11 +61,12 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, i * 100)
|
||||
db.insert('uploaded', imgs[i])
|
||||
result.push(imgs[i].imgUrl!)
|
||||
await GalleryDB.getInstance().insert(imgs[i])
|
||||
result.push(handleUrlEncode(imgs[i].imgUrl!))
|
||||
}
|
||||
handleCopyUrl(pasteText)
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('uploadFiles', imgs)
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)?.webContents?.send('uploadFiles', imgs)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
app,
|
||||
Notification,
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
@@ -7,12 +6,17 @@ import {
|
||||
} from 'electron'
|
||||
import dayjs from 'dayjs'
|
||||
import picgo from '@core/picgo'
|
||||
import db from '#/datastore'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import util from 'util'
|
||||
import { IPicGo } from 'picgo/dist/src/types'
|
||||
import { showNotification, calcDurationRange } from '~/main/utils/common'
|
||||
import { TALKING_DATA_EVENT } from '~/universal/events/constants'
|
||||
import logger from '@core/picgo/logger'
|
||||
|
||||
const waitForShow = (webcontent: WebContents) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
webcontent.on('did-finish-load', () => {
|
||||
resolve()
|
||||
})
|
||||
@@ -20,7 +24,7 @@ const waitForShow = (webcontent: WebContents) => {
|
||||
}
|
||||
|
||||
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
const windowId = window.id
|
||||
ipcMain.once(`rename${id}`, (evt: Event, newName: string) => {
|
||||
resolve(newName)
|
||||
@@ -34,8 +38,23 @@ const waitForRename = (window: BrowserWindow, id: number): Promise<string|null>
|
||||
})
|
||||
}
|
||||
|
||||
const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) => {
|
||||
const data: ITalkingDataOptions = {
|
||||
EventId: 'upload',
|
||||
Label: options.type,
|
||||
MapKv: {
|
||||
by: options.fromClipboard ? 'clipboard' : 'files', // 上传剪贴板图片还是选择的文文件
|
||||
count: options.count, // 上传的数量
|
||||
duration: calcDurationRange(options.duration || 0), // 上传耗时
|
||||
type: options.type
|
||||
}
|
||||
}
|
||||
webContents.send(TALKING_DATA_EVENT, data)
|
||||
}
|
||||
|
||||
class Uploader {
|
||||
private webContents: WebContents | null = null
|
||||
// private uploading: boolean = false
|
||||
constructor () {
|
||||
this.init()
|
||||
}
|
||||
@@ -49,7 +68,7 @@ class Uploader {
|
||||
picgo.on('uploadProgress', progress => {
|
||||
this.webContents?.send('uploadProgress', progress)
|
||||
})
|
||||
picgo.on('beforeTransform', ctx => {
|
||||
picgo.on('beforeTransform', () => {
|
||||
if (db.get('settings.uploadNotification')) {
|
||||
const notification = new Notification({
|
||||
title: '上传进度',
|
||||
@@ -59,7 +78,7 @@ class Uploader {
|
||||
}
|
||||
})
|
||||
picgo.helper.beforeUploadPlugins.register('renameFn', {
|
||||
handle: async ctx => {
|
||||
handle: async (ctx: IPicGo) => {
|
||||
const rename = db.get('settings.rename')
|
||||
const autoRename = db.get('settings.autoRename')
|
||||
if (autoRename || rename) {
|
||||
@@ -67,7 +86,7 @@ class Uploader {
|
||||
let name: undefined | string | null
|
||||
let fileName: string | undefined
|
||||
if (autoRename) {
|
||||
fileName = dayjs().add(index, 'second').format('YYYYMMDDHHmmss') + item.extname
|
||||
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS') + item.extname
|
||||
} else {
|
||||
fileName = item.fileName
|
||||
}
|
||||
@@ -89,30 +108,34 @@ class Uploader {
|
||||
return this
|
||||
}
|
||||
|
||||
upload (img?: IUploadOption): Promise<ImgInfo[]|false> {
|
||||
picgo.upload(img)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
picgo.once('finished', ctx => {
|
||||
if (ctx.output.every((item: ImgInfo) => item.imgUrl)) {
|
||||
resolve(ctx.output)
|
||||
} else {
|
||||
resolve(false)
|
||||
async upload (img?: IUploadOption): Promise<ImgInfo[]|false> {
|
||||
try {
|
||||
const startTime = Date.now()
|
||||
const output = await picgo.upload(img)
|
||||
if (Array.isArray(output) && output.some((item: ImgInfo) => item.imgUrl)) {
|
||||
if (this.webContents) {
|
||||
handleTalkingData(this.webContents, {
|
||||
fromClipboard: !img,
|
||||
type: db.get('picBed.uploader') || db.get('picBed.current') || 'smms',
|
||||
count: img ? img.length : 1,
|
||||
duration: Date.now() - startTime
|
||||
} as IAnalyticsData)
|
||||
}
|
||||
picgo.removeAllListeners('failed')
|
||||
})
|
||||
picgo.once('failed', ctx => {
|
||||
setTimeout(() => {
|
||||
const notification = new Notification({
|
||||
title: '上传失败',
|
||||
body: '请检查配置和上传的文件是否符合要求'
|
||||
})
|
||||
notification.show()
|
||||
}, 500)
|
||||
picgo.removeAllListeners('finished')
|
||||
resolve(false)
|
||||
})
|
||||
})
|
||||
return output.filter(item => item.imgUrl)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
setTimeout(() => {
|
||||
showNotification({
|
||||
title: '上传失败',
|
||||
body: util.format(e.stack),
|
||||
clickToCopy: true
|
||||
})
|
||||
}, 500)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,16 @@ const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
export const TRAY_WINDOW_URL = isDevelopment
|
||||
? (process.env.WEBPACK_DEV_SERVER_URL as string)
|
||||
: `picgo://./index.html`
|
||||
: 'picgo://./index.html'
|
||||
|
||||
export const SETTING_WINDOW_URL = isDevelopment
|
||||
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#main-page/upload`
|
||||
: `picgo://./index.html#main-page/upload`
|
||||
: 'picgo://./index.html#main-page/upload'
|
||||
|
||||
export const MINI_WINDOW_URL = isDevelopment
|
||||
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page`
|
||||
: `picgo://./index.html#mini-page`
|
||||
: 'picgo://./index.html#mini-page'
|
||||
|
||||
export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development'
|
||||
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page`
|
||||
: `picgo://./index.html#rename-page`
|
||||
: 'picgo://./index.html#rename-page'
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { IWindowListItem } from '#/types/electron'
|
||||
import bus from '@core/bus'
|
||||
import { CREATE_APP_MENU } from '@core/bus/constants'
|
||||
import db from '#/datastore'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import { app } from 'electron'
|
||||
|
||||
@@ -28,7 +28,8 @@ windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
transparent: true,
|
||||
vibrancy: 'ultra-dark',
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
|
||||
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
|
||||
nodeIntegrationInWorker: true,
|
||||
backgroundThrottling: false
|
||||
}
|
||||
@@ -60,7 +61,8 @@ windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
nodeIntegration: true,
|
||||
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
|
||||
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
|
||||
nodeIntegrationInWorker: true,
|
||||
webSecurity: false
|
||||
}
|
||||
@@ -93,7 +95,7 @@ windowList.set(IWindowList.MINI_WINDOW, {
|
||||
isValid: process.platform !== 'darwin',
|
||||
multiple: false,
|
||||
options () {
|
||||
let obj: IBrowserWindowOptions = {
|
||||
const obj: IBrowserWindowOptions = {
|
||||
height: 64,
|
||||
width: 64,
|
||||
show: process.platform === 'linux',
|
||||
@@ -105,7 +107,8 @@ windowList.set(IWindowList.MINI_WINDOW, {
|
||||
icon: `${__static}/logo.png`,
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
nodeIntegration: true,
|
||||
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
|
||||
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
|
||||
nodeIntegrationInWorker: true
|
||||
}
|
||||
}
|
||||
@@ -124,7 +127,7 @@ windowList.set(IWindowList.RENAME_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: true,
|
||||
options () {
|
||||
let options: IBrowserWindowOptions = {
|
||||
const options: IBrowserWindowOptions = {
|
||||
height: 175,
|
||||
width: 300,
|
||||
show: true,
|
||||
@@ -132,7 +135,8 @@ windowList.set(IWindowList.RENAME_WINDOW, {
|
||||
resizable: false,
|
||||
vibrancy: 'ultra-dark',
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
|
||||
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
|
||||
nodeIntegrationInWorker: true,
|
||||
backgroundThrottling: false
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class WindowManager implements IWindowManager {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
get (name: IWindowList) {
|
||||
if (this.has(name)) {
|
||||
return this.windowMap.get(name)!
|
||||
@@ -42,9 +43,11 @@ class WindowManager implements IWindowManager {
|
||||
return window
|
||||
}
|
||||
}
|
||||
|
||||
has (name: IWindowList) {
|
||||
return this.windowMap.has(name)
|
||||
}
|
||||
|
||||
// useless
|
||||
// delete (name: IWindowList) {
|
||||
// const window = this.windowMap.get(name)
|
||||
@@ -60,6 +63,7 @@ class WindowManager implements IWindowManager {
|
||||
this.windowIdMap.delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
getAvailableWindow () {
|
||||
const miniWindow = this.windowMap.get(IWindowList.MINI_WINDOW)
|
||||
if (miniWindow && miniWindow.isVisible()) {
|
||||
|
||||
@@ -14,7 +14,7 @@ export const uploadWithClipboardFiles = (): Promise<{
|
||||
success: boolean,
|
||||
result?: string[]
|
||||
}> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
bus.once(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, (result: string) => {
|
||||
if (result) {
|
||||
return resolve({
|
||||
@@ -35,7 +35,7 @@ export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
|
||||
success: boolean,
|
||||
result?: string[]
|
||||
}> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
bus.once(UPLOAD_WITH_FILES_RESPONSE, (result: string[]) => {
|
||||
if (result.length) {
|
||||
return resolve({
|
||||
@@ -55,7 +55,7 @@ export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
|
||||
// get available window id:
|
||||
// miniWindow or settingWindow or trayWindow
|
||||
export const getWindowId = (): Promise<number> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
bus.once(GET_WINDOW_ID_REPONSE, (id: number) => {
|
||||
resolve(id)
|
||||
})
|
||||
@@ -65,7 +65,7 @@ export const getWindowId = (): Promise<number> => {
|
||||
|
||||
// get settingWindow id:
|
||||
export const getSettingWindowId = (): Promise<number> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
bus.once(GET_SETTING_WINDOW_ID_RESPONSE, (id: number) => {
|
||||
resolve(id)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import { app as APP } from 'electron'
|
||||
import { getLogger } from '@core/utils/localLogger'
|
||||
import dayjs from 'dayjs'
|
||||
const STORE_PATH = APP.getPath('userData')
|
||||
const configFilePath = path.join(STORE_PATH, 'data.json')
|
||||
const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json')
|
||||
export const defaultConfigPath = configFilePath
|
||||
let _configFilePath = ''
|
||||
let hasCheckPath = false
|
||||
|
||||
const errorMsg = {
|
||||
broken: 'PicGo 配置文件损坏,已经恢复为默认配置',
|
||||
brokenButBackup: 'PicGo 配置文件损坏,已经恢复为备份配置'
|
||||
}
|
||||
|
||||
/** ensure notification list */
|
||||
if (!global.notificationList) global.notificationList = []
|
||||
|
||||
function dbChecker () {
|
||||
if (process.type !== 'renderer') {
|
||||
// db save bak
|
||||
try {
|
||||
const { dbPath, dbBackupPath } = getGalleryDBPath()
|
||||
if (fs.existsSync(dbPath)) {
|
||||
fs.copyFileSync(dbPath, dbBackupPath)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
const configFilePath = dbPathChecker()
|
||||
if (!fs.existsSync(configFilePath)) {
|
||||
return
|
||||
}
|
||||
let configFile: string = '{}'
|
||||
const optionsTpl = {
|
||||
title: '注意',
|
||||
body: ''
|
||||
}
|
||||
// config save bak
|
||||
try {
|
||||
configFile = fs.readFileSync(configFilePath, { encoding: 'utf-8' })
|
||||
JSON.parse(configFile)
|
||||
} catch (e) {
|
||||
fs.unlinkSync(configFilePath)
|
||||
if (fs.existsSync(configFileBackupPath)) {
|
||||
try {
|
||||
configFile = fs.readFileSync(configFileBackupPath, { encoding: 'utf-8' })
|
||||
JSON.parse(configFile)
|
||||
fs.writeFileSync(configFilePath, configFile, { encoding: 'utf-8' })
|
||||
const stats = fs.statSync(configFileBackupPath)
|
||||
optionsTpl.body = `${errorMsg.brokenButBackup}\n备份文件版本:${dayjs(stats.mtime).format('YYYY-MM-DD HH:mm:ss')}`
|
||||
global.notificationList?.push(optionsTpl)
|
||||
return
|
||||
} catch (e) {
|
||||
optionsTpl.body = errorMsg.broken
|
||||
global.notificationList?.push(optionsTpl)
|
||||
return
|
||||
}
|
||||
}
|
||||
optionsTpl.body = errorMsg.broken
|
||||
global.notificationList?.push(optionsTpl)
|
||||
return
|
||||
}
|
||||
fs.writeFileSync(configFileBackupPath, configFile, { encoding: 'utf-8' })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get config path
|
||||
*/
|
||||
function dbPathChecker (): string {
|
||||
if (_configFilePath) {
|
||||
return _configFilePath
|
||||
}
|
||||
// defaultConfigPath
|
||||
_configFilePath = defaultConfigPath
|
||||
// if defaultConfig path is not exit
|
||||
// do not parse the content of config
|
||||
if (!fs.existsSync(defaultConfigPath)) {
|
||||
return _configFilePath
|
||||
}
|
||||
try {
|
||||
const configString = fs.readFileSync(defaultConfigPath, { encoding: 'utf-8' })
|
||||
const config = JSON.parse(configString)
|
||||
const userConfigPath: string = config.configPath || ''
|
||||
if (userConfigPath) {
|
||||
if (fs.existsSync(userConfigPath) && userConfigPath.endsWith('.json')) {
|
||||
_configFilePath = userConfigPath
|
||||
return _configFilePath
|
||||
}
|
||||
}
|
||||
return _configFilePath
|
||||
} catch (e) {
|
||||
const picgoLogPath = path.join(defaultConfigPath, 'picgo.log')
|
||||
const logger = getLogger(picgoLogPath)
|
||||
if (!hasCheckPath) {
|
||||
const optionsTpl = {
|
||||
title: '注意',
|
||||
body: '自定义文件解析出错,请检查路径内容是否正确'
|
||||
}
|
||||
global.notificationList?.push(optionsTpl)
|
||||
hasCheckPath = true
|
||||
}
|
||||
logger('error', e)
|
||||
console.error(e)
|
||||
_configFilePath = defaultConfigPath
|
||||
return _configFilePath
|
||||
}
|
||||
}
|
||||
|
||||
function dbPathDir () {
|
||||
return path.dirname(dbPathChecker())
|
||||
}
|
||||
|
||||
function getGalleryDBPath (): {
|
||||
dbPath: string
|
||||
dbBackupPath: string
|
||||
} {
|
||||
const configPath = dbPathChecker()
|
||||
const dbPath = path.join(path.dirname(configPath), 'picgo.db')
|
||||
const dbBackupPath = path.join(path.dirname(dbPath), 'picgo.bak.db')
|
||||
return {
|
||||
dbPath,
|
||||
dbBackupPath
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
dbChecker,
|
||||
dbPathChecker,
|
||||
dbPathDir,
|
||||
getGalleryDBPath
|
||||
}
|
||||
@@ -2,34 +2,34 @@ import Datastore from 'lowdb'
|
||||
// @ts-ignore
|
||||
import LodashId from 'lodash-id'
|
||||
import FileSync from 'lowdb/adapters/FileSync'
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { remote, app } from 'electron'
|
||||
import { dbPathChecker, dbPathDir, getGalleryDBPath } from './dbChecker'
|
||||
import { DBStore } from '@picgo/store'
|
||||
|
||||
const APP = process.type === 'renderer' ? remote.app : app
|
||||
const STORE_PATH = APP.getPath('userData')
|
||||
const STORE_PATH = dbPathDir()
|
||||
|
||||
if (process.type !== 'renderer') {
|
||||
if (!fs.pathExistsSync(STORE_PATH)) {
|
||||
fs.mkdirpSync(STORE_PATH)
|
||||
}
|
||||
if (!fs.pathExistsSync(STORE_PATH)) {
|
||||
fs.mkdirpSync(STORE_PATH)
|
||||
}
|
||||
const CONFIG_PATH: string = dbPathChecker()
|
||||
const DB_PATH: string = getGalleryDBPath().dbPath
|
||||
|
||||
class DB {
|
||||
// TODO: use JSONStore with @picgo/store
|
||||
class ConfigStore {
|
||||
private db: Datastore.LowdbSync<Datastore.AdapterSync>
|
||||
constructor () {
|
||||
const adapter = new FileSync(path.join(STORE_PATH, '/data.json'))
|
||||
const adapter = new FileSync(CONFIG_PATH)
|
||||
|
||||
this.db = Datastore(adapter)
|
||||
this.db._.mixin(LodashId)
|
||||
|
||||
if (!this.db.has('uploaded').value()) {
|
||||
this.db.set('uploaded', []).write()
|
||||
}
|
||||
|
||||
if (!this.db.has('picBed').value()) {
|
||||
this.db.set('picBed', {
|
||||
current: 'weibo'
|
||||
current: 'smms', // deprecated
|
||||
uploader: 'smms',
|
||||
smms: {
|
||||
token: ''
|
||||
}
|
||||
}).write()
|
||||
}
|
||||
|
||||
@@ -42,33 +42,64 @@ class DB {
|
||||
}).write()
|
||||
}
|
||||
}
|
||||
|
||||
read () {
|
||||
return this.db.read()
|
||||
}
|
||||
|
||||
get (key = '') {
|
||||
return this.read().get(key).value()
|
||||
}
|
||||
|
||||
set (key: string, value: any) {
|
||||
return this.read().set(key, value).write()
|
||||
}
|
||||
|
||||
has (key: string) {
|
||||
return this.read().has(key).value()
|
||||
}
|
||||
|
||||
insert (key: string, value: any): void {
|
||||
// @ts-ignore
|
||||
return this.read().get(key).insert(value).write()
|
||||
}
|
||||
|
||||
unset (key: string, value: any): boolean {
|
||||
return this.read().get(key).unset(value).value()
|
||||
}
|
||||
|
||||
getById (key: string, id: string) {
|
||||
// @ts-ignore
|
||||
return this.read().get(key).getById(id).value()
|
||||
}
|
||||
|
||||
removeById (key: string, id: string) {
|
||||
// @ts-ignore
|
||||
return this.read().get(key).removeById(id).write()
|
||||
}
|
||||
|
||||
getConfigPath () {
|
||||
return CONFIG_PATH
|
||||
}
|
||||
}
|
||||
|
||||
export default new DB()
|
||||
export default new ConfigStore()
|
||||
|
||||
// v2.3.0 add gallery db
|
||||
class GalleryDB {
|
||||
private static instance: DBStore
|
||||
private constructor () {
|
||||
console.log('init gallery db')
|
||||
}
|
||||
|
||||
public static getInstance (): DBStore {
|
||||
if (!GalleryDB.instance) {
|
||||
GalleryDB.instance = new DBStore(DB_PATH, 'gallery')
|
||||
}
|
||||
return GalleryDB.instance
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
GalleryDB
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
import PicGoCore from '~/universal/types/picgo'
|
||||
import {
|
||||
app
|
||||
} from 'electron'
|
||||
import path from 'path'
|
||||
import { dbChecker, dbPathChecker } from 'apis/core/datastore/dbChecker'
|
||||
import pkg from 'root/package.json'
|
||||
// eslint-disable-next-line
|
||||
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
|
||||
const PicGo = requireFunc('picgo') as typeof PicGoCore
|
||||
const STORE_PATH = app.getPath('userData')
|
||||
|
||||
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
|
||||
const CONFIG_PATH = dbPathChecker()
|
||||
|
||||
dbChecker()
|
||||
|
||||
const picgo = new PicGo(CONFIG_PATH)
|
||||
picgo.saveConfig({
|
||||
@@ -16,4 +15,7 @@ picgo.saveConfig({
|
||||
PICGO_ENV: 'GUI'
|
||||
})
|
||||
|
||||
export default picgo as PicGoCore
|
||||
global.PICGO_GUI_VERSION = pkg.version
|
||||
picgo.GUI_VERSION = global.PICGO_GUI_VERSION
|
||||
|
||||
export default picgo! as PicGoCore
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import fs from 'fs-extra'
|
||||
import dayjs from 'dayjs'
|
||||
import util from 'util'
|
||||
|
||||
/**
|
||||
* for local log before picgo inited
|
||||
*/
|
||||
const getLogger = (logPath: string) => {
|
||||
if (!fs.existsSync(logPath)) {
|
||||
fs.ensureFileSync(logPath)
|
||||
}
|
||||
return (type: string, ...msg: any[]) => {
|
||||
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicGo ${type.toUpperCase()}] `
|
||||
msg.forEach((item: ILogArgvTypeWithError) => {
|
||||
if (typeof item === 'object' && type === 'error') {
|
||||
log += `\n------Error Stack Begin------\n${util.format(item.stack)}\n-------Error Stack End------- `
|
||||
} else {
|
||||
if (typeof item === 'object') {
|
||||
item = JSON.stringify(item)
|
||||
}
|
||||
log += `${item} `
|
||||
}
|
||||
})
|
||||
log += '\n'
|
||||
// A synchronized approach to avoid log msg sequence errors
|
||||
fs.appendFileSync(logPath, log)
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getLogger
|
||||
}
|
||||
+77
-25
@@ -4,9 +4,10 @@ import {
|
||||
Notification,
|
||||
ipcMain
|
||||
} from 'electron'
|
||||
import db from '#/datastore'
|
||||
import db, { GalleryDB } from 'apis/core/datastore'
|
||||
import { dbPathChecker, defaultConfigPath, getGalleryDBPath } from 'apis/core/datastore/dbChecker'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import pasteTemplate from '#/utils/pasteTemplate'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
import {
|
||||
getWindowId,
|
||||
@@ -15,19 +16,32 @@ import {
|
||||
import {
|
||||
SHOW_INPUT_BOX
|
||||
} from '~/universal/events/constants'
|
||||
import { DBStore } from '@picgo/store'
|
||||
|
||||
// Cross-process support may be required in the future
|
||||
class GuiApi implements IGuiApi {
|
||||
private static instance: GuiApi
|
||||
private windowId: number = -1
|
||||
private settingWindowId: number = -1
|
||||
private constructor () {
|
||||
console.log('init guiapi')
|
||||
}
|
||||
|
||||
public static getInstance (): GuiApi {
|
||||
if (!GuiApi.instance) {
|
||||
GuiApi.instance = new GuiApi()
|
||||
}
|
||||
return GuiApi.instance
|
||||
}
|
||||
|
||||
private async showSettingWindow () {
|
||||
this.settingWindowId = await getSettingWindowId()
|
||||
const settingWindow = BrowserWindow.fromId(this.settingWindowId)
|
||||
if (settingWindow.isVisible()) {
|
||||
if (settingWindow?.isVisible()) {
|
||||
return true
|
||||
}
|
||||
settingWindow.show()
|
||||
return new Promise((resolve, reject) => {
|
||||
settingWindow?.show()
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, 1000) // TODO: a better way to wait page loaded.
|
||||
@@ -35,7 +49,7 @@ class GuiApi implements IGuiApi {
|
||||
}
|
||||
|
||||
private getWebcontentsByWindowId (id: number) {
|
||||
return BrowserWindow.fromId(id).webContents
|
||||
return BrowserWindow.fromId(id)?.webContents
|
||||
}
|
||||
|
||||
async showInputBox (options: IShowInputBoxOption = {
|
||||
@@ -43,33 +57,29 @@ class GuiApi implements IGuiApi {
|
||||
placeholder: ''
|
||||
}) {
|
||||
await this.showSettingWindow()
|
||||
this.getWebcontentsByWindowId(this.settingWindowId)
|
||||
.send(SHOW_INPUT_BOX, options)
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
|
||||
return new Promise<string>((resolve) => {
|
||||
ipcMain.once(SHOW_INPUT_BOX, (event: Event, value: string) => {
|
||||
resolve(value)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
showFileExplorer (options: IShowFileExplorerOption = {}) {
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
this.windowId = await getWindowId()
|
||||
dialog.showOpenDialog(BrowserWindow.fromId(this.windowId), options, (filename: string) => {
|
||||
resolve(filename)
|
||||
})
|
||||
})
|
||||
async showFileExplorer (options: IShowFileExplorerOption = {}) {
|
||||
this.windowId = await getWindowId()
|
||||
const res = await dialog.showOpenDialog(BrowserWindow.fromId(this.windowId)!, options)
|
||||
return res.filePaths?.[0]
|
||||
}
|
||||
|
||||
async upload (input: IUploadOption) {
|
||||
this.windowId = await getWindowId()
|
||||
const webContents = this.getWebcontentsByWindowId(this.windowId)
|
||||
const imgs = await uploader.setWebContents(webContents).upload(input)
|
||||
const imgs = await uploader.setWebContents(webContents!).upload(input)
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
let pasteText = ''
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
pasteText += pasteTemplate(pasteStyle, imgs[i]) + '\r\n'
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: '上传成功',
|
||||
body: imgs[i].imgUrl as string,
|
||||
@@ -78,11 +88,11 @@ class GuiApi implements IGuiApi {
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, i * 100)
|
||||
db.insert('uploaded', imgs[i])
|
||||
await GalleryDB.getInstance().insert(imgs[i])
|
||||
}
|
||||
handleCopyUrl(pasteText)
|
||||
webContents.send('uploadFiles', imgs)
|
||||
webContents.send('updateGallery')
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
webContents?.send('uploadFiles', imgs)
|
||||
webContents?.send('updateGallery')
|
||||
return imgs
|
||||
}
|
||||
return []
|
||||
@@ -105,10 +115,10 @@ class GuiApi implements IGuiApi {
|
||||
type: 'info',
|
||||
buttons: ['Yes', 'No']
|
||||
}) {
|
||||
return new Promise<IShowMessageBoxResult>(async (resolve, reject) => {
|
||||
return new Promise<IShowMessageBoxResult>(async (resolve) => {
|
||||
this.windowId = await getWindowId()
|
||||
dialog.showMessageBox(
|
||||
BrowserWindow.fromId(this.windowId),
|
||||
BrowserWindow.fromId(this.windowId)!,
|
||||
options
|
||||
).then((res) => {
|
||||
resolve({
|
||||
@@ -118,6 +128,48 @@ class GuiApi implements IGuiApi {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* get picgo config/data path
|
||||
*/
|
||||
async getConfigPath () {
|
||||
const currentConfigPath = dbPathChecker()
|
||||
const galleryDBPath = getGalleryDBPath().dbPath
|
||||
return {
|
||||
defaultConfigPath,
|
||||
currentConfigPath,
|
||||
galleryDBPath
|
||||
}
|
||||
}
|
||||
|
||||
get galleryDB (): DBStore {
|
||||
return new Proxy<DBStore>(GalleryDB.getInstance(), {
|
||||
get (target, prop: keyof DBStore) {
|
||||
if (prop === 'removeById') {
|
||||
return new Proxy(GalleryDB.getInstance().removeById, {
|
||||
apply (target, ctx, args) {
|
||||
return new Promise((resolve) => {
|
||||
const guiApi = GuiApi.getInstance()
|
||||
guiApi.showMessageBox({
|
||||
title: '警告',
|
||||
message: '有插件正在试图删除一些相册图片,是否继续',
|
||||
type: 'info',
|
||||
buttons: ['Yes', 'No']
|
||||
}).then(res => {
|
||||
if (res.result === 0) {
|
||||
resolve(Reflect.apply(target, ctx, args))
|
||||
} else {
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
return Reflect.get(target, prop)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default GuiApi
|
||||
|
||||
@@ -28,7 +28,7 @@ function initEventCenter () {
|
||||
[GET_SETTING_WINDOW_ID]: busCallGetSettingWindowId,
|
||||
[CREATE_APP_MENU]: createMenu
|
||||
}
|
||||
for (let i in eventList) {
|
||||
for (const i in eventList) {
|
||||
bus.on(i, eventList[i])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
import {
|
||||
app,
|
||||
ipcMain,
|
||||
shell,
|
||||
Notification,
|
||||
IpcMainEvent
|
||||
IpcMainEvent,
|
||||
BrowserWindow
|
||||
} from 'electron'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import pasteTemplate from '#/utils/pasteTemplate'
|
||||
import db from '#/datastore'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import server from '~/main/server'
|
||||
import getPicBeds from '~/main/utils/getPicBeds'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
import bus from '@core/bus'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
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
|
||||
} from '#/events/constants'
|
||||
import {
|
||||
uploadClipboardFiles,
|
||||
uploadChoosedFiles
|
||||
} from '~/main/apis/app/uploader/apis'
|
||||
import picgoCoreIPC from './picgoCoreIPC'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
import { buildMainPageMenu, buildMiniPageMenu, buildPluginPageMenu, buildUploadPageMenu } from './remotes/menu'
|
||||
import path from 'path'
|
||||
|
||||
const STORE_PATH = app.getPath('userData')
|
||||
|
||||
export default {
|
||||
listen () {
|
||||
@@ -30,7 +49,7 @@ export default {
|
||||
const img = await uploader.setWebContents(trayWindow.webContents).upload()
|
||||
if (img !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0]))
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: '上传成功',
|
||||
body: img[0].imgUrl!,
|
||||
@@ -38,7 +57,7 @@ export default {
|
||||
icon: img[0].imgUrl
|
||||
})
|
||||
notification.show()
|
||||
db.insert('uploaded', img[0])
|
||||
await GalleryDB.getInstance().insert(img[0])
|
||||
trayWindow.webContents.send('clipboardFiles', [])
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
|
||||
@@ -139,6 +158,65 @@ export default {
|
||||
ipcMain.on('updateServer', () => {
|
||||
server.restart()
|
||||
})
|
||||
ipcMain.on(OPEN_DEVTOOLS, (event: IpcMainEvent) => {
|
||||
event.sender.openDevTools()
|
||||
})
|
||||
// menu & window methods
|
||||
ipcMain.on(SHOW_MINI_PAGE_MENU, () => {
|
||||
const window = windowManager.get(IWindowList.MINI_WINDOW)!
|
||||
const menu = buildMiniPageMenu()
|
||||
menu.popup({
|
||||
window
|
||||
})
|
||||
})
|
||||
ipcMain.on(SHOW_MAIN_PAGE_MENU, () => {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const menu = buildMainPageMenu()
|
||||
menu.popup({
|
||||
window
|
||||
})
|
||||
})
|
||||
ipcMain.on(SHOW_UPLOAD_PAGE_MENU, () => {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const menu = buildUploadPageMenu()
|
||||
menu.popup({
|
||||
window
|
||||
})
|
||||
})
|
||||
ipcMain.on(SHOW_PLUGIN_PAGE_MENU, (evt: IpcMainEvent, plugin: IPicGoPlugin) => {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const menu = buildPluginPageMenu(plugin)
|
||||
menu.popup({
|
||||
window
|
||||
})
|
||||
})
|
||||
ipcMain.on(MINIMIZE_WINDOW, () => {
|
||||
const window = BrowserWindow.getFocusedWindow()
|
||||
window?.minimize()
|
||||
})
|
||||
ipcMain.on(CLOSE_WINDOW, () => {
|
||||
const window = BrowserWindow.getFocusedWindow()
|
||||
if (process.platform === 'linux') {
|
||||
window?.hide()
|
||||
} else {
|
||||
window?.close()
|
||||
}
|
||||
})
|
||||
ipcMain.on(OPEN_USER_STORE_FILE, (evt: IpcMainEvent, filePath: string) => {
|
||||
const abFilePath = path.join(STORE_PATH, filePath)
|
||||
shell.openPath(abFilePath)
|
||||
})
|
||||
ipcMain.on(OPEN_URL, (evt: IpcMainEvent, url: string) => {
|
||||
shell.openExternal(url)
|
||||
})
|
||||
ipcMain.on(RELOAD_APP, () => {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
})
|
||||
ipcMain.on(SET_MINI_WINDOW_POS, (evt: IpcMainEvent, pos: IMiniWindowPos) => {
|
||||
const window = BrowserWindow.getFocusedWindow()
|
||||
window?.setBounds(pos)
|
||||
})
|
||||
},
|
||||
dispose () {}
|
||||
}
|
||||
|
||||
+231
-114
@@ -5,24 +5,41 @@ import {
|
||||
shell,
|
||||
IpcMainEvent,
|
||||
ipcMain,
|
||||
app
|
||||
clipboard
|
||||
} from 'electron'
|
||||
import PicGoCore from '~/universal/types/picgo'
|
||||
import { IPicGoHelperType } from '#/types/enum'
|
||||
import shortKeyHandler from '../apis/app/shortKey/shortKeyHandler'
|
||||
import { IPasteStyle, IPicGoHelperType } from '#/types/enum'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
import picgo from '@core/picgo'
|
||||
import { handleStreamlinePluginName } from '~/universal/utils/common'
|
||||
import { IGuiMenuItem } from 'picgo/dist/src/types'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import { showNotification } from '~/main/utils/common'
|
||||
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
|
||||
import {
|
||||
PICGO_SAVE_CONFIG,
|
||||
PICGO_GET_CONFIG,
|
||||
PICGO_GET_DB,
|
||||
PICGO_INSERT_DB,
|
||||
PICGO_INSERT_MANY_DB,
|
||||
PICGO_UPDATE_BY_ID_DB,
|
||||
PICGO_GET_BY_ID_DB,
|
||||
PICGO_REMOVE_BY_ID_DB,
|
||||
PICGO_OPEN_FILE,
|
||||
PASTE_TEXT
|
||||
} from '#/events/constants'
|
||||
|
||||
import { GalleryDB } from 'apis/core/datastore'
|
||||
import { IObject, IFilter } from '@picgo/store/dist/types'
|
||||
import pasteTemplate from '../utils/pasteTemplate'
|
||||
|
||||
// eslint-disable-next-line
|
||||
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
|
||||
// const PluginHandler = requireFunc('picgo/dist/lib/PluginHandler').default
|
||||
const STORE_PATH = app.getPath('userData')
|
||||
const STORE_PATH = path.dirname(dbPathChecker())
|
||||
// const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
|
||||
|
||||
type PicGoNotice = {
|
||||
title: string,
|
||||
body: string[]
|
||||
}
|
||||
|
||||
interface GuiMenuItem {
|
||||
label: string
|
||||
handle: (arg0: PicGoCore, arg1: GuiApi) => Promise<void>
|
||||
@@ -45,7 +62,7 @@ const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => {
|
||||
}
|
||||
|
||||
const handleConfigWithFunction = (config: any[]) => {
|
||||
for (let i in config) {
|
||||
for (const i in config) {
|
||||
if (typeof config[i].default === 'function') {
|
||||
config[i].default = config[i].default()
|
||||
}
|
||||
@@ -56,110 +73,118 @@ const handleConfigWithFunction = (config: any[]) => {
|
||||
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: IGuiMenuItem[] = []
|
||||
if (plugin.guiMenu) {
|
||||
menu = plugin.guiMenu(picgo)
|
||||
}
|
||||
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) => {
|
||||
const pluginList = picgo.pluginLoader.getList()
|
||||
const list = []
|
||||
for (let 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 = []
|
||||
if (plugin.guiMenu) {
|
||||
menu = plugin.guiMenu(picgo)
|
||||
}
|
||||
let gui = false
|
||||
if (pluginPKG.keywords && pluginPKG.keywords.length > 0) {
|
||||
if (pluginPKG.keywords.includes('picgo-gui-plugin')) {
|
||||
gui = true
|
||||
}
|
||||
}
|
||||
const obj: IPicGoPlugin = {
|
||||
name: pluginList[i].replace(/picgo-plugin-/, ''),
|
||||
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: {
|
||||
name: pluginList[i].replace(/picgo-plugin-/, ''),
|
||||
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)
|
||||
}
|
||||
const list = getPluginList()
|
||||
event.sender.send('pluginList', list)
|
||||
picgo.cmd.program.removeAllListeners()
|
||||
})
|
||||
}
|
||||
|
||||
const handlePluginInstall = () => {
|
||||
ipcMain.on('installPlugin', async (event: IpcMainEvent, msg: string) => {
|
||||
ipcMain.on('installPlugin', async (event: IpcMainEvent, fullName: string) => {
|
||||
const dispose = handleNPMError()
|
||||
picgo.once('installSuccess', (notice: PicGoNotice) => {
|
||||
event.sender.send('installSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
|
||||
shortKeyHandler.registerPluginShortKey(notice.body[0])
|
||||
picgo.removeAllListeners('installFailed')
|
||||
dispose()
|
||||
const res = await picgo.pluginHandler.install([fullName])
|
||||
event.sender.send('installPlugin', {
|
||||
success: res.success,
|
||||
body: fullName,
|
||||
errMsg: res.success ? '' : res.body
|
||||
})
|
||||
picgo.once('installFailed', () => {
|
||||
picgo.removeAllListeners('installSuccess')
|
||||
dispose()
|
||||
})
|
||||
await picgo.pluginHandler.install([msg])
|
||||
picgo.cmd.program.removeAllListeners()
|
||||
if (res.success) {
|
||||
shortKeyHandler.registerPluginShortKey(res.body[0])
|
||||
} else {
|
||||
showNotification({
|
||||
title: '插件安装失败',
|
||||
body: res.body as string
|
||||
})
|
||||
}
|
||||
event.sender.send('hideLoading')
|
||||
dispose()
|
||||
})
|
||||
}
|
||||
|
||||
const handlePluginUninstall = () => {
|
||||
ipcMain.on('uninstallPlugin', async (event: IpcMainEvent, msg: string) => {
|
||||
const dispose = handleNPMError()
|
||||
picgo.once('uninstallSuccess', (notice: PicGoNotice) => {
|
||||
event.sender.send('uninstallSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
|
||||
shortKeyHandler.unregisterPluginShortKey(notice.body[0])
|
||||
picgo.removeAllListeners('uninstallFailed')
|
||||
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: '插件卸载失败',
|
||||
body: res.body as string
|
||||
})
|
||||
picgo.once('uninstallFailed', () => {
|
||||
picgo.removeAllListeners('uninstallSuccess')
|
||||
dispose()
|
||||
})
|
||||
await picgo.pluginHandler.uninstall([msg])
|
||||
picgo.cmd.program.removeAllListeners()
|
||||
})
|
||||
}
|
||||
window.webContents.send('hideLoading')
|
||||
dispose()
|
||||
}
|
||||
|
||||
const handlePluginUpdate = () => {
|
||||
ipcMain.on('updatePlugin', async (event: IpcMainEvent, msg: string) => {
|
||||
const dispose = handleNPMError()
|
||||
picgo.once('updateSuccess', (notice: { body: string[], title: string }) => {
|
||||
event.sender.send('updateSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
|
||||
picgo.removeAllListeners('updateFailed')
|
||||
dispose()
|
||||
const handlePluginUpdate = async (fullName: string) => {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const dispose = handleNPMError()
|
||||
const res = await picgo.pluginHandler.update([fullName])
|
||||
if (res.success) {
|
||||
window.webContents.send('updateSuccess', res.body[0])
|
||||
} else {
|
||||
showNotification({
|
||||
title: '插件更新失败',
|
||||
body: res.body as string
|
||||
})
|
||||
picgo.once('updateFailed', () => {
|
||||
picgo.removeAllListeners('updateSuccess')
|
||||
dispose()
|
||||
})
|
||||
await picgo.pluginHandler.update([msg])
|
||||
picgo.cmd.program.removeAllListeners()
|
||||
})
|
||||
}
|
||||
window.webContents.send('hideLoading')
|
||||
dispose()
|
||||
}
|
||||
|
||||
const handleNPMError = (): IDispose => {
|
||||
@@ -182,26 +207,25 @@ const handleNPMError = (): IDispose => {
|
||||
|
||||
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 = handleConfigWithFunction(picgo.helper.uploader.get(type).config(picgo))
|
||||
const name = picgo.helper.uploader.get(type)?.name || type
|
||||
if (picgo.helper.uploader.get(type)?.config) {
|
||||
const config = handleConfigWithFunction(picgo.helper.uploader.get(type)!.config(picgo))
|
||||
event.sender.send('getPicBedConfig', config, name)
|
||||
} else {
|
||||
event.sender.send('getPicBedConfig', [], name)
|
||||
}
|
||||
picgo.cmd.program.removeAllListeners()
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: remove it
|
||||
const handlePluginActions = () => {
|
||||
ipcMain.on('pluginActions', (event: IpcMainEvent, name: string, label: string) => {
|
||||
const plugin = picgo.pluginLoader.getPlugin(`picgo-plugin-${name}`)
|
||||
const guiApi = new GuiApi()
|
||||
if (plugin.guiMenu && plugin.guiMenu(picgo).length > 0) {
|
||||
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)
|
||||
item.handle(picgo, GuiApi.getInstance())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -210,28 +234,121 @@ const handlePluginActions = () => {
|
||||
|
||||
const handleRemoveFiles = () => {
|
||||
ipcMain.on('removeFiles', (event: IpcMainEvent, files: ImgInfo[]) => {
|
||||
const guiApi = new GuiApi()
|
||||
setTimeout(() => {
|
||||
picgo.emit('remove', files, guiApi)
|
||||
picgo.emit('remove', files, GuiApi.getInstance())
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
const handlePicGoSaveData = () => {
|
||||
ipcMain.on('picgoSaveData', (event: IpcMainEvent, data: IObj) => {
|
||||
const handlePicGoSaveConfig = () => {
|
||||
ipcMain.on(PICGO_SAVE_CONFIG, (event: IpcMainEvent, data: IObj) => {
|
||||
picgo.saveConfig(data)
|
||||
})
|
||||
}
|
||||
|
||||
const handlePicGoGetConfig = () => {
|
||||
ipcMain.on(PICGO_GET_CONFIG, (event: IpcMainEvent, key: string | undefined, callbackId: string) => {
|
||||
const result = picgo.getConfig(key)
|
||||
event.sender.send(PICGO_GET_CONFIG, result, callbackId)
|
||||
})
|
||||
}
|
||||
|
||||
const handleImportLocalPlugin = () => {
|
||||
ipcMain.on('importLocalPlugin', 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) {
|
||||
const list = getPluginList()
|
||||
event.sender.send('pluginList', list)
|
||||
showNotification({
|
||||
title: '导入插件成功',
|
||||
body: ''
|
||||
})
|
||||
} else {
|
||||
showNotification({
|
||||
title: '导入插件失败',
|
||||
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>('settings.pasteStyle') || IPasteStyle.MARKDOWN
|
||||
const customLink = picgo.getConfig<string>('settings.customLink')
|
||||
const txt = pasteTemplate(pasteStyle, item, customLink)
|
||||
if (copy) {
|
||||
clipboard.writeText(txt)
|
||||
}
|
||||
return txt
|
||||
})
|
||||
}
|
||||
|
||||
const handleOpenFile = () => {
|
||||
ipcMain.on(PICGO_OPEN_FILE, (event: IpcMainEvent, fileName: string) => {
|
||||
const abFilePath = path.join(STORE_PATH, fileName)
|
||||
shell.openPath(abFilePath)
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
listen () {
|
||||
handleGetPluginList()
|
||||
handlePluginInstall()
|
||||
handlePluginUninstall()
|
||||
handlePluginUpdate()
|
||||
handleGetPicBedConfig()
|
||||
handlePluginActions()
|
||||
handleRemoveFiles()
|
||||
handlePicGoSaveData()
|
||||
}
|
||||
handlePicGoSaveConfig()
|
||||
handlePicGoGetConfig()
|
||||
handlePicGoGalleryDB()
|
||||
handleImportLocalPlugin()
|
||||
handleOpenFile()
|
||||
},
|
||||
// TODO: separate to single file
|
||||
handlePluginUninstall,
|
||||
handlePluginUpdate
|
||||
}
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import { Menu, BrowserWindow, app, dialog } from 'electron'
|
||||
import getPicBeds from '~/main/utils/getPicBeds'
|
||||
import picgo from '@core/picgo'
|
||||
import {
|
||||
uploadClipboardFiles
|
||||
} from '~/main/apis/app/uploader/apis'
|
||||
import { privacyManager } from '~/main/utils/privacyManager'
|
||||
import pkg from 'root/package.json'
|
||||
import GuiApi from 'apis/gui'
|
||||
import PicGoCore from '~/universal/types/picgo'
|
||||
import { PICGO_CONFIG_PLUGIN, PICGO_HANDLE_PLUGIN_ING, PICGO_TOGGLE_PLUGIN } from '~/universal/events/constants'
|
||||
import picgoCoreIPC from '~/main/events/picgoCoreIPC'
|
||||
|
||||
interface GuiMenuItem {
|
||||
label: string
|
||||
handle: (arg0: PicGoCore, arg1: GuiApi) => Promise<void>
|
||||
}
|
||||
|
||||
const buildMiniPageMenu = () => {
|
||||
const picBeds = getPicBeds()
|
||||
const current = picgo.getConfig('picBed.uploader')
|
||||
const submenu = picBeds.filter(item => item.visible).map(item => {
|
||||
return {
|
||||
label: item.name,
|
||||
type: 'radio',
|
||||
checked: current === item.type,
|
||||
click () {
|
||||
picgo.saveConfig({
|
||||
'picBed.current': item.type,
|
||||
'picBed.uploader': item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
const template = [
|
||||
{
|
||||
label: '打开详细窗口',
|
||||
click () {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.show()
|
||||
if (windowManager.has(IWindowList.MINI_WINDOW)) {
|
||||
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '选择默认图床',
|
||||
type: 'submenu',
|
||||
submenu
|
||||
},
|
||||
{
|
||||
label: '剪贴板图片上传',
|
||||
click () {
|
||||
uploadClipboardFiles()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '隐藏窗口',
|
||||
click () {
|
||||
BrowserWindow.getFocusedWindow()!.hide()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '隐私协议',
|
||||
click () {
|
||||
privacyManager.show(false)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '重启应用',
|
||||
click () {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
}
|
||||
},
|
||||
{
|
||||
role: 'quit',
|
||||
label: '退出'
|
||||
}
|
||||
]
|
||||
// @ts-ignore
|
||||
return Menu.buildFromTemplate(template)
|
||||
}
|
||||
|
||||
const buildMainPageMenu = () => {
|
||||
const template = [
|
||||
{
|
||||
label: '关于',
|
||||
click () {
|
||||
dialog.showMessageBox({
|
||||
title: 'PicGo',
|
||||
message: 'PicGo',
|
||||
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '赞助PicGo',
|
||||
click () {
|
||||
// TODO: show donation
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '生成图床配置二维码',
|
||||
click () {
|
||||
// TODO: qrcode
|
||||
// _this.qrcodeVisible = true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '隐私协议',
|
||||
click () {
|
||||
privacyManager.show(false)
|
||||
}
|
||||
}
|
||||
]
|
||||
// @ts-ignore
|
||||
return Menu.buildFromTemplate(template)
|
||||
}
|
||||
|
||||
const buildUploadPageMenu = () => {
|
||||
const picBeds = getPicBeds()
|
||||
const currentPicBed = picgo.getConfig('picBed.uploader')
|
||||
const submenu = picBeds.filter(item => item.visible).map(item => {
|
||||
return {
|
||||
label: item.name,
|
||||
type: 'radio',
|
||||
checked: currentPicBed === item.type,
|
||||
click () {
|
||||
picgo.saveConfig({
|
||||
'picBed.current': item.type,
|
||||
'picBed.uploader': item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
return Menu.buildFromTemplate(submenu)
|
||||
}
|
||||
|
||||
// TODO: separate to single file
|
||||
|
||||
const handleRestoreState = (item: string, name: string): void => {
|
||||
if (item === 'uploader') {
|
||||
const current = picgo.getConfig('picBed.current')
|
||||
if (current === name) {
|
||||
picgo.saveConfig({
|
||||
'picBed.current': 'smms',
|
||||
'picBed.uploader': 'smms'
|
||||
})
|
||||
}
|
||||
}
|
||||
if (item === 'transformer') {
|
||||
const current = picgo.getConfig('picBed.transformer')
|
||||
if (current === name) {
|
||||
picgo.saveConfig({
|
||||
'picBed.transformer': 'path'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
const menu = [{
|
||||
label: '启用插件',
|
||||
enabled: !plugin.enabled,
|
||||
click () {
|
||||
picgo.saveConfig({
|
||||
[`picgoPlugins.${plugin.fullName}`]: true
|
||||
})
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, true)
|
||||
}
|
||||
}, {
|
||||
label: '禁用插件',
|
||||
enabled: plugin.enabled,
|
||||
click () {
|
||||
picgo.saveConfig({
|
||||
[`picgoPlugins.${plugin.fullName}`]: false
|
||||
})
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
|
||||
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, false)
|
||||
if (plugin.config.transformer.name) {
|
||||
handleRestoreState('transformer', plugin.config.transformer.name)
|
||||
}
|
||||
if (plugin.config.uploader.name) {
|
||||
handleRestoreState('uploader', plugin.config.uploader.name)
|
||||
}
|
||||
}
|
||||
}, {
|
||||
label: '卸载插件',
|
||||
click () {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
|
||||
picgoCoreIPC.handlePluginUninstall(plugin.fullName)
|
||||
}
|
||||
}, {
|
||||
label: '更新插件',
|
||||
click () {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
|
||||
picgoCoreIPC.handlePluginUpdate(plugin.fullName)
|
||||
}
|
||||
}]
|
||||
for (const i in plugin.config) {
|
||||
if (plugin.config[i].config.length > 0) {
|
||||
const obj = {
|
||||
label: `配置${i} - ${plugin.config[i].fullName || plugin.config[i].name}`,
|
||||
click () {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const currentType = i
|
||||
const configName = plugin.config[i].fullName || plugin.config[i].name
|
||||
const config = plugin.config[i].config
|
||||
window.webContents.send(PICGO_CONFIG_PLUGIN, currentType, configName, config)
|
||||
}
|
||||
}
|
||||
menu.push(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// handle transformer
|
||||
if (plugin.config.transformer.name) {
|
||||
const currentTransformer = picgo.getConfig<string>('picBed.transformer') || 'path'
|
||||
const pluginTransformer = plugin.config.transformer.name
|
||||
const obj = {
|
||||
label: `${currentTransformer === pluginTransformer ? '禁用' : '启用'}transformer - ${plugin.config.transformer.name}`,
|
||||
click () {
|
||||
const transformer = plugin.config.transformer.name
|
||||
const currentTransformer = picgo.getConfig<string>('picBed.transformer') || 'path'
|
||||
if (currentTransformer === transformer) {
|
||||
picgo.saveConfig({
|
||||
'picBed.transformer': 'path'
|
||||
})
|
||||
} else {
|
||||
picgo.saveConfig({
|
||||
'picBed.transformer': transformer
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
menu.push(obj)
|
||||
}
|
||||
|
||||
// plugin custom menus
|
||||
if (plugin.guiMenu) {
|
||||
menu.push({
|
||||
// @ts-ignore
|
||||
type: 'separator'
|
||||
})
|
||||
for (const i of plugin.guiMenu) {
|
||||
menu.push({
|
||||
label: i.label,
|
||||
click () {
|
||||
// ipcRenderer.send('pluginActions', plugin.fullName, i.label)
|
||||
const picgPlugin = picgo.pluginLoader.getPlugin(plugin.fullName)
|
||||
if (picgPlugin?.guiMenu?.(picgo)?.length) {
|
||||
const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo)
|
||||
menu.forEach(item => {
|
||||
if (item.label === i.label) {
|
||||
item.handle(picgo, GuiApi.getInstance())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return Menu.buildFromTemplate(menu)
|
||||
}
|
||||
|
||||
export {
|
||||
buildMiniPageMenu,
|
||||
buildMainPageMenu,
|
||||
buildUploadPageMenu,
|
||||
buildPluginPageMenu
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import fse from 'fs-extra'
|
||||
import path from 'path'
|
||||
import dayjs from 'dayjs'
|
||||
import util from 'util'
|
||||
import { dbPathDir } from 'apis/core/datastore/dbChecker'
|
||||
const STORE_PATH = dbPathDir()
|
||||
const LOG_PATH = path.join(STORE_PATH, '/picgo.log')
|
||||
|
||||
// since the error may occur in picgo-core
|
||||
// so we can't use the log from picgo
|
||||
export const loggerWriter = (error: Error) => {
|
||||
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicGo ERROR] startup error`
|
||||
if (error?.stack) {
|
||||
log += `\n------Error Stack Begin------\n${util.format(error.stack)}\n-------Error Stack End-------\n`
|
||||
} else {
|
||||
const msg = JSON.stringify(error)
|
||||
log += `${msg}\n`
|
||||
}
|
||||
fse.appendFileSync(LOG_PATH, log)
|
||||
}
|
||||
|
||||
const handleProcessError = (error: Error) => {
|
||||
console.error(error)
|
||||
loggerWriter(error)
|
||||
}
|
||||
|
||||
process.on('uncaughtException', error => {
|
||||
handleProcessError(error)
|
||||
})
|
||||
|
||||
process.on('unhandledRejection', (error: any) => {
|
||||
handleProcessError(error)
|
||||
})
|
||||
+63
-34
@@ -1,12 +1,14 @@
|
||||
import './errorHandler'
|
||||
import {
|
||||
app,
|
||||
globalShortcut,
|
||||
protocol
|
||||
protocol,
|
||||
Notification
|
||||
} from 'electron'
|
||||
import {
|
||||
createProtocol,
|
||||
installVueDevtools
|
||||
createProtocol
|
||||
} from 'vue-cli-plugin-electron-builder/lib'
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
import beforeOpen from '~/main/utils/beforeOpen'
|
||||
import fixPath from 'fix-path'
|
||||
import ipcList from '~/main/events/ipcList'
|
||||
@@ -14,7 +16,8 @@ import busEventList from '~/main/events/busEventList'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import {
|
||||
updateShortKeyFromVersion212
|
||||
updateShortKeyFromVersion212,
|
||||
migrateGalleryFromVersion230
|
||||
} from '~/main/migrate'
|
||||
import {
|
||||
uploadChoosedFiles,
|
||||
@@ -27,12 +30,33 @@ import server from '~/main/server/index'
|
||||
import updateChecker from '~/main/utils/updateChecker'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
import { getUploadFiles } from '~/main/utils/handleArgv'
|
||||
import db from '#/datastore'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import bus from '@core/bus'
|
||||
import { privacyManager } from '~/main/utils/privacyManager'
|
||||
import logger from 'apis/core/picgo/logger'
|
||||
import picgo from 'apis/core/picgo'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
const handleStartUpFiles = (argv: string[], cwd: string) => {
|
||||
const files = getUploadFiles(argv, cwd, logger)
|
||||
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动
|
||||
if (files === null) {
|
||||
logger.info('cli -> uploading file from clipboard')
|
||||
uploadClipboardFiles()
|
||||
} else {
|
||||
logger.info('cli -> uploading files from cli', ...files.map(item => item.path))
|
||||
const win = windowManager.getAvailableWindow()
|
||||
uploadChoosedFiles(win.webContents, files)
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class LifeCycle {
|
||||
private beforeReady () {
|
||||
private async beforeReady () {
|
||||
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
|
||||
// fix the $PATH in macOS
|
||||
fixPath()
|
||||
@@ -40,23 +64,28 @@ class LifeCycle {
|
||||
ipcList.listen()
|
||||
busEventList.listen()
|
||||
updateShortKeyFromVersion212(db, db.get('settings.shortKey'))
|
||||
await migrateGalleryFromVersion230(db, GalleryDB.getInstance(), picgo)
|
||||
}
|
||||
|
||||
private onReady () {
|
||||
app.on('ready', async () => {
|
||||
const readyFunction = async () => {
|
||||
console.log('on ready')
|
||||
createProtocol('picgo')
|
||||
if (isDevelopment && !process.env.IS_TEST) {
|
||||
// Install Vue Devtools
|
||||
try {
|
||||
await installVueDevtools()
|
||||
} catch (e) {
|
||||
await installExtension(VUEJS_DEVTOOLS)
|
||||
} catch (e: any) {
|
||||
console.error('Vue Devtools failed to install:', e.toString())
|
||||
}
|
||||
}
|
||||
const res = await privacyManager.init()
|
||||
if (!res) {
|
||||
return app.quit()
|
||||
}
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
if (process.platform === 'darwin' || process.platform === 'win32') {
|
||||
createTray()
|
||||
}
|
||||
createTray()
|
||||
db.set('needReload', false)
|
||||
updateChecker()
|
||||
// 不需要阻塞
|
||||
@@ -65,29 +94,29 @@ class LifeCycle {
|
||||
})
|
||||
server.startup()
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
let files = getUploadFiles()
|
||||
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动
|
||||
if (files === null) {
|
||||
uploadClipboardFiles()
|
||||
} else {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
uploadChoosedFiles(win.webContents, files)
|
||||
}
|
||||
handleStartUpFiles(process.argv, process.cwd())
|
||||
}
|
||||
|
||||
if (global.notificationList && global.notificationList?.length > 0) {
|
||||
while (global.notificationList?.length) {
|
||||
const option = global.notificationList.pop()
|
||||
const notice = new Notification(option!)
|
||||
notice.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!app.isReady()) {
|
||||
app.on('ready', readyFunction)
|
||||
} else {
|
||||
readyFunction()
|
||||
}
|
||||
}
|
||||
|
||||
private onRunning () {
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
let files = getUploadFiles(commandLine, workingDirectory)
|
||||
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动
|
||||
if (files === null) {
|
||||
uploadClipboardFiles()
|
||||
} else {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
uploadChoosedFiles(win.webContents, files)
|
||||
}
|
||||
} else {
|
||||
logger.info('detect second instance')
|
||||
const result = handleStartUpFiles(commandLine, workingDirectory)
|
||||
if (!result) {
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
if (settingWindow.isMinimized()) {
|
||||
@@ -117,6 +146,7 @@ class LifeCycle {
|
||||
process.env.XDG_CURRENT_DESKTOP = 'Unity'
|
||||
}
|
||||
}
|
||||
|
||||
private onQuit () {
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
@@ -135,23 +165,22 @@ class LifeCycle {
|
||||
process.on('message', data => {
|
||||
if (data === 'graceful-exit') {
|
||||
app.quit()
|
||||
server.shutdown()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
process.on('SIGTERM', () => {
|
||||
app.quit()
|
||||
server.shutdown()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
launchApp () {
|
||||
|
||||
async launchApp () {
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
this.beforeReady()
|
||||
await this.beforeReady()
|
||||
this.onReady()
|
||||
this.onRunning()
|
||||
this.onQuit()
|
||||
|
||||
+38
-10
@@ -1,9 +1,22 @@
|
||||
import DB from '#/datastore'
|
||||
import { DBStore } from '@picgo/store'
|
||||
import ConfigStore from '~/main/apis/core/datastore'
|
||||
import path from 'path'
|
||||
import fse from 'fs-extra'
|
||||
import PicGoCore from '#/types/picgo'
|
||||
// from v2.1.2
|
||||
const updateShortKeyFromVersion212 = (db: typeof DB, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
|
||||
let needUpgrade = false
|
||||
const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
|
||||
// #557 极端情况可能会出现配置不存在,需要重新写入
|
||||
if (shortKeyConfig === undefined) {
|
||||
const defaultShortKeyConfig = {
|
||||
enable: true,
|
||||
key: 'CommandOrControl+Shift+P',
|
||||
name: 'upload',
|
||||
label: '快捷上传'
|
||||
}
|
||||
db.set('settings.shortKey[picgo:upload]', defaultShortKeyConfig)
|
||||
return true
|
||||
}
|
||||
if (shortKeyConfig.upload) {
|
||||
needUpgrade = true
|
||||
// @ts-ignore
|
||||
shortKeyConfig['picgo:upload'] = {
|
||||
enable: true,
|
||||
@@ -11,16 +24,31 @@ const updateShortKeyFromVersion212 = (db: typeof DB, shortKeyConfig: IShortKeyCo
|
||||
name: 'upload',
|
||||
label: '快捷上传'
|
||||
}
|
||||
// @ts-ignore
|
||||
delete shortKeyConfig.upload
|
||||
}
|
||||
if (needUpgrade) {
|
||||
db.set('settings.shortKey', shortKeyConfig)
|
||||
return shortKeyConfig
|
||||
} else {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const migrateGalleryFromVersion230 = async (configDB: typeof ConfigStore, galleryDB: DBStore, picgo: PicGoCore) => {
|
||||
const originGallery: ImgInfo[] = configDB.get('uploaded')
|
||||
const configPath = configDB.getConfigPath()
|
||||
const configBakPath = path.join(path.dirname(configPath), 'config.bak.json')
|
||||
// migrate gallery from config to gallery db
|
||||
if (originGallery && Array.isArray(originGallery) && originGallery?.length > 0) {
|
||||
if (fse.existsSync(configBakPath)) {
|
||||
fse.copyFileSync(configPath, configBakPath)
|
||||
}
|
||||
await galleryDB.insertMany(originGallery)
|
||||
picgo.saveConfig({
|
||||
uploaded: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
updateShortKeyFromVersion212
|
||||
updateShortKeyFromVersion212,
|
||||
migrateGalleryFromVersion230
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class Server {
|
||||
private httpServer: http.Server
|
||||
private config: IServerConfig
|
||||
constructor () {
|
||||
let config = picgo.getConfig('settings.server')
|
||||
let config = picgo.getConfig<IServerConfig>('settings.server')
|
||||
const result = this.checkIfConfigIsValid(config)
|
||||
if (result) {
|
||||
this.config = config
|
||||
@@ -29,6 +29,7 @@ class Server {
|
||||
}
|
||||
this.httpServer = http.createServer(this.handleRequest)
|
||||
}
|
||||
|
||||
private checkIfConfigIsValid (config: IObj | undefined) {
|
||||
if (config && config.port && config.host && (config.enable !== undefined)) {
|
||||
return true
|
||||
@@ -36,9 +37,11 @@ class Server {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private handleRequest = (request: http.IncomingMessage, response: http.ServerResponse) => {
|
||||
if (request.method === 'POST') {
|
||||
if (!routers.getHandler(request.url!)) {
|
||||
logger.warn(`[PicGo Server] don't support [${request.url}] url`)
|
||||
handleResponse({
|
||||
response,
|
||||
statusCode: 404,
|
||||
@@ -56,7 +59,8 @@ class Server {
|
||||
request.on('end', () => {
|
||||
try {
|
||||
postObj = (body === '') ? {} : JSON.parse(body)
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
logger.error('[PicGo Server]', err)
|
||||
return handleResponse({
|
||||
response,
|
||||
body: {
|
||||
@@ -65,6 +69,7 @@ class Server {
|
||||
}
|
||||
})
|
||||
}
|
||||
logger.info('[PicGo Server] get the request', body)
|
||||
const handler = routers.getHandler(request.url!)
|
||||
handler!({
|
||||
...postObj,
|
||||
@@ -73,10 +78,12 @@ class Server {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
logger.warn(`[PicGo Server] don't support [${request.method}] method`)
|
||||
response.statusCode = 404
|
||||
response.end()
|
||||
}
|
||||
}
|
||||
|
||||
// port as string is a bug
|
||||
private listen = (port: number | string) => {
|
||||
logger.info(`[PicGo Server] is listening at ${port}`)
|
||||
@@ -98,17 +105,21 @@ class Server {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
startup () {
|
||||
console.log('startup', this.config.enable)
|
||||
if (this.config.enable) {
|
||||
this.listen(this.config.port)
|
||||
}
|
||||
}
|
||||
|
||||
shutdown (hasStarted?: boolean) {
|
||||
this.httpServer.close()
|
||||
if (!hasStarted) {
|
||||
logger.info('[PicGo Server] shutdown')
|
||||
}
|
||||
}
|
||||
|
||||
restart () {
|
||||
this.config = picgo.getConfig('settings.server')
|
||||
this.shutdown()
|
||||
|
||||
@@ -4,6 +4,7 @@ class Router {
|
||||
get (url: string, callback: routeHandler): void {
|
||||
this.router.set(url, callback)
|
||||
}
|
||||
|
||||
post (url: string, callback: routeHandler): void {
|
||||
this.router.set(url, callback)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import router from './router'
|
||||
import {
|
||||
uploadWithClipboardFiles,
|
||||
uploadWithFiles
|
||||
} from '@core/bus/apis'
|
||||
import {
|
||||
handleResponse
|
||||
} from './utils'
|
||||
import logger from '@core/picgo/logger'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
|
||||
import path from 'path'
|
||||
import { dbPathDir } from 'apis/core/datastore/dbChecker'
|
||||
const STORE_PATH = dbPathDir()
|
||||
const LOG_PATH = path.join(STORE_PATH, 'picgo.log')
|
||||
|
||||
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
|
||||
|
||||
router.post('/upload', async ({
|
||||
response,
|
||||
@@ -18,49 +22,60 @@ router.post('/upload', async ({
|
||||
try {
|
||||
if (list.length === 0) {
|
||||
// upload with clipboard
|
||||
const res = await uploadWithClipboardFiles()
|
||||
if (res.success) {
|
||||
logger.info('[PicGo Server] upload clipboard file')
|
||||
const res = await uploadClipboardFiles()
|
||||
if (res) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: res.result
|
||||
result: [res]
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
response
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
logger.info('[PicGo Server] upload files in list')
|
||||
// upload with files
|
||||
const pathList = list.map(item => {
|
||||
return {
|
||||
path: item
|
||||
}
|
||||
})
|
||||
const res = await uploadWithFiles(pathList)
|
||||
if (res.success) {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
const res = await uploadChoosedFiles(win.webContents, pathList)
|
||||
if (res.length) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: res.result
|
||||
result: res
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
response
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: err
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logger from '@core/picgo/logger'
|
||||
|
||||
export const handleResponse = ({
|
||||
response,
|
||||
statusCode = 200,
|
||||
@@ -13,6 +15,9 @@ export const handleResponse = ({
|
||||
header?: IObj,
|
||||
body?: any
|
||||
}) => {
|
||||
if (body?.success === false) {
|
||||
logger.warn('[PicGo Server] upload failed, see picgo.log for more detail ↑')
|
||||
}
|
||||
response.writeHead(statusCode, header)
|
||||
response.write(JSON.stringify(body))
|
||||
response.end()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
import { remote, app } from 'electron'
|
||||
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
|
||||
|
||||
const APP = process.type === 'renderer' ? remote.app : app
|
||||
const STORE_PATH = APP.getPath('userData')
|
||||
const configPath = dbPathChecker()
|
||||
const CONFIG_DIR = path.dirname(configPath)
|
||||
|
||||
function beforeOpen () {
|
||||
if (process.platform === 'darwin') {
|
||||
@@ -33,8 +33,8 @@ function resolveMacWorkFlow () {
|
||||
* 初始化剪贴板生成图片的脚本
|
||||
*/
|
||||
function resolveClipboardImageGenerator () {
|
||||
let clipboardFiles = getClipboardFiles()
|
||||
if (!fs.pathExistsSync(path.join(STORE_PATH, 'windows10.ps1'))) {
|
||||
const clipboardFiles = getClipboardFiles()
|
||||
if (!fs.pathExistsSync(path.join(CONFIG_DIR, 'windows10.ps1'))) {
|
||||
clipboardFiles.forEach(item => {
|
||||
fs.copyFileSync(item.origin, item.dest)
|
||||
})
|
||||
@@ -45,26 +45,32 @@ function resolveClipboardImageGenerator () {
|
||||
}
|
||||
|
||||
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
|
||||
let file1 = fs.readFileSync(filePath1)
|
||||
let file2 = fs.readFileSync(filePath2)
|
||||
try {
|
||||
const file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
|
||||
const file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
|
||||
|
||||
if (!file1.equals(file2)) {
|
||||
if (!file1 || !file2 || !file1.equals(file2)) {
|
||||
fs.copyFileSync(filePath1, filePath2)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
fs.copyFileSync(filePath1, filePath2)
|
||||
}
|
||||
}
|
||||
|
||||
function getClipboardFiles () {
|
||||
let files = [
|
||||
const files = [
|
||||
'/linux.sh',
|
||||
'/mac.applescript',
|
||||
'/windows.ps1',
|
||||
'/windows10.ps1'
|
||||
'/windows10.ps1',
|
||||
'/wsl.sh'
|
||||
]
|
||||
|
||||
return files.map(item => {
|
||||
return {
|
||||
origin: path.join(__static, item),
|
||||
dest: path.join(STORE_PATH, item)
|
||||
dest: path.join(CONFIG_DIR, item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,8 +1,71 @@
|
||||
import db from '#/datastore'
|
||||
import { clipboard } from 'electron'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { clipboard, Notification, dialog } from 'electron'
|
||||
|
||||
export function handleCopyUrl (str: string): void {
|
||||
if (db.get('settings.autoCopy') === true) {
|
||||
export const handleCopyUrl = (str: string): void => {
|
||||
if (db.get('settings.autoCopy') !== false) {
|
||||
clipboard.writeText(str)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show notification
|
||||
* @param options
|
||||
*/
|
||||
export const showNotification = (options: IPrivateShowNotificationOption = {
|
||||
title: '',
|
||||
body: '',
|
||||
clickToCopy: false
|
||||
}) => {
|
||||
const notification = new Notification({
|
||||
title: options.title,
|
||||
body: options.body,
|
||||
icon: options.icon || undefined
|
||||
})
|
||||
const handleClick = () => {
|
||||
if (options.clickToCopy) {
|
||||
clipboard.writeText(options.body)
|
||||
}
|
||||
}
|
||||
notification.once('click', handleClick)
|
||||
notification.once('close', () => {
|
||||
notification.removeListener('click', handleClick)
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
|
||||
export const showMessageBox = (options: any) => {
|
||||
return new Promise<IShowMessageBoxResult>(async (resolve) => {
|
||||
dialog.showMessageBox(
|
||||
options
|
||||
).then((res) => {
|
||||
resolve({
|
||||
result: res.response,
|
||||
checkboxChecked: res.checkboxChecked
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const calcDurationRange = (duration: number) => {
|
||||
if (duration < 1000) {
|
||||
return 500
|
||||
} else if (duration < 1500) {
|
||||
return 1000
|
||||
} else if (duration < 3000) {
|
||||
return 2000
|
||||
} else if (duration < 5000) {
|
||||
return 3000
|
||||
} else if (duration < 7000) {
|
||||
return 5000
|
||||
} else if (duration < 10000) {
|
||||
return 8000
|
||||
} else if (duration < 12000) {
|
||||
return 10000
|
||||
} else if (duration < 20000) {
|
||||
return 15000
|
||||
} else if (duration < 30000) {
|
||||
return 20000
|
||||
}
|
||||
// max range
|
||||
return 100000
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import picgo from '@core/picgo'
|
||||
|
||||
const getPicBeds = () => {
|
||||
const picBedTypes = picgo.helper.uploader.getIdList()
|
||||
const picBedFromDB = picgo.getConfig('picBed.list') || []
|
||||
const picBedFromDB = picgo.getConfig<IPicBedType[]>('picBed.list') || []
|
||||
const picBeds = picBedTypes.map((item: string) => {
|
||||
const visible = picBedFromDB.find((i: IPicBedType) => i.type === item) // object or undefined
|
||||
return {
|
||||
type: item,
|
||||
name: picgo.helper.uploader.get(item).name || item,
|
||||
name: picgo.helper.uploader.get(item)!.name || item,
|
||||
visible: visible ? visible.visible : true
|
||||
}
|
||||
}) as IPicBedType[]
|
||||
|
||||
@@ -1,35 +1,55 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
type ClipboardFileObject = {
|
||||
import Logger from 'picgo/dist/src/lib/Logger'
|
||||
interface IResultFileObject {
|
||||
path: string
|
||||
}
|
||||
type Result = ClipboardFileObject[]
|
||||
const getUploadFiles = (argv = process.argv, cwd = process.cwd()) => {
|
||||
let files = argv.slice(1)
|
||||
if (files.length > 0 && files[0] === 'upload') {
|
||||
if (files.length === 1) {
|
||||
type Result = IResultFileObject[]
|
||||
|
||||
interface IHandleArgvResult {
|
||||
success: boolean
|
||||
fileList?: string[]
|
||||
}
|
||||
|
||||
const handleArgv = (argv: string[]): IHandleArgvResult => {
|
||||
const uploadIndex = argv.indexOf('upload')
|
||||
if (uploadIndex !== -1) {
|
||||
const fileList = argv.slice(1 + uploadIndex)
|
||||
return {
|
||||
success: true,
|
||||
fileList
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: false
|
||||
}
|
||||
}
|
||||
|
||||
const getUploadFiles = (argv = process.argv, cwd = process.cwd(), logger: Logger) => {
|
||||
const { success, fileList } = handleArgv(argv)
|
||||
if (!success) {
|
||||
return []
|
||||
} else {
|
||||
if (fileList?.length === 0) {
|
||||
return null // for uploading images in clipboard
|
||||
} else if (files.length > 1) {
|
||||
files = argv.slice(1)
|
||||
let result: Result = []
|
||||
if (files.length > 0) {
|
||||
result = files.map(item => {
|
||||
if (path.isAbsolute(item)) {
|
||||
} else if ((fileList?.length || 0) > 0) {
|
||||
const result = fileList!.map(item => {
|
||||
if (path.isAbsolute(item)) {
|
||||
return {
|
||||
path: item
|
||||
}
|
||||
} else {
|
||||
const tempPath = path.join(cwd, item)
|
||||
if (fs.existsSync(tempPath)) {
|
||||
return {
|
||||
path: item
|
||||
path: tempPath
|
||||
}
|
||||
} else {
|
||||
let tempPath = path.join(cwd, item)
|
||||
if (fs.existsSync(tempPath)) {
|
||||
return {
|
||||
path: tempPath
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
logger.warn(`cli -> can't get file: ${tempPath}, invalid path`)
|
||||
return null
|
||||
}
|
||||
}).filter(item => item !== null) as Result
|
||||
}
|
||||
}
|
||||
}).filter(item => item !== null) as Result
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { IPasteStyle } from '#/types/enum'
|
||||
|
||||
const formatCustomLink = (customLink: string, item: ImgInfo) => {
|
||||
const fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')
|
||||
const url = item.url || item.imgUrl
|
||||
const formatObj = {
|
||||
url,
|
||||
fileName
|
||||
}
|
||||
const keys = Object.keys(formatObj) as ['url', 'fileName']
|
||||
keys.forEach(item => {
|
||||
if (customLink.indexOf(`$${item}`) !== -1) {
|
||||
const reg = new RegExp(`\\$${item}`, 'g')
|
||||
customLink = customLink.replace(reg, formatObj[item])
|
||||
}
|
||||
})
|
||||
return customLink
|
||||
}
|
||||
|
||||
export default (style: IPasteStyle, item: ImgInfo, customLink: string | undefined) => {
|
||||
const url = item.url || item.imgUrl
|
||||
const _customLink = customLink || '$url'
|
||||
const tpl = {
|
||||
markdown: ``,
|
||||
HTML: `<img src="${url}"/>`,
|
||||
URL: url,
|
||||
UBB: `[IMG]${url}[/IMG]`,
|
||||
Custom: formatCustomLink(_customLink, item)
|
||||
}
|
||||
return tpl[style]
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { ipcMain } from 'electron'
|
||||
import { showMessageBox } from '~/main/utils/common'
|
||||
import { SHOW_PRIVACY_MESSAGE } from '~/universal/events/constants'
|
||||
|
||||
class PrivacyManager {
|
||||
async init () {
|
||||
ipcMain.on(SHOW_PRIVACY_MESSAGE, () => {
|
||||
this.show(false)
|
||||
})
|
||||
if (db.get('settings.privacyEnsure') !== true) {
|
||||
const res = await this.show(true)
|
||||
// cancel
|
||||
if (res.result === 1) {
|
||||
return false
|
||||
} else {
|
||||
db.set('settings.privacyEnsure', true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async show (showCancel = true) {
|
||||
const res = await showMessageBox({
|
||||
type: 'info',
|
||||
buttons: showCancel ? ['Yes', 'No'] : ['Yes'],
|
||||
title: '隐私协议',
|
||||
message: `
|
||||
本软件尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更优质的服务,本软件会按照本隐私权政策的规定使用和收集您的一些行为信息。您在同意本软件服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本软件服务使用协议不可分割的一部分,如果不同意将无法使用。本协议会定期更新。
|
||||
|
||||
1.适用范围
|
||||
|
||||
a)在您使用本软件时,本软件会记录的您对本软件的一些操作行为信息,包括但不限于您使用本软件进行文件上传的耗时、类型、数量等信息。
|
||||
|
||||
2.信息的使用
|
||||
|
||||
a)在获得您的使用数据之后,本软件会将其上传至数据分析服务器,以便分析数据后,提供给您更好的服务。
|
||||
|
||||
3.信息披露
|
||||
|
||||
a)本软件不会将您的信息披露给不受信任的第三方。
|
||||
|
||||
b)根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露;
|
||||
|
||||
c)如您出现违反中国有关法律、法规或者相关规则的情况,需要向第三方披露;
|
||||
|
||||
4.信息安全
|
||||
|
||||
a)本软件不会收集您的个人信息、密钥信息等隐私信息,所收集的信息仅仅作为改善软件、优化体验、了解软件日活等用途。
|
||||
`
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
const privacyManager = new PrivacyManager()
|
||||
|
||||
export {
|
||||
privacyManager
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { dialog, shell } from 'electron'
|
||||
import db from '#/datastore'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import axios from 'axios'
|
||||
import pkg from 'root/package.json'
|
||||
import { lt } from 'semver'
|
||||
@@ -53,7 +53,7 @@ const checkVersion = async () => {
|
||||
// if true -> update else return false
|
||||
const compareVersion2Update = (current: string, latest: string) => {
|
||||
try {
|
||||
if (latest.includes('Beta')) {
|
||||
if (latest.includes('beta')) {
|
||||
const isCheckBetaUpdate = db.get('settings.checkBetaUpdate') !== false
|
||||
if (!isCheckBetaUpdate) {
|
||||
return false
|
||||
|
||||
@@ -8,30 +8,28 @@
|
||||
</el-switch>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'choose-pic-bed',
|
||||
props: {
|
||||
type: String,
|
||||
label: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
value: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.type === this.$db.get('picBed.current')) {
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from 'vue-property-decorator'
|
||||
@Component({
|
||||
name: 'choose-pic-bed'
|
||||
})
|
||||
export default class extends Vue {
|
||||
value = false
|
||||
@Prop() type!: string
|
||||
@Prop() label!: string
|
||||
async created () {
|
||||
const current = await this.getConfig<string>('picBed.current')
|
||||
if (this.type === current) {
|
||||
this.value = true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
choosePicBed (val) {
|
||||
this.letPicGoSaveData({
|
||||
'picBed.current': this.type
|
||||
})
|
||||
this.$emit('update:choosed', this.type)
|
||||
}
|
||||
}
|
||||
|
||||
choosePicBed () {
|
||||
this.saveConfig({
|
||||
'picBed.current': this.type,
|
||||
'picBed.uploader': this.type
|
||||
})
|
||||
this.$emit('update:choosed', this.type)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -72,7 +72,7 @@ import { cloneDeep, union } from 'lodash'
|
||||
})
|
||||
export default class extends Vue {
|
||||
@Prop() private config!: any[]
|
||||
@Prop() readonly type!: string
|
||||
@Prop() readonly type!: 'uploader' | 'transformer' | 'plugin'
|
||||
@Prop() readonly id!: string
|
||||
configList = []
|
||||
ruleForm = {}
|
||||
@@ -81,13 +81,49 @@ export default class extends Vue {
|
||||
immediate: true
|
||||
})
|
||||
handleConfigChange (val: any) {
|
||||
this.handleConfig(val)
|
||||
}
|
||||
|
||||
async validate () {
|
||||
return new Promise((resolve) => {
|
||||
// @ts-ignore
|
||||
this.$refs.form.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(this.ruleForm)
|
||||
} else {
|
||||
resolve(false)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
getConfigType () {
|
||||
switch (this.type) {
|
||||
case 'plugin': {
|
||||
return this.id
|
||||
}
|
||||
case 'uploader': {
|
||||
return `picBed.${this.id}`
|
||||
}
|
||||
case 'transformer': {
|
||||
return `transformer.${this.id}`
|
||||
}
|
||||
default:
|
||||
return 'unknown'
|
||||
}
|
||||
}
|
||||
|
||||
async handleConfig (val: any) {
|
||||
this.ruleForm = Object.assign({}, {})
|
||||
const config = this.$db.get(`picBed.${this.id}`)
|
||||
const config = await this.getConfig<IPicGoPluginConfig>(this.getConfigType())
|
||||
if (val.length > 0) {
|
||||
this.configList = cloneDeep(val).map((item: any) => {
|
||||
let defaultValue = item.default !== undefined
|
||||
? item.default : item.type === 'checkbox'
|
||||
? [] : null
|
||||
? item.default
|
||||
: item.type === 'checkbox'
|
||||
? []
|
||||
: null
|
||||
if (item.type === 'checkbox') {
|
||||
const defaults = item.choices.filter((i: any) => {
|
||||
return i.checked
|
||||
@@ -102,19 +138,6 @@ export default class extends Vue {
|
||||
})
|
||||
}
|
||||
}
|
||||
async validate () {
|
||||
return new Promise((resolve, reject) => {
|
||||
// @ts-ignore
|
||||
this.$refs.form.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(this.ruleForm)
|
||||
} else {
|
||||
resolve(false)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { remote, ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import {
|
||||
SHOW_INPUT_BOX,
|
||||
SHOW_INPUT_BOX_RESPONSE
|
||||
@@ -30,30 +30,36 @@ export default class extends Vue {
|
||||
title: '',
|
||||
placeholder: ''
|
||||
}
|
||||
|
||||
created () {
|
||||
ipcRenderer.on(SHOW_INPUT_BOX, this.ipcEventHandler)
|
||||
this.$bus.$on(SHOW_INPUT_BOX, this.initInputBoxValue)
|
||||
}
|
||||
|
||||
ipcEventHandler (evt: IpcRendererEvent, options: IShowInputBoxOption) {
|
||||
this.initInputBoxValue(options)
|
||||
}
|
||||
|
||||
initInputBoxValue (options: IShowInputBoxOption) {
|
||||
this.inputBoxValue = options.value || ''
|
||||
this.inputBoxOptions.title = options.title || ''
|
||||
this.inputBoxOptions.placeholder = options.placeholder || ''
|
||||
this.showInputBoxVisible = true
|
||||
}
|
||||
|
||||
handleInputBoxCancel () {
|
||||
// TODO: RPCServer
|
||||
this.showInputBoxVisible = false
|
||||
ipcRenderer.send(SHOW_INPUT_BOX, '')
|
||||
this.$bus.$emit(SHOW_INPUT_BOX_RESPONSE, '')
|
||||
}
|
||||
|
||||
handleInputBoxConfirm () {
|
||||
this.showInputBoxVisible = false
|
||||
ipcRenderer.send(SHOW_INPUT_BOX, this.inputBoxValue)
|
||||
this.$bus.$emit(SHOW_INPUT_BOX_RESPONSE, this.inputBoxValue)
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeListener(SHOW_INPUT_BOX, this.ipcEventHandler)
|
||||
this.$bus.$off(SHOW_INPUT_BOX)
|
||||
|
||||
+100
-97
@@ -17,6 +17,7 @@
|
||||
:default-active="defaultActive"
|
||||
@select="handleSelect"
|
||||
:unique-opened="true"
|
||||
@open="handleGetPicPeds"
|
||||
>
|
||||
<el-menu-item index="upload">
|
||||
<i class="el-icon-upload"></i>
|
||||
@@ -92,86 +93,114 @@
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
title="自定义链接格式"
|
||||
:visible.sync="customLinkVisible"
|
||||
class="qrcode-dialog"
|
||||
top="3vh"
|
||||
width="60%"
|
||||
title="图床配置二维码"
|
||||
:visible.sync="qrcodeVisible"
|
||||
:modal-append-to-body="false"
|
||||
lock-scroll
|
||||
>
|
||||
<el-form
|
||||
label-position="top"
|
||||
:model="customLink"
|
||||
ref="customLink"
|
||||
:rules="rules"
|
||||
label-position="left"
|
||||
label-width="70px"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item
|
||||
label="用占位符$url来表示url的位置"
|
||||
prop="value"
|
||||
label="选择图床"
|
||||
>
|
||||
<el-input
|
||||
class="align-center"
|
||||
v-model="customLink.value"
|
||||
:autofocus="true"
|
||||
></el-input>
|
||||
<el-select
|
||||
v-model="choosedPicBedForQRCode"
|
||||
multiple
|
||||
collapse-tags
|
||||
>
|
||||
<el-option
|
||||
v-for="item in picBed"
|
||||
:key="item.type"
|
||||
:label="item.name"
|
||||
:value="item.type"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<el-button
|
||||
v-show="choosedPicBedForQRCode.length > 0"
|
||||
type="primary"
|
||||
round
|
||||
class="copy-picbed-config"
|
||||
@click="handleCopyPicBedConfig"
|
||||
>
|
||||
复制图床配置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div>
|
||||
如[]($url)
|
||||
<div class="qrcode-container">
|
||||
<qrcode-vue
|
||||
v-show="choosedPicBedForQRCode.length > 0"
|
||||
:size="280"
|
||||
:value="picBedConfigString"
|
||||
/>
|
||||
</div>
|
||||
<span slot="footer">
|
||||
<el-button @click="cancelCustomLink">取消</el-button>
|
||||
<el-button type="primary" @click="confirmCustomLink">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<input-box-dialog />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import pick from 'lodash/pick'
|
||||
import pkg from 'root/package.json'
|
||||
import keyDetect from '@/utils/key-binding'
|
||||
import { remote, ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import db from '#/datastore'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent,
|
||||
clipboard
|
||||
} from 'electron'
|
||||
import mixin from '@/utils/mixin'
|
||||
import InputBoxDialog from '@/components/InputBoxDialog.vue'
|
||||
const { Menu, dialog, BrowserWindow } = remote
|
||||
const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) => void) => {
|
||||
if (!/\$url/.test(value)) {
|
||||
return callback(new Error('必须含有$url'))
|
||||
} else {
|
||||
return callback()
|
||||
}
|
||||
}
|
||||
import {
|
||||
MINIMIZE_WINDOW,
|
||||
CLOSE_WINDOW,
|
||||
SHOW_MAIN_PAGE_MENU
|
||||
} from '~/universal/events/constants'
|
||||
@Component({
|
||||
name: 'main-page',
|
||||
mixins: [mixin],
|
||||
components: {
|
||||
InputBoxDialog
|
||||
InputBoxDialog,
|
||||
QrcodeVue
|
||||
}
|
||||
})
|
||||
export default class extends Vue {
|
||||
version = process.env.NODE_ENV === 'production' ? pkg.version : 'Dev'
|
||||
defaultActive = 'upload'
|
||||
menu: Electron.Menu | null = null
|
||||
visible = false
|
||||
keyBindingVisible = false
|
||||
customLinkVisible = false
|
||||
customLink = {
|
||||
value: db.get('customLink') || '$url'
|
||||
}
|
||||
rules = {
|
||||
value: [
|
||||
{ validator: customLinkRule, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
os = ''
|
||||
shortKey: IShortKeyMap = {
|
||||
upload: db.get('shortKey.upload')
|
||||
}
|
||||
picBed: IPicBedType[] = []
|
||||
qrcodeVisible = false
|
||||
picBedConfigString = ''
|
||||
choosedPicBedForQRCode: string[] = []
|
||||
created () {
|
||||
this.os = process.platform
|
||||
this.buildMenu()
|
||||
ipcRenderer.send('getPicBeds')
|
||||
ipcRenderer.on('getPicBeds', this.getPicBeds)
|
||||
this.handleGetPicPeds()
|
||||
}
|
||||
|
||||
@Watch('choosedPicBedForQRCode')
|
||||
choosedPicBedForQRCodeChange (val: string[]) {
|
||||
if (val.length > 0) {
|
||||
this.$nextTick(async () => {
|
||||
const picBedConfig = await this.getConfig('picBed')
|
||||
const config = pick(picBedConfig, ...this.choosedPicBedForQRCode)
|
||||
this.picBedConfigString = JSON.stringify(config)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleGetPicPeds = () => {
|
||||
ipcRenderer.send('getPicBeds')
|
||||
}
|
||||
|
||||
handleSelect (index: string) {
|
||||
const type = index.match(/picbeds-/)
|
||||
if (type === null) {
|
||||
@@ -194,74 +223,38 @@ export default class extends Vue {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
minimizeWindow () {
|
||||
const window = BrowserWindow.getFocusedWindow()
|
||||
window!.minimize()
|
||||
ipcRenderer.send(MINIMIZE_WINDOW)
|
||||
}
|
||||
|
||||
closeWindow () {
|
||||
const window = BrowserWindow.getFocusedWindow()
|
||||
window!.close()
|
||||
}
|
||||
buildMenu () {
|
||||
const _this = this
|
||||
const template = [
|
||||
{
|
||||
label: '关于',
|
||||
click () {
|
||||
dialog.showMessageBox({
|
||||
title: 'PicGo',
|
||||
message: 'PicGo',
|
||||
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '赞助PicGo',
|
||||
click () {
|
||||
_this.visible = true
|
||||
}
|
||||
}
|
||||
]
|
||||
this.menu = Menu.buildFromTemplate(template)
|
||||
ipcRenderer.send(CLOSE_WINDOW)
|
||||
}
|
||||
|
||||
openDialog () {
|
||||
// this.menu!.popup(remote.getCurrentWindow())
|
||||
this.menu!.popup()
|
||||
}
|
||||
keyDetect (type: string, event: KeyboardEvent) {
|
||||
this.shortKey[type] = keyDetect(event).join('+')
|
||||
}
|
||||
cancelKeyBinding () {
|
||||
this.keyBindingVisible = false
|
||||
this.shortKey = db.get('shortKey')
|
||||
}
|
||||
cancelCustomLink () {
|
||||
this.customLinkVisible = false
|
||||
this.customLink.value = db.get('customLink') || '$url'
|
||||
}
|
||||
confirmCustomLink () {
|
||||
// @ts-ignore
|
||||
this.$refs.customLink.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
db.set('customLink', this.customLink.value)
|
||||
this.customLinkVisible = false
|
||||
ipcRenderer.send('updateCustomLink')
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
ipcRenderer.send(SHOW_MAIN_PAGE_MENU)
|
||||
}
|
||||
|
||||
openMiniWindow () {
|
||||
ipcRenderer.send('openMiniWindow')
|
||||
}
|
||||
|
||||
handleCopyPicBedConfig () {
|
||||
clipboard.writeText(this.picBedConfigString)
|
||||
this.$message.success('图床配置复制成功')
|
||||
}
|
||||
|
||||
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
|
||||
this.picBed = picBeds
|
||||
}
|
||||
beforeRouteEnter (to: any, from: any, next: any) {
|
||||
|
||||
beforeRouteEnter (to: any, next: any) {
|
||||
next((vm: this) => {
|
||||
vm.defaultActive = to.name
|
||||
})
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
|
||||
}
|
||||
@@ -283,6 +276,16 @@ $darwinBg = transparentify(#172426, #000, 0.7)
|
||||
text-align center
|
||||
margin 10px auto
|
||||
#main-page
|
||||
.qrcode-dialog
|
||||
.qrcode-container
|
||||
display flex
|
||||
justify-content center
|
||||
.el-dialog__body
|
||||
padding-top 10px
|
||||
.copy-picbed-config
|
||||
margin-left 10px
|
||||
.el-input__inner
|
||||
border-radius 14px
|
||||
.fake-title-bar
|
||||
-webkit-app-region drag
|
||||
height h = 22px
|
||||
|
||||
+112
-81
@@ -71,12 +71,12 @@
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-row :gutter="16">
|
||||
<gallerys
|
||||
:images="images"
|
||||
:images="filterList"
|
||||
:index="idx"
|
||||
@close="handleClose"
|
||||
:options="options"
|
||||
></gallerys>
|
||||
<el-col :span="6" v-for="(item, index) in images" :key="item.id" class="gallery-list__img">
|
||||
<el-col :span="6" v-for="(item, index) in filterList" :key="item.id" class="gallery-list__img">
|
||||
<div
|
||||
class="gallery-list__item"
|
||||
@click="zoomImage(index)"
|
||||
@@ -110,8 +110,9 @@
|
||||
<script lang="ts">
|
||||
// @ts-ignore
|
||||
import gallerys from 'vue-gallery'
|
||||
import pasteStyle from '#/utils/pasteTemplate'
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
import { IResult } from '@picgo/store/dist/types'
|
||||
import { PASTE_TEXT } from '#/events/constants'
|
||||
import {
|
||||
ipcRenderer,
|
||||
clipboard,
|
||||
@@ -131,11 +132,13 @@ export default class extends Vue {
|
||||
urlProperty: 'imgUrl',
|
||||
closeOnSlideClick: true
|
||||
}
|
||||
|
||||
dialogVisible = false
|
||||
imgInfo = {
|
||||
id: '',
|
||||
imgUrl: ''
|
||||
}
|
||||
|
||||
choosedList: IObjT<boolean> = {}
|
||||
choosedPicBed: string[] = []
|
||||
lastChoosed: number = -1
|
||||
@@ -150,37 +153,49 @@ export default class extends Vue {
|
||||
UBB: 'UBB',
|
||||
Custom: 'Custom'
|
||||
}
|
||||
|
||||
picBed: IPicBedType[] = []
|
||||
@Watch('$route')
|
||||
handleRouteUpdate (to: any, from: any) {
|
||||
console.log(to, from)
|
||||
if (from.name === 'gallery') {
|
||||
this.clearChoosedList()
|
||||
}
|
||||
if (to.name === 'gallery') {
|
||||
this.updateGallery()
|
||||
}
|
||||
}
|
||||
created () {
|
||||
ipcRenderer.on('updateGallery', (event: IpcRendererEvent) => {
|
||||
this.$nextTick(() => {
|
||||
this.filterList = this.getGallery()
|
||||
|
||||
async created () {
|
||||
ipcRenderer.on('updateGallery', () => {
|
||||
this.$nextTick(async () => {
|
||||
this.updateGallery()
|
||||
})
|
||||
})
|
||||
ipcRenderer.send('getPicBeds')
|
||||
ipcRenderer.on('getPicBeds', this.getPicBeds)
|
||||
this.updateGallery()
|
||||
}
|
||||
|
||||
mounted () {
|
||||
document.addEventListener('keydown', this.handleDetectShiftKey)
|
||||
document.addEventListener('keyup', this.handleDetectShiftKey)
|
||||
}
|
||||
|
||||
handleDetectShiftKey (event: KeyboardEvent) {
|
||||
if (event.keyCode === 16) {
|
||||
this.isShiftKeyPress = !this.isShiftKeyPress
|
||||
if (event.key === 'Shift') {
|
||||
if (event.type === 'keydown') {
|
||||
this.isShiftKeyPress = true
|
||||
} else if (event.type === 'keyup') {
|
||||
this.isShiftKeyPress = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get filterList () {
|
||||
return this.getGallery()
|
||||
}
|
||||
set filterList (val) {
|
||||
this.images = val
|
||||
}
|
||||
|
||||
get isAllSelected () {
|
||||
const values = Object.values(this.choosedList)
|
||||
if (values.length === 0) {
|
||||
@@ -191,50 +206,45 @@ export default class extends Vue {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
|
||||
this.picBed = picBeds
|
||||
}
|
||||
getGallery () {
|
||||
if (this.choosedPicBed.length > 0) {
|
||||
let arr: ImgInfo[] = []
|
||||
this.choosedPicBed.forEach(item => {
|
||||
let obj: IObj = {
|
||||
type: item
|
||||
}
|
||||
if (this.searchText) {
|
||||
obj.fileName = this.searchText
|
||||
}
|
||||
// @ts-ignore
|
||||
arr = arr.concat(this.$db.read().get('uploaded').filter(obj => {
|
||||
return obj.fileName.indexOf(this.searchText) !== -1 && obj.type === item
|
||||
}).reverse().value())
|
||||
})
|
||||
this.images = arr
|
||||
|
||||
getGallery (): ImgInfo[] {
|
||||
if (this.searchText || this.choosedPicBed.length > 0) {
|
||||
return this.images
|
||||
.filter(item => {
|
||||
let isInChoosedPicBed = true
|
||||
let isIncludesSearchText = true
|
||||
if (this.choosedPicBed.length > 0) {
|
||||
isInChoosedPicBed = this.choosedPicBed.some(type => type === item.type)
|
||||
}
|
||||
if (this.searchText) {
|
||||
isIncludesSearchText = item.fileName?.includes(this.searchText) || false
|
||||
}
|
||||
return isIncludesSearchText && isInChoosedPicBed
|
||||
})
|
||||
} else {
|
||||
if (this.searchText) {
|
||||
let data = this.$db.read().get('uploaded')
|
||||
// @ts-ignore
|
||||
.filter(item => {
|
||||
return item.fileName.indexOf(this.searchText) !== -1
|
||||
}).reverse().value()
|
||||
this.images = data
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.images = this.$db.read().get('uploaded').slice().reverse().value()
|
||||
}
|
||||
return this.images
|
||||
}
|
||||
return this.images
|
||||
}
|
||||
|
||||
async updateGallery () {
|
||||
this.images = (await this.$$db.get({ orderBy: 'desc' })).data
|
||||
}
|
||||
|
||||
@Watch('filterList')
|
||||
handleFilterListChange () {
|
||||
this.clearChoosedList()
|
||||
}
|
||||
|
||||
handleChooseImage (val: boolean, index: number) {
|
||||
if (val === true) {
|
||||
this.handleBarActive = true
|
||||
if (this.lastChoosed !== -1 && this.isShiftKeyPress) {
|
||||
let min = Math.min(this.lastChoosed, index)
|
||||
let max = Math.max(this.lastChoosed, index)
|
||||
const min = Math.min(this.lastChoosed, index)
|
||||
const max = Math.max(this.lastChoosed, index)
|
||||
for (let i = min + 1; i < max; i++) {
|
||||
const id = this.filterList[i].id!
|
||||
this.$set(this.choosedList, id, true)
|
||||
@@ -243,6 +253,7 @@ export default class extends Vue {
|
||||
this.lastChoosed = index
|
||||
}
|
||||
}
|
||||
|
||||
clearChoosedList () {
|
||||
this.isShiftKeyPress = false
|
||||
Object.keys(this.choosedList).forEach(key => {
|
||||
@@ -250,10 +261,12 @@ export default class extends Vue {
|
||||
})
|
||||
this.lastChoosed = -1
|
||||
}
|
||||
|
||||
zoomImage (index: number) {
|
||||
this.idx = index
|
||||
this.changeZIndexForGallery(true)
|
||||
}
|
||||
|
||||
changeZIndexForGallery (isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
// @ts-ignore
|
||||
@@ -263,32 +276,33 @@ export default class extends Vue {
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 10
|
||||
}
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.idx = null
|
||||
this.changeZIndexForGallery(false)
|
||||
}
|
||||
copy (item: ImgInfo) {
|
||||
const style = this.$db.get('settings.pasteStyle') || 'markdown'
|
||||
const copyLink = pasteStyle(style, item)
|
||||
|
||||
async copy (item: ImgInfo) {
|
||||
const copyLink = await ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
const obj = {
|
||||
title: '复制链接成功',
|
||||
body: copyLink,
|
||||
icon: item.url || item.imgUrl
|
||||
}
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
clipboard.writeText(copyLink)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
remove (id: string) {
|
||||
this.$confirm('此操作将把该图片移出相册, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const file = this.$db.getById('uploaded', id)
|
||||
this.$db.removeById('uploaded', id)
|
||||
}).then(async () => {
|
||||
const file = await this.$$db.getById(id)
|
||||
await this.$$db.removeById(id)
|
||||
ipcRenderer.send('removeFiles', [file])
|
||||
const obj = {
|
||||
title: '操作结果',
|
||||
@@ -298,23 +312,23 @@ export default class extends Vue {
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
this.getGallery()
|
||||
this.updateGallery()
|
||||
}).catch((e) => {
|
||||
console.log(e)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
openDialog (item: ImgInfo) {
|
||||
this.imgInfo.id = item.id!
|
||||
this.imgInfo.imgUrl = item.imgUrl as string
|
||||
this.dialogVisible = true
|
||||
}
|
||||
confirmModify () {
|
||||
this.$db.read().get('uploaded')
|
||||
// @ts-ignore
|
||||
.getById(this.imgInfo.id)
|
||||
.assign({ imgUrl: this.imgInfo.imgUrl })
|
||||
.write()
|
||||
|
||||
async confirmModify () {
|
||||
await this.$$db.updateById(this.imgInfo.id, {
|
||||
imgUrl: this.imgInfo.imgUrl
|
||||
})
|
||||
const obj = {
|
||||
title: '修改图片URL成功',
|
||||
body: this.imgInfo.imgUrl,
|
||||
@@ -325,28 +339,33 @@ export default class extends Vue {
|
||||
return true
|
||||
}
|
||||
this.dialogVisible = false
|
||||
this.getGallery()
|
||||
this.updateGallery()
|
||||
}
|
||||
|
||||
choosePicBed (type: string) {
|
||||
let idx = this.choosedPicBed.indexOf(type)
|
||||
const idx = this.choosedPicBed.indexOf(type)
|
||||
if (idx !== -1) {
|
||||
this.choosedPicBed.splice(idx, 1)
|
||||
} else {
|
||||
this.choosedPicBed.push(type)
|
||||
}
|
||||
}
|
||||
|
||||
cleanSearch () {
|
||||
this.searchText = ''
|
||||
}
|
||||
|
||||
isMultiple (obj: IObj) {
|
||||
return Object.values(obj).some(item => item)
|
||||
}
|
||||
|
||||
toggleSelectAll () {
|
||||
const result = !this.isAllSelected
|
||||
this.filterList.forEach(item => {
|
||||
this.$set(this.choosedList, item.id!, result)
|
||||
})
|
||||
}
|
||||
|
||||
multiRemove () {
|
||||
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
|
||||
const multiRemoveNumber = Object.values(this.choosedList).filter(item => item).length
|
||||
@@ -355,18 +374,21 @@ export default class extends Vue {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
let files: ImgInfo[] = []
|
||||
Object.keys(this.choosedList).forEach(key => {
|
||||
}).then(async () => {
|
||||
const files: IResult<ImgInfo>[] = []
|
||||
const imageIDList = Object.keys(this.choosedList)
|
||||
for (let i = 0; i < imageIDList.length; i++) {
|
||||
const key = imageIDList[i]
|
||||
if (this.choosedList[key]) {
|
||||
const file = this.$db.getById('uploaded', key)
|
||||
files.push(file)
|
||||
this.$db.removeById('uploaded', key)
|
||||
const file = await this.$$db.getById<ImgInfo>(key)
|
||||
if (file) {
|
||||
files.push(file)
|
||||
await this.$$db.removeById(key)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
this.clearChoosedList()
|
||||
this.choosedList = {} // 只有删除才能将这个置空
|
||||
this.getGallery()
|
||||
const obj = {
|
||||
title: '操作结果',
|
||||
body: '删除成功'
|
||||
@@ -376,44 +398,53 @@ export default class extends Vue {
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
this.updateGallery()
|
||||
}).catch(() => {
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
multiCopy () {
|
||||
|
||||
async multiCopy () {
|
||||
if (Object.values(this.choosedList).some(item => item)) {
|
||||
let copyString = ''
|
||||
const style = this.$db.get('settings.pasteStyle') || 'markdown'
|
||||
const copyString: string[] = []
|
||||
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
|
||||
Object.keys(this.choosedList).forEach(key => {
|
||||
const imageIDList = Object.keys(this.choosedList)
|
||||
for (let i = 0; i < imageIDList.length; i++) {
|
||||
const key = imageIDList[i]
|
||||
if (this.choosedList[key]) {
|
||||
const item = this.$db.getById('uploaded', key)
|
||||
copyString += pasteStyle(style, item) + '\n'
|
||||
this.choosedList[key] = false
|
||||
const item = await this.$$db.getById<ImgInfo>(key)
|
||||
if (item) {
|
||||
const txt = await ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
copyString.push(txt)
|
||||
this.choosedList[key] = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const obj = {
|
||||
title: '批量复制链接成功',
|
||||
body: copyString
|
||||
body: copyString.join('\n')
|
||||
}
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
clipboard.writeText(copyString)
|
||||
clipboard.writeText(copyString.join('\n'))
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleHandleBar () {
|
||||
this.handleBarActive = !this.handleBarActive
|
||||
}
|
||||
getPasteStyle () {
|
||||
this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
|
||||
}
|
||||
handlePasteStyleChange (val: string) {
|
||||
this.$db.set('settings.pasteStyle', val)
|
||||
|
||||
// getPasteStyle () {
|
||||
// this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
|
||||
// }
|
||||
async handlePasteStyleChange (val: string) {
|
||||
this.saveConfig('settings.pasteStyle', val)
|
||||
this.pasteStyle = val
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeAllListeners('updateGallery')
|
||||
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
|
||||
|
||||
@@ -20,10 +20,9 @@ import mixin from '@/utils/mixin'
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent,
|
||||
remote
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
import path from 'path'
|
||||
import { SHOW_MINI_PAGE_MENU, SET_MINI_WINDOW_POS } from '~/universal/events/constants'
|
||||
@Component({
|
||||
name: 'mini-page',
|
||||
mixins: [mixin]
|
||||
@@ -55,6 +54,7 @@ export default class extends Vue {
|
||||
})
|
||||
this.getPicBeds()
|
||||
}
|
||||
|
||||
mounted () {
|
||||
window.addEventListener('mousedown', this.handleMouseDown, false)
|
||||
window.addEventListener('mousemove', this.handleMouseMove, false)
|
||||
@@ -73,27 +73,31 @@ export default class extends Vue {
|
||||
}, 1200)
|
||||
}
|
||||
}
|
||||
|
||||
getPicBeds () {
|
||||
this.picBed = ipcRenderer.sendSync('getPicBeds')
|
||||
this.buildMenu()
|
||||
}
|
||||
|
||||
onDrop (e: DragEvent) {
|
||||
this.dragover = false
|
||||
this.ipcSendFiles(e.dataTransfer!.files)
|
||||
}
|
||||
|
||||
openUploadWindow () {
|
||||
// @ts-ignore
|
||||
document.getElementById('file-uploader').click()
|
||||
}
|
||||
|
||||
onChange (e: any) {
|
||||
this.ipcSendFiles(e.target.files)
|
||||
// @ts-ignore
|
||||
document.getElementById('file-uploader').value = ''
|
||||
}
|
||||
|
||||
ipcSendFiles (files: FileList) {
|
||||
let sendFiles: IFileWithPath[] = []
|
||||
Array.from(files).forEach((item, index) => {
|
||||
let obj = {
|
||||
const sendFiles: IFileWithPath[] = []
|
||||
Array.from(files).forEach((item) => {
|
||||
const obj = {
|
||||
name: item.name,
|
||||
path: item.path
|
||||
}
|
||||
@@ -101,6 +105,7 @@ export default class extends Vue {
|
||||
})
|
||||
ipcRenderer.send('uploadChoosedFiles', sendFiles)
|
||||
}
|
||||
|
||||
handleMouseDown (e: MouseEvent) {
|
||||
this.dragging = true
|
||||
this.wX = e.pageX
|
||||
@@ -108,20 +113,28 @@ export default class extends Vue {
|
||||
this.screenX = e.screenX
|
||||
this.screenY = e.screenY
|
||||
}
|
||||
|
||||
handleMouseMove (e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (this.dragging) {
|
||||
const xLoc = e.screenX - this.wX
|
||||
const yLoc = e.screenY - this.wY
|
||||
remote.BrowserWindow.getFocusedWindow()!.setBounds({
|
||||
ipcRenderer.send(SET_MINI_WINDOW_POS, {
|
||||
x: xLoc,
|
||||
y: yLoc,
|
||||
width: 64,
|
||||
height: 64
|
||||
})
|
||||
// remote.BrowserWindow.getFocusedWindow()!.setBounds({
|
||||
// x: xLoc,
|
||||
// y: yLoc,
|
||||
// width: 64,
|
||||
// height: 64
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseUp (e: MouseEvent) {
|
||||
this.dragging = false
|
||||
if (this.screenX === e.screenX && this.screenY === e.screenY) {
|
||||
@@ -133,63 +146,11 @@ export default class extends Vue {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openContextMenu () {
|
||||
this.menu!.popup()
|
||||
}
|
||||
buildMenu () {
|
||||
const _this = this
|
||||
const submenu = this.picBed.filter(item => item.visible).map(item => {
|
||||
return {
|
||||
label: item.name,
|
||||
type: 'radio',
|
||||
checked: this.$db.get('picBed.current') === item.type,
|
||||
click () {
|
||||
_this.letPicGoSaveData({
|
||||
'picBed.current': item.type
|
||||
})
|
||||
ipcRenderer.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
})
|
||||
const template = [
|
||||
{
|
||||
label: '打开详细窗口',
|
||||
click () {
|
||||
ipcRenderer.send('openSettingWindow')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '选择默认图床',
|
||||
type: 'submenu',
|
||||
submenu
|
||||
},
|
||||
{
|
||||
label: '剪贴板图片上传',
|
||||
click () {
|
||||
ipcRenderer.send('uploadClipboardFilesFromUploadPage')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '隐藏窗口',
|
||||
click () {
|
||||
remote.BrowserWindow.getFocusedWindow()!.hide()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '重启应用',
|
||||
click () {
|
||||
remote.app.relaunch()
|
||||
remote.app.exit(0)
|
||||
}
|
||||
},
|
||||
{
|
||||
role: 'quit',
|
||||
label: '退出'
|
||||
}
|
||||
]
|
||||
// @ts-ignore
|
||||
this.menu = remote.Menu.buildFromTemplate(template)
|
||||
ipcRenderer.send(SHOW_MINI_PAGE_MENU)
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeAllListeners('uploadProgress')
|
||||
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<el-button type="primary" round size="mini" @click="customLinkVisible = true">点击设置</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设置代理"
|
||||
label="设置代理和镜像地址"
|
||||
>
|
||||
<el-button type="primary" round size="mini" @click="proxyVisible = true">点击设置</el-button>
|
||||
</el-form-item>
|
||||
@@ -183,19 +183,20 @@
|
||||
</span>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
title="设置代理"
|
||||
title="设置代理和镜像地址"
|
||||
:visible.sync="proxyVisible"
|
||||
:modal-append-to-body="false"
|
||||
width="70%"
|
||||
>
|
||||
<el-form
|
||||
label-position="right"
|
||||
:model="customLink"
|
||||
ref="customLink"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item
|
||||
label="代理地址"
|
||||
label="上传代理"
|
||||
>
|
||||
<el-input
|
||||
v-model="proxy"
|
||||
@@ -203,6 +204,24 @@
|
||||
placeholder="例如:http://127.0.0.1:1080"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="插件安装代理"
|
||||
>
|
||||
<el-input
|
||||
v-model="npmProxy"
|
||||
:autofocus="true"
|
||||
placeholder="例如:http://127.0.0.1:1080"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="插件镜像地址"
|
||||
>
|
||||
<el-input
|
||||
v-model="npmRegistry"
|
||||
:autofocus="true"
|
||||
placeholder="例如:https://registry.npm.taobao.org/"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="cancelProxy" round>取消</el-button>
|
||||
@@ -319,13 +338,13 @@
|
||||
<script lang="ts">
|
||||
import keyDetect from '@/utils/key-binding'
|
||||
import pkg from 'root/package.json'
|
||||
import path from 'path'
|
||||
import { IConfig } from 'picgo/dist/src/types/index'
|
||||
import { PICGO_OPEN_FILE, OPEN_URL } from '#/events/constants'
|
||||
import {
|
||||
ipcRenderer,
|
||||
remote
|
||||
ipcRenderer
|
||||
} from 'electron'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import db from '#/datastore'
|
||||
// import db from '#/datastore'
|
||||
const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
|
||||
const releaseUrlBackup = 'https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@latest/package.json'
|
||||
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
|
||||
@@ -336,31 +355,24 @@ const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) =>
|
||||
return callback()
|
||||
}
|
||||
}
|
||||
let logLevel = db.get('settings.logLevel')
|
||||
if (!Array.isArray(logLevel)) {
|
||||
if (logLevel && logLevel.length > 0) {
|
||||
logLevel = [logLevel]
|
||||
} else {
|
||||
logLevel = ['all']
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
name: 'picgo-setting'
|
||||
})
|
||||
export default class extends Vue {
|
||||
form: ISettingForm = {
|
||||
updateHelper: db.get('settings.showUpdateTip'),
|
||||
updateHelper: false,
|
||||
showPicBedList: [],
|
||||
autoStart: db.get('settings.autoStart') || false,
|
||||
rename: db.get('settings.rename') || false,
|
||||
autoRename: db.get('settings.autoRename') || false,
|
||||
uploadNotification: db.get('settings.uploadNotification') || false,
|
||||
miniWindowOntop: db.get('settings.miniWindowOntop') || false,
|
||||
logLevel,
|
||||
autoCopyUrl: db.get('settings.autoCopy') === undefined ? true : db.get('settings.autoCopy'),
|
||||
checkBetaUpdate: db.get('settings.checkBetaUpdate') === undefined ? true : db.get('settings.checkBetaUpdate')
|
||||
autoStart: false,
|
||||
rename: false,
|
||||
autoRename: false,
|
||||
uploadNotification: false,
|
||||
miniWindowOntop: false,
|
||||
logLevel: ['all'],
|
||||
autoCopyUrl: true,
|
||||
checkBetaUpdate: true
|
||||
}
|
||||
|
||||
picBed: IPicBedType[] = []
|
||||
logFileVisible = false
|
||||
keyBindingVisible = false
|
||||
@@ -369,17 +381,22 @@ export default class extends Vue {
|
||||
serverVisible = false
|
||||
proxyVisible = false
|
||||
customLink = {
|
||||
value: db.get('settings.customLink') || '$url'
|
||||
value: '$url'
|
||||
}
|
||||
|
||||
shortKey: IShortKeyMap = {
|
||||
upload: db.get('settings.shortKey.upload')
|
||||
upload: ''
|
||||
}
|
||||
proxy = db.get('picBed.proxy') || ''
|
||||
|
||||
proxy = ''
|
||||
npmRegistry = ''
|
||||
npmProxy = ''
|
||||
rules = {
|
||||
value: [
|
||||
{ validator: customLinkRule, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
logLevel = {
|
||||
all: '全部-All',
|
||||
success: '成功-Success',
|
||||
@@ -388,11 +405,13 @@ export default class extends Vue {
|
||||
warn: '提醒-Warn',
|
||||
none: '不记录日志-None'
|
||||
}
|
||||
server = db.get('settings.server') || {
|
||||
|
||||
server = {
|
||||
port: 36677,
|
||||
host: '127.0.0.1',
|
||||
enable: true
|
||||
}
|
||||
|
||||
version = pkg.version
|
||||
latestVersion = ''
|
||||
os = ''
|
||||
@@ -404,40 +423,85 @@ export default class extends Vue {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
created () {
|
||||
this.os = process.platform
|
||||
ipcRenderer.send('getPicBeds')
|
||||
ipcRenderer.on('getPicBeds', this.getPicBeds)
|
||||
this.initData()
|
||||
}
|
||||
|
||||
async initData () {
|
||||
const config = (await this.getConfig<IConfig>())!
|
||||
if (config !== undefined) {
|
||||
const settings = config.settings || {}
|
||||
const picBed = config.picBed
|
||||
this.form.updateHelper = settings.showUpdateTip || false
|
||||
this.form.autoStart = settings.autoStart || false
|
||||
this.form.rename = settings.rename || false
|
||||
this.form.autoRename = settings.autoRename || false
|
||||
this.form.uploadNotification = settings.uploadNotification || false
|
||||
this.form.miniWindowOntop = settings.miniWindowOntop || false
|
||||
this.form.logLevel = this.initLogLevel(settings.logLevel || [])
|
||||
this.form.autoCopyUrl = settings.autoCopy === undefined ? true : settings.autoCopy
|
||||
this.form.checkBetaUpdate = settings.checkBetaUpdate === undefined ? true : settings.checkBetaUpdate
|
||||
|
||||
this.customLink.value = settings.customLink || '$url'
|
||||
this.shortKey.upload = settings.shortKey.upload
|
||||
this.proxy = picBed.proxy || ''
|
||||
this.npmRegistry = settings.registry || ''
|
||||
this.npmProxy = settings.proxy || ''
|
||||
this.server = settings.server || {
|
||||
port: 36677,
|
||||
host: '127.0.0.1',
|
||||
enable: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initLogLevel (logLevel: string | string[]) {
|
||||
if (!Array.isArray(logLevel)) {
|
||||
if (logLevel && logLevel.length > 0) {
|
||||
logLevel = [logLevel]
|
||||
} else {
|
||||
logLevel = ['all']
|
||||
}
|
||||
}
|
||||
return logLevel
|
||||
}
|
||||
|
||||
getPicBeds (event: Event, picBeds: IPicBedType[]) {
|
||||
this.picBed = picBeds
|
||||
this.form.showPicBedList = this.picBed.map(item => {
|
||||
if (item.visible) {
|
||||
return item.name
|
||||
}
|
||||
}) as string[]
|
||||
return null
|
||||
}).filter(item => item) as string[]
|
||||
}
|
||||
|
||||
openFile (file: string) {
|
||||
const { app, shell } = remote
|
||||
const STORE_PATH = app.getPath('userData')
|
||||
const FILE = path.join(STORE_PATH, `/${file}`)
|
||||
shell.openItem(FILE)
|
||||
ipcRenderer.send(PICGO_OPEN_FILE, file)
|
||||
}
|
||||
|
||||
openLogSetting () {
|
||||
this.logFileVisible = true
|
||||
}
|
||||
|
||||
keyDetect (type: string, event: KeyboardEvent) {
|
||||
this.shortKey[type] = keyDetect(event).join('+')
|
||||
}
|
||||
cancelCustomLink () {
|
||||
|
||||
async cancelCustomLink () {
|
||||
this.customLinkVisible = false
|
||||
this.customLink.value = db.get('settings.customLink') || '$url'
|
||||
this.customLink.value = await this.getConfig<string>('settings.customLink') || '$url'
|
||||
}
|
||||
|
||||
confirmCustomLink () {
|
||||
// @ts-ignore
|
||||
this.$refs.customLink.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
db.set('settings.customLink', this.customLink.value)
|
||||
this.saveConfig('settings.customLink', this.customLink.value)
|
||||
this.customLinkVisible = false
|
||||
ipcRenderer.send('updateCustomLink')
|
||||
} else {
|
||||
@@ -445,14 +509,18 @@ export default class extends Vue {
|
||||
}
|
||||
})
|
||||
}
|
||||
cancelProxy () {
|
||||
|
||||
async cancelProxy () {
|
||||
this.proxyVisible = false
|
||||
this.proxy = db.get('picBed.proxy') || undefined
|
||||
this.proxy = await this.getConfig<string>('picBed.proxy') || ''
|
||||
}
|
||||
|
||||
confirmProxy () {
|
||||
this.proxyVisible = false
|
||||
this.letPicGoSaveData({
|
||||
'picBed.proxy': this.proxy
|
||||
this.saveConfig({
|
||||
'picBed.proxy': this.proxy,
|
||||
'settings.proxy': this.npmProxy,
|
||||
'settings.registry': this.npmRegistry
|
||||
})
|
||||
const successNotification = new Notification('设置代理', {
|
||||
body: '设置成功'
|
||||
@@ -461,12 +529,15 @@ export default class extends Vue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
updateHelperChange (val: boolean) {
|
||||
db.set('settings.showUpdateTip', val)
|
||||
this.saveConfig('settings.showUpdateTip', val)
|
||||
}
|
||||
|
||||
checkBetaUpdateChange (val: boolean) {
|
||||
db.set('settings.checkBetaUpdate', val)
|
||||
this.saveConfig('settings.checkBetaUpdate', val)
|
||||
}
|
||||
|
||||
handleShowPicBedListChange (val: string[]) {
|
||||
const list = this.picBed.map(item => {
|
||||
if (!val.includes(item.name)) {
|
||||
@@ -476,25 +547,29 @@ export default class extends Vue {
|
||||
}
|
||||
return item
|
||||
})
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'picBed.list': list
|
||||
})
|
||||
ipcRenderer.send('getPicBeds')
|
||||
}
|
||||
|
||||
handleAutoStartChange (val: boolean) {
|
||||
db.set('settings.autoStart', val)
|
||||
this.saveConfig('settings.autoStart', val)
|
||||
ipcRenderer.send('autoStart', val)
|
||||
}
|
||||
|
||||
handleRename (val: boolean) {
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'settings.rename': val
|
||||
})
|
||||
}
|
||||
|
||||
handleAutoRename (val: boolean) {
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'settings.autoRename': val
|
||||
})
|
||||
}
|
||||
|
||||
compareVersion2Update (current: string, latest: string) {
|
||||
const currentVersion = current.split('.').map(item => parseInt(item))
|
||||
const latestVersion = latest.split('.').map(item => parseInt(item))
|
||||
@@ -509,6 +584,7 @@ export default class extends Vue {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
checkUpdate () {
|
||||
this.checkUpdateVisible = true
|
||||
this.$http.get(releaseUrl)
|
||||
@@ -523,24 +599,31 @@ export default class extends Vue {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
confirmCheckVersion () {
|
||||
if (this.needUpdate) {
|
||||
remote.shell.openExternal(downloadUrl)
|
||||
ipcRenderer.send(OPEN_URL, downloadUrl)
|
||||
}
|
||||
this.checkUpdateVisible = false
|
||||
}
|
||||
|
||||
cancelCheckVersion () {
|
||||
this.checkUpdateVisible = false
|
||||
}
|
||||
|
||||
handleUploadNotification (val: boolean) {
|
||||
db.set('settings.uploadNotification', val)
|
||||
this.saveConfig({
|
||||
'settings.uploadNotification': val
|
||||
})
|
||||
}
|
||||
|
||||
handleMiniWindowOntop (val: boolean) {
|
||||
db.set('settings.miniWindowOntop', val)
|
||||
this.saveConfig('settings.miniWindowOntop', val)
|
||||
this.$message.info('需要重启生效')
|
||||
}
|
||||
|
||||
handleAutoCopyUrl (val: boolean) {
|
||||
db.set('settings.autoCopy', val)
|
||||
this.saveConfig('settings.autoCopy', val)
|
||||
const successNotification = new Notification('设置自动复制链接', {
|
||||
body: '设置成功'
|
||||
})
|
||||
@@ -548,11 +631,12 @@ export default class extends Vue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
confirmLogLevelSetting () {
|
||||
if (this.form.logLevel.length === 0) {
|
||||
return this.$message.error('请选择日志记录等级')
|
||||
}
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'settings.logLevel': this.form.logLevel
|
||||
})
|
||||
const successNotification = new Notification('设置日志', {
|
||||
@@ -563,9 +647,10 @@ export default class extends Vue {
|
||||
}
|
||||
this.logFileVisible = false
|
||||
}
|
||||
cancelLogLevelSetting () {
|
||||
|
||||
async cancelLogLevelSetting () {
|
||||
this.logFileVisible = false
|
||||
let logLevel = db.get('settings.logLevel')
|
||||
let logLevel = await this.getConfig<string | string[]>('settings.logLevel')
|
||||
if (!Array.isArray(logLevel)) {
|
||||
if (logLevel && logLevel.length > 0) {
|
||||
logLevel = [logLevel]
|
||||
@@ -575,9 +660,11 @@ export default class extends Vue {
|
||||
}
|
||||
this.form.logLevel = logLevel
|
||||
}
|
||||
|
||||
confirmServerSetting () {
|
||||
// @ts-ignore
|
||||
this.server.port = parseInt(this.server.port, 10)
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'settings.server': this.server
|
||||
})
|
||||
const successNotification = new Notification('设置PicGo-Server', {
|
||||
@@ -589,18 +676,20 @@ export default class extends Vue {
|
||||
this.serverVisible = false
|
||||
ipcRenderer.send('updateServer')
|
||||
}
|
||||
cancelServerSetting () {
|
||||
|
||||
async cancelServerSetting () {
|
||||
this.serverVisible = false
|
||||
this.server = db.get('settings.server') || {
|
||||
this.server = await this.getConfig('settings.server') || {
|
||||
port: 36677,
|
||||
host: '127.0.0.1',
|
||||
enable: true
|
||||
}
|
||||
}
|
||||
|
||||
handleLevelDisabled (val: string) {
|
||||
let currentLevel = val
|
||||
const currentLevel = val
|
||||
let flagLevel
|
||||
let result = this.form.logLevel.some(item => {
|
||||
const result = this.form.logLevel.some(item => {
|
||||
if (item === 'all' || item === 'none') {
|
||||
flagLevel = item
|
||||
}
|
||||
@@ -617,12 +706,15 @@ export default class extends Vue {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
goConfigPage () {
|
||||
remote.shell.openExternal('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
|
||||
ipcRenderer.send(OPEN_URL, 'https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
|
||||
}
|
||||
|
||||
goShortCutPage () {
|
||||
this.$router.push('shortKey')
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
|
||||
}
|
||||
|
||||
+124
-134
@@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<div id="plugin-view">
|
||||
<div class="view-title">
|
||||
插件设置 - <i class="el-icon-goods" @click="goAwesomeList"></i>
|
||||
插件设置 -
|
||||
<el-tooltip :content="pluginListToolTip" placement="right">
|
||||
<i class="el-icon-goods" @click="goAwesomeList"></i>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="importLocalPluginToolTip" placement="left">
|
||||
<i class="el-icon-download" @click="handleImportLocalPlugin"/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-row class="handle-bar" :class="{ 'cut-width': pluginList.length > 6 }">
|
||||
<el-input
|
||||
@@ -13,7 +19,7 @@
|
||||
</el-input>
|
||||
</el-row>
|
||||
<el-row :gutter="10" class="plugin-list" v-loading="loading">
|
||||
<el-col :span="12" v-for="item in pluginList" :key="item.name">
|
||||
<el-col :span="12" v-for="item in pluginList" :key="item.fullName">
|
||||
<div class="plugin-item" :class="{ 'darwin': os === 'darwin' }">
|
||||
<div class="cli-only-badge" v-if="!item.gui" title="CLI only">CLI</div>
|
||||
<img class="plugin-item__logo" :src="item.logo"
|
||||
@@ -103,10 +109,17 @@ import ConfigForm from '@/components/ConfigForm.vue'
|
||||
import { debounce } from 'lodash'
|
||||
import {
|
||||
ipcRenderer,
|
||||
remote,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
const { Menu } = remote
|
||||
import { handleStreamlinePluginName } from '~/universal/utils/common'
|
||||
import {
|
||||
OPEN_URL,
|
||||
RELOAD_APP,
|
||||
PICGO_CONFIG_PLUGIN,
|
||||
PICGO_HANDLE_PLUGIN_ING,
|
||||
PICGO_TOGGLE_PLUGIN,
|
||||
SHOW_PLUGIN_PAGE_MENU
|
||||
} from '#/events/constants'
|
||||
|
||||
@Component({
|
||||
name: 'plugin',
|
||||
@@ -125,6 +138,8 @@ export default class extends Vue {
|
||||
pluginNameList: string[] = []
|
||||
loading = true
|
||||
needReload = false
|
||||
pluginListToolTip = '插件列表'
|
||||
importLocalPluginToolTip = '导入本地插件'
|
||||
id = ''
|
||||
os = ''
|
||||
defaultLogo: string = 'this.src="https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@dev/public/roundLogo.png"'
|
||||
@@ -135,6 +150,7 @@ export default class extends Vue {
|
||||
? `picgo-plugin-${this.searchText}`
|
||||
: this.searchText
|
||||
}
|
||||
|
||||
@Watch('npmSearchText')
|
||||
onNpmSearchTextChange (val: string) {
|
||||
if (val) {
|
||||
@@ -145,6 +161,7 @@ export default class extends Vue {
|
||||
this.getPluginList()
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('dialogVisible')
|
||||
onDialogVisible (val: boolean) {
|
||||
if (val) {
|
||||
@@ -155,26 +172,33 @@ export default class extends Vue {
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 10
|
||||
}
|
||||
}
|
||||
created () {
|
||||
|
||||
async created () {
|
||||
this.os = process.platform
|
||||
ipcRenderer.on('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => {
|
||||
this.pluginList = list
|
||||
this.pluginNameList = list.map(item => item.name)
|
||||
ipcRenderer.on('hideLoading', () => {
|
||||
this.loading = false
|
||||
})
|
||||
ipcRenderer.on('installSuccess', (evt: IpcRendererEvent, plugin: string) => {
|
||||
ipcRenderer.on('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => {
|
||||
this.pluginList = list
|
||||
this.pluginNameList = list.map(item => item.fullName)
|
||||
this.loading = false
|
||||
})
|
||||
ipcRenderer.on('installPlugin', (evt: IpcRendererEvent, { success, body }: {
|
||||
success: boolean,
|
||||
body: string
|
||||
}) => {
|
||||
this.loading = false
|
||||
this.pluginList.forEach(item => {
|
||||
if (item.name === plugin) {
|
||||
if (item.fullName === body) {
|
||||
item.ing = false
|
||||
item.hasInstall = true
|
||||
item.hasInstall = success
|
||||
}
|
||||
})
|
||||
})
|
||||
ipcRenderer.on('updateSuccess', (evt: IpcRendererEvent, plugin: string) => {
|
||||
this.loading = false
|
||||
this.pluginList.forEach(item => {
|
||||
if (item.name === plugin) {
|
||||
if (item.fullName === plugin) {
|
||||
item.ing = false
|
||||
item.hasInstall = true
|
||||
}
|
||||
@@ -186,7 +210,7 @@ export default class extends Vue {
|
||||
ipcRenderer.on('uninstallSuccess', (evt: IpcRendererEvent, plugin: string) => {
|
||||
this.loading = false
|
||||
this.pluginList = this.pluginList.filter(item => {
|
||||
if (item.name === plugin) { // restore Uploader & Transformer after uninstalling
|
||||
if (item.fullName === plugin) { // restore Uploader & Transformer after uninstalling
|
||||
if (item.config.transformer.name) {
|
||||
this.handleRestoreState('transformer', item.config.transformer.name)
|
||||
}
|
||||
@@ -195,106 +219,49 @@ export default class extends Vue {
|
||||
}
|
||||
this.getPicBeds()
|
||||
}
|
||||
return item.name !== plugin
|
||||
return item.fullName !== plugin
|
||||
})
|
||||
this.pluginNameList = this.pluginNameList.filter(item => item !== plugin)
|
||||
})
|
||||
ipcRenderer.on(PICGO_CONFIG_PLUGIN, (evt: IpcRendererEvent, currentType: string, configName: string, config: any) => {
|
||||
this.currentType = currentType
|
||||
this.configName = configName
|
||||
this.dialogVisible = true
|
||||
this.config = config
|
||||
})
|
||||
ipcRenderer.on(PICGO_HANDLE_PLUGIN_ING, (evt: IpcRendererEvent, fullName: string) => {
|
||||
this.pluginList.forEach(item => {
|
||||
if (item.fullName === fullName || (item.name === fullName)) {
|
||||
item.ing = true
|
||||
}
|
||||
})
|
||||
this.loading = true
|
||||
})
|
||||
ipcRenderer.on(PICGO_TOGGLE_PLUGIN, (evt: IpcRendererEvent, fullName: string, enabled: boolean) => {
|
||||
const plugin = this.pluginList.find(item => item.fullName === fullName)
|
||||
if (plugin) {
|
||||
plugin.enabled = enabled
|
||||
this.getPicBeds()
|
||||
this.needReload = true
|
||||
}
|
||||
})
|
||||
this.getPluginList()
|
||||
this.getSearchResult = debounce(this.getSearchResult, 50)
|
||||
this.needReload = this.$db.get('needReload')
|
||||
this.needReload = await this.getConfig<boolean>('needReload') || false
|
||||
}
|
||||
buildContextMenu (plugin: IPicGoPlugin) {
|
||||
const _this = this
|
||||
let menu = [{
|
||||
label: '启用插件',
|
||||
enabled: !plugin.enabled,
|
||||
click () {
|
||||
_this.letPicGoSaveData({
|
||||
[`picgoPlugins.picgo-plugin-${plugin.name}`]: true
|
||||
})
|
||||
plugin.enabled = true
|
||||
_this.getPicBeds()
|
||||
}
|
||||
}, {
|
||||
label: '禁用插件',
|
||||
enabled: plugin.enabled,
|
||||
click () {
|
||||
_this.letPicGoSaveData({
|
||||
[`picgoPlugins.picgo-plugin-${plugin.name}`]: false
|
||||
})
|
||||
plugin.enabled = false
|
||||
_this.getPicBeds()
|
||||
if (plugin.config.transformer.name) {
|
||||
_this.handleRestoreState('transformer', plugin.config.transformer.name)
|
||||
}
|
||||
if (plugin.config.uploader.name) {
|
||||
_this.handleRestoreState('uploader', plugin.config.uploader.name)
|
||||
}
|
||||
}
|
||||
}, {
|
||||
label: '卸载插件',
|
||||
click () {
|
||||
_this.uninstallPlugin(plugin.name)
|
||||
}
|
||||
}, {
|
||||
label: '更新插件',
|
||||
click () {
|
||||
_this.updatePlugin(plugin.name)
|
||||
}
|
||||
}]
|
||||
for (let i in plugin.config) {
|
||||
if (plugin.config[i].config.length > 0) {
|
||||
const obj = {
|
||||
label: `配置${i} - ${plugin.config[i].name}`,
|
||||
click () {
|
||||
_this.currentType = i
|
||||
_this.configName = plugin.config[i].name
|
||||
_this.dialogVisible = true
|
||||
_this.config = plugin.config[i].config
|
||||
}
|
||||
}
|
||||
menu.push(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// handle transformer
|
||||
if (plugin.config.transformer.name) {
|
||||
let currentTransformer = this.$db.get('picBed.transformer') || 'path'
|
||||
let pluginTransformer = plugin.config.transformer.name
|
||||
const obj = {
|
||||
label: `${currentTransformer === pluginTransformer ? '禁用' : '启用'}transformer - ${plugin.config.transformer.name}`,
|
||||
click () {
|
||||
_this.toggleTransformer(plugin.config.transformer.name)
|
||||
}
|
||||
}
|
||||
menu.push(obj)
|
||||
}
|
||||
|
||||
// plugin custom menus
|
||||
if (plugin.guiMenu) {
|
||||
menu.push({
|
||||
// @ts-ignore
|
||||
type: 'separator'
|
||||
})
|
||||
for (let i of plugin.guiMenu) {
|
||||
menu.push({
|
||||
label: i.label,
|
||||
click () {
|
||||
ipcRenderer.send('pluginActions', plugin.name, i.label)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.menu = Menu.buildFromTemplate(menu)
|
||||
this.menu.popup()
|
||||
async buildContextMenu (plugin: IPicGoPlugin) {
|
||||
ipcRenderer.send(SHOW_PLUGIN_PAGE_MENU, plugin)
|
||||
}
|
||||
|
||||
getPluginList () {
|
||||
ipcRenderer.send('getPluginList')
|
||||
}
|
||||
|
||||
getPicBeds () {
|
||||
ipcRenderer.send('getPicBeds')
|
||||
}
|
||||
|
||||
installPlugin (item: IPicGoPlugin) {
|
||||
if (!item.gui) {
|
||||
this.$confirm('该插件未对可视化界面进行优化, 是否继续安装?', '提示', {
|
||||
@@ -303,37 +270,44 @@ export default class extends Vue {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
item.ing = true
|
||||
ipcRenderer.send('installPlugin', item.name)
|
||||
ipcRenderer.send('installPlugin', item.fullName)
|
||||
}).catch(() => {
|
||||
console.log('Install canceled')
|
||||
})
|
||||
} else {
|
||||
item.ing = true
|
||||
ipcRenderer.send('installPlugin', item.name)
|
||||
ipcRenderer.send('installPlugin', item.fullName)
|
||||
}
|
||||
}
|
||||
|
||||
uninstallPlugin (val: string) {
|
||||
this.pluginList.forEach(item => {
|
||||
if (item.name === val) {
|
||||
item.ing = true
|
||||
}
|
||||
})
|
||||
this.loading = true
|
||||
ipcRenderer.send('uninstallPlugin', val)
|
||||
}
|
||||
|
||||
updatePlugin (val: string) {
|
||||
this.pluginList.forEach(item => {
|
||||
if (item.name === val) {
|
||||
if (item.fullName === val) {
|
||||
item.ing = true
|
||||
}
|
||||
})
|
||||
this.loading = true
|
||||
ipcRenderer.send('updatePlugin', val)
|
||||
}
|
||||
|
||||
reloadApp () {
|
||||
remote.app.relaunch()
|
||||
remote.app.exit(0)
|
||||
ipcRenderer.send(RELOAD_APP)
|
||||
}
|
||||
handleReload () {
|
||||
this.$db.set('needReload', true)
|
||||
|
||||
async handleReload () {
|
||||
this.saveConfig({
|
||||
needReload: true
|
||||
})
|
||||
this.needReload = true
|
||||
const successNotification = new Notification('更新成功', {
|
||||
body: '请点击此通知重启应用以生效'
|
||||
@@ -342,38 +316,28 @@ export default class extends Vue {
|
||||
this.reloadApp()
|
||||
}
|
||||
}
|
||||
|
||||
cleanSearch () {
|
||||
this.searchText = ''
|
||||
}
|
||||
toggleTransformer (transformer: string) {
|
||||
let currentTransformer = this.$db.get('picBed.transformer') || 'path'
|
||||
if (currentTransformer === transformer) {
|
||||
this.letPicGoSaveData({
|
||||
'picBed.transformer': 'path'
|
||||
})
|
||||
} else {
|
||||
this.letPicGoSaveData({
|
||||
'picBed.transformer': transformer
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async handleConfirmConfig () {
|
||||
// @ts-ignore
|
||||
const result = await this.$refs.configForm.validate()
|
||||
if (result !== false) {
|
||||
switch (this.currentType) {
|
||||
case 'plugin':
|
||||
this.letPicGoSaveData({
|
||||
[`picgo-plugin-${this.configName}`]: result
|
||||
this.saveConfig({
|
||||
[`${this.configName}`]: result
|
||||
})
|
||||
break
|
||||
case 'uploader':
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
[`picBed.${this.configName}`]: result
|
||||
})
|
||||
break
|
||||
case 'transformer':
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
[`transformer.${this.configName}`]: result
|
||||
})
|
||||
break
|
||||
@@ -388,6 +352,7 @@ export default class extends Vue {
|
||||
this.getPluginList()
|
||||
}
|
||||
}
|
||||
|
||||
getSearchResult (val: string) {
|
||||
// this.$http.get(`https://api.npms.io/v2/search?q=${val}`)
|
||||
this.$http.get(`https://registry.npmjs.com/-/v1/search?text=${val}`)
|
||||
@@ -406,8 +371,9 @@ export default class extends Vue {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchResult (item: INPMSearchResultObject) {
|
||||
const name = item.package.name.replace(/picgo-plugin-/, '')
|
||||
const name = handleStreamlinePluginName(item.package.name)
|
||||
let gui = false
|
||||
if (item.package.keywords && item.package.keywords.length > 0) {
|
||||
if (item.package.keywords.includes('picgo-gui-plugin')) {
|
||||
@@ -416,52 +382,65 @@ export default class extends Vue {
|
||||
}
|
||||
return {
|
||||
name: name,
|
||||
fullName: item.package.name,
|
||||
author: item.package.author.name,
|
||||
description: item.package.description,
|
||||
logo: `https://cdn.jsdelivr.net/npm/${item.package.name}/logo.png`,
|
||||
config: {},
|
||||
homepage: item.package.links ? item.package.links.homepage : '',
|
||||
hasInstall: this.pluginNameList.some(plugin => plugin === item.package.name.replace(/picgo-plugin-/, '')),
|
||||
hasInstall: this.pluginNameList.some(plugin => plugin === item.package.name),
|
||||
version: item.package.version,
|
||||
gui,
|
||||
ing: false // installing or uninstalling
|
||||
}
|
||||
}
|
||||
|
||||
// restore Uploader & Transformer
|
||||
handleRestoreState (item: string, name: string) {
|
||||
async handleRestoreState (item: string, name: string) {
|
||||
if (item === 'uploader') {
|
||||
const current = this.$db.get('picBed.current')
|
||||
const current = await this.getConfig('picBed.current')
|
||||
if (current === name) {
|
||||
this.letPicGoSaveData({
|
||||
'picBed.current': 'smms'
|
||||
this.saveConfig({
|
||||
'picBed.current': 'smms',
|
||||
'picBed.uploader': 'smms'
|
||||
})
|
||||
}
|
||||
}
|
||||
if (item === 'transformer') {
|
||||
const current = this.$db.get('picBed.transformer')
|
||||
const current = await this.getConfig('picBed.transformer')
|
||||
if (current === name) {
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'picBed.transformer': 'path'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openHomepage (url: string) {
|
||||
if (url) {
|
||||
remote.shell.openExternal(url)
|
||||
ipcRenderer.send(OPEN_URL, url)
|
||||
}
|
||||
}
|
||||
|
||||
goAwesomeList () {
|
||||
remote.shell.openExternal('https://github.com/PicGo/Awesome-PicGo')
|
||||
ipcRenderer.send(OPEN_URL, 'https://github.com/PicGo/Awesome-PicGo')
|
||||
}
|
||||
letPicGoSaveData (data: IObj) {
|
||||
|
||||
saveConfig (data: IObj) {
|
||||
ipcRenderer.send('picgoSaveData', data)
|
||||
}
|
||||
|
||||
handleImportLocalPlugin () {
|
||||
ipcRenderer.send('importLocalPlugin')
|
||||
this.loading = true
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeAllListeners('pluginList')
|
||||
ipcRenderer.removeAllListeners('installSuccess')
|
||||
ipcRenderer.removeAllListeners('installPlugin')
|
||||
ipcRenderer.removeAllListeners('uninstallSuccess')
|
||||
ipcRenderer.removeAllListeners('updateSuccess')
|
||||
ipcRenderer.removeAllListeners('hideLoading')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -491,6 +470,7 @@ $darwinBg = #172426
|
||||
font-size 20px
|
||||
text-align center
|
||||
margin 10px auto
|
||||
position relative
|
||||
i.el-icon-goods
|
||||
font-size 20px
|
||||
vertical-align middle
|
||||
@@ -498,6 +478,16 @@ $darwinBg = #172426
|
||||
transition color .2s ease-in-out
|
||||
&:hover
|
||||
color #49B1F5
|
||||
i.el-icon-download
|
||||
position absolute
|
||||
right 0
|
||||
top 8px
|
||||
font-size 20px
|
||||
vertical-align middle
|
||||
cursor pointer
|
||||
transition color .2s ease-in-out
|
||||
&:hover
|
||||
color #49B1F5
|
||||
.handle-bar
|
||||
margin-bottom 20px
|
||||
&.cut-width
|
||||
|
||||
@@ -41,12 +41,15 @@ export default class extends Vue {
|
||||
this.id = id
|
||||
})
|
||||
}
|
||||
|
||||
confirmName () {
|
||||
ipcRenderer.send(`rename${this.id}`, this.fileName)
|
||||
}
|
||||
|
||||
cancel () {
|
||||
ipcRenderer.send(`rename${this.id}`, null)
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeAllListeners('rename')
|
||||
}
|
||||
|
||||
@@ -109,8 +109,8 @@ export default class extends Vue {
|
||||
command = ''
|
||||
shortKey = ''
|
||||
currentIndex = 0
|
||||
created () {
|
||||
const shortKeyConfig = this.$db.get('settings.shortKey') as IShortKeyConfigs
|
||||
async created () {
|
||||
const shortKeyConfig = (await this.getConfig<IShortKeyConfigs>('settings.shortKey'))!
|
||||
this.list = Object.keys(shortKeyConfig).map(item => {
|
||||
return {
|
||||
...shortKeyConfig[item],
|
||||
@@ -118,40 +118,45 @@ export default class extends Vue {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Watch('keyBindingVisible')
|
||||
onKeyBindingVisibleChange (val: boolean) {
|
||||
ipcRenderer.send(TOGGLE_SHORTKEY_MODIFIED_MODE, val)
|
||||
}
|
||||
|
||||
calcOrigin (item: string) {
|
||||
const [origin] = item.split(':')
|
||||
return origin
|
||||
}
|
||||
|
||||
calcOriginShowName (item: string) {
|
||||
return item.replace('picgo-plugin-', '')
|
||||
}
|
||||
|
||||
toggleEnable (item: IShortKeyConfig) {
|
||||
const status = !item.enable
|
||||
item.enable = status
|
||||
// this.$db.set(`settings.shortKey.${item.name}.enable`, status)
|
||||
ipcRenderer.send('bindOrUnbindShortKey', item, item.from)
|
||||
}
|
||||
|
||||
keyDetect (event: KeyboardEvent) {
|
||||
this.shortKey = keyDetect(event).join('+')
|
||||
}
|
||||
openKeyBindingDialog (config: IShortKeyConfig, index: number) {
|
||||
|
||||
async openKeyBindingDialog (config: IShortKeyConfig, index: number) {
|
||||
this.command = `${config.from}:${config.name}`
|
||||
this.shortKey = this.$db.get(`settings.shortKey.${this.command}.key`)
|
||||
this.shortKey = await this.getConfig(`settings.shortKey.${this.command}.key`) || ''
|
||||
this.currentIndex = index
|
||||
this.keyBindingVisible = true
|
||||
}
|
||||
cancelKeyBinding () {
|
||||
|
||||
async cancelKeyBinding () {
|
||||
this.keyBindingVisible = false
|
||||
this.shortKey = this.$db.get(`settings.shortKey.${this.command}.key`)
|
||||
this.shortKey = await this.getConfig<string>(`settings.shortKey.${this.command}.key`) || ''
|
||||
}
|
||||
confirmKeyBinding () {
|
||||
const oldKey = this.$db.get(`settings.shortKey.${this.command}.key`)
|
||||
// this.$db.set(`settings.shortKey.${this.command}.key`, this.shortKey)
|
||||
// const newKey = this.$db.get(`settings.shortKey.${this.command}`)
|
||||
|
||||
async confirmKeyBinding () {
|
||||
const oldKey = await this.getConfig<string>(`settings.shortKey.${this.command}.key`)
|
||||
const config = Object.assign({}, this.list[this.currentIndex])
|
||||
config.key = this.shortKey
|
||||
ipcRenderer.send('updateShortKey', config, oldKey, config.from)
|
||||
@@ -162,6 +167,7 @@ export default class extends Vue {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.send(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
||||
}
|
||||
|
||||
@@ -28,41 +28,46 @@
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/mixin'
|
||||
import pasteTemplate from '#/utils/pasteTemplate'
|
||||
import { ipcRenderer, clipboard } from 'electron'
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { IResult } from '@picgo/store/dist/types'
|
||||
import { PASTE_TEXT } from '#/events/constants'
|
||||
|
||||
@Component({
|
||||
name: 'tray-page',
|
||||
mixins: [mixin]
|
||||
})
|
||||
export default class extends Vue {
|
||||
files = []
|
||||
files: IResult<ImgInfo>[] = []
|
||||
notification = {
|
||||
title: '复制链接成功',
|
||||
body: '',
|
||||
icon: ''
|
||||
}
|
||||
|
||||
clipboardFiles: ImgInfo[] = []
|
||||
uploadFlag = false
|
||||
get reverseList () {
|
||||
return this.files.slice().reverse()
|
||||
}
|
||||
getData () {
|
||||
// @ts-ignore
|
||||
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
|
||||
|
||||
async getData () {
|
||||
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
|
||||
}
|
||||
copyTheLink (item: ImgInfo) {
|
||||
|
||||
async copyTheLink (item: ImgInfo) {
|
||||
this.notification.body = item.imgUrl!
|
||||
this.notification.icon = item.imgUrl!
|
||||
const myNotification = new Notification(this.notification.title, this.notification)
|
||||
const pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
|
||||
clipboard.writeText(pasteTemplate(pasteStyle, item))
|
||||
ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
calcHeight (width: number, height: number): number {
|
||||
return height * 160 / width
|
||||
}
|
||||
|
||||
disableDragFile () {
|
||||
window.addEventListener('dragover', (e) => {
|
||||
e = e || event
|
||||
@@ -73,6 +78,7 @@ export default class extends Vue {
|
||||
e.preventDefault()
|
||||
}, false)
|
||||
}
|
||||
|
||||
uploadClipboardFiles () {
|
||||
if (this.uploadFlag) {
|
||||
return
|
||||
@@ -80,29 +86,30 @@ export default class extends Vue {
|
||||
this.uploadFlag = true
|
||||
ipcRenderer.send('uploadClipboardFiles')
|
||||
}
|
||||
|
||||
mounted () {
|
||||
this.disableDragFile()
|
||||
this.getData()
|
||||
ipcRenderer.on('dragFiles', (event: Event, files: string[]) => {
|
||||
files.forEach(item => {
|
||||
this.$db.insert('uploaded', item)
|
||||
})
|
||||
// @ts-ignore
|
||||
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
|
||||
ipcRenderer.on('dragFiles', async (event: Event, files: string[]) => {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const item = files[i]
|
||||
await this.$$db.insert(item)
|
||||
}
|
||||
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
|
||||
})
|
||||
ipcRenderer.on('clipboardFiles', (event: Event, files: ImgInfo[]) => {
|
||||
this.clipboardFiles = files
|
||||
})
|
||||
ipcRenderer.on('uploadFiles', (event: Event) => {
|
||||
// @ts-ignore
|
||||
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
|
||||
ipcRenderer.on('uploadFiles', async () => {
|
||||
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
|
||||
console.log(this.files)
|
||||
this.uploadFlag = false
|
||||
})
|
||||
ipcRenderer.on('updateFiles', (event: Event) => {
|
||||
ipcRenderer.on('updateFiles', () => {
|
||||
this.getData()
|
||||
})
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeAllListeners('dragFiles')
|
||||
ipcRenderer.removeAllListeners('clipboardFiles')
|
||||
|
||||
@@ -60,17 +60,16 @@
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent,
|
||||
remote
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
import {
|
||||
SHOW_INPUT_BOX,
|
||||
SHOW_INPUT_BOX_RESPONSE
|
||||
SHOW_INPUT_BOX_RESPONSE,
|
||||
SHOW_UPLOAD_PAGE_MENU
|
||||
} from '~/universal/events/constants'
|
||||
import {
|
||||
isUrl
|
||||
} from '~/universal/utils/common'
|
||||
const { Menu } = remote
|
||||
@Component({
|
||||
name: 'upload'
|
||||
})
|
||||
@@ -82,7 +81,6 @@ export default class extends Vue {
|
||||
pasteStyle = ''
|
||||
picBed: IPicBedType[] = []
|
||||
picBedName = ''
|
||||
menu: Electron.Menu | null= null
|
||||
mounted () {
|
||||
ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, progress: number) => {
|
||||
if (progress !== -1) {
|
||||
@@ -102,6 +100,7 @@ export default class extends Vue {
|
||||
ipcRenderer.on('getPicBeds', this.getPicBeds)
|
||||
this.$bus.$on(SHOW_INPUT_BOX_RESPONSE, this.handleInputBoxValue)
|
||||
}
|
||||
|
||||
@Watch('progress')
|
||||
onProgressChange (val: number) {
|
||||
if (val === 100) {
|
||||
@@ -114,12 +113,14 @@ export default class extends Vue {
|
||||
}, 1200)
|
||||
}
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
this.$bus.$off(SHOW_INPUT_BOX_RESPONSE)
|
||||
ipcRenderer.removeAllListeners('uploadProgress')
|
||||
ipcRenderer.removeAllListeners('syncPicBed')
|
||||
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
|
||||
}
|
||||
|
||||
onDrop (e: DragEvent) {
|
||||
this.dragover = false
|
||||
const items = e.dataTransfer!.items
|
||||
@@ -136,6 +137,7 @@ export default class extends Vue {
|
||||
this.ipcSendFiles(e.dataTransfer!.files)
|
||||
}
|
||||
}
|
||||
|
||||
handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer) {
|
||||
// text/html
|
||||
// Use this data to get a more precise URL
|
||||
@@ -151,17 +153,20 @@ export default class extends Vue {
|
||||
this.$message.error('请拖入合法的图片文件或者图片URL地址')
|
||||
}
|
||||
}
|
||||
|
||||
openUplodWindow () {
|
||||
document.getElementById('file-uploader')!.click()
|
||||
}
|
||||
|
||||
onChange (e: any) {
|
||||
this.ipcSendFiles(e.target.files);
|
||||
(document.getElementById('file-uploader') as HTMLInputElement).value = ''
|
||||
}
|
||||
|
||||
ipcSendFiles (files: FileList) {
|
||||
let sendFiles: IFileWithPath[] = []
|
||||
Array.from(files).forEach((item, index) => {
|
||||
let obj = {
|
||||
const sendFiles: IFileWithPath[] = []
|
||||
Array.from(files).forEach((item) => {
|
||||
const obj = {
|
||||
name: item.name,
|
||||
path: item.path
|
||||
}
|
||||
@@ -169,15 +174,21 @@ export default class extends Vue {
|
||||
})
|
||||
ipcRenderer.send('uploadChoosedFiles', sendFiles)
|
||||
}
|
||||
getPasteStyle () {
|
||||
this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
|
||||
|
||||
async getPasteStyle () {
|
||||
this.pasteStyle = await this.getConfig('settings.pasteStyle') || 'markdown'
|
||||
}
|
||||
|
||||
handlePasteStyleChange (val: string) {
|
||||
this.$db.set('settings.pasteStyle', val)
|
||||
this.saveConfig({
|
||||
'settings.pasteStyle': val
|
||||
})
|
||||
}
|
||||
|
||||
uploadClipboardFiles () {
|
||||
ipcRenderer.send('uploadClipboardFilesFromUploadPage')
|
||||
}
|
||||
|
||||
async uploadURLFiles () {
|
||||
const str = await navigator.clipboard.readText()
|
||||
this.$bus.$emit(SHOW_INPUT_BOX, {
|
||||
@@ -186,6 +197,7 @@ export default class extends Vue {
|
||||
placeholder: 'http://或者https://开头'
|
||||
})
|
||||
}
|
||||
|
||||
handleInputBoxValue (val: string) {
|
||||
if (val === '') return false
|
||||
if (isUrl(val)) {
|
||||
@@ -196,40 +208,23 @@ export default class extends Vue {
|
||||
this.$message.error('请输入合法的URL')
|
||||
}
|
||||
}
|
||||
getDefaultPicBed () {
|
||||
const current: string = this.$db.get('picBed.current')
|
||||
|
||||
async getDefaultPicBed () {
|
||||
const currentPicBed = await this.getConfig<string>('picBed.current')
|
||||
this.picBed.forEach(item => {
|
||||
if (item.type === current) {
|
||||
if (item.type === currentPicBed) {
|
||||
this.picBedName = item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getPicBeds (event: Event, picBeds: IPicBedType[]) {
|
||||
this.picBed = picBeds
|
||||
this.getDefaultPicBed()
|
||||
}
|
||||
handleChangePicBed () {
|
||||
this.buildMenu()
|
||||
// this.menu.popup(remote.getCurrentWindow())
|
||||
this.menu!.popup()
|
||||
}
|
||||
buildMenu () {
|
||||
const _this = this
|
||||
const submenu = this.picBed.filter(item => item.visible).map(item => {
|
||||
return {
|
||||
label: item.name,
|
||||
type: 'radio',
|
||||
checked: this.$db.get('picBed.current') === item.type,
|
||||
click () {
|
||||
_this.letPicGoSaveData({
|
||||
'picBed.current': item.type
|
||||
})
|
||||
ipcRenderer.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
this.menu = Menu.buildFromTemplate(submenu)
|
||||
|
||||
async handleChangePicBed () {
|
||||
ipcRenderer.send(SHOW_UPLOAD_PAGE_MENU)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -86,17 +86,19 @@ export default class extends Vue {
|
||||
customUrl: '',
|
||||
options: ''
|
||||
}
|
||||
created () {
|
||||
const config = this.$db.get('picBed.aliyun') as IAliYunConfig
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<IAliYunConfig>('picBed.aliyun')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.aliyun.validate((valid) => {
|
||||
if (valid) {
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'picBed.aliyun': this.form
|
||||
})
|
||||
const successNotification = new window.Notification('设置结果', {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
:rules="{
|
||||
required: true, message: '分支名不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.branch" @keyup.native.enter="confirm" placeholder="例如:master"></el-input>
|
||||
<el-input v-model="form.branch" @keyup.native.enter="confirm" placeholder="例如:main"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定Token"
|
||||
@@ -71,17 +71,19 @@ export default class extends Vue {
|
||||
customUrl: '',
|
||||
branch: ''
|
||||
}
|
||||
created () {
|
||||
const config = this.$db.get('picBed.github') as IGitHubConfig
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<IGitHubConfig>('picBed.github')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.github.validate((valid) => {
|
||||
if (valid) {
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'picBed.github': this.form
|
||||
})
|
||||
const successNotification = new Notification('设置结果', {
|
||||
|
||||
@@ -48,17 +48,19 @@ export default class extends Vue {
|
||||
clientId: '',
|
||||
proxy: ''
|
||||
}
|
||||
created () {
|
||||
const config = this.$db.get('picBed.imgur') as IImgurConfig
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<IImgurConfig>('picBed.imgur')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.imgur.validate((valid) => {
|
||||
if (valid) {
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'picBed.imgur': this.form
|
||||
})
|
||||
const successNotification = new Notification('设置结果', {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div id="others-view">
|
||||
<el-row :gutter="16">
|
||||
<el-row :gutter="16" class="setting-list">
|
||||
<el-col :span="16" :offset="4">
|
||||
<div class="view-title">
|
||||
{{ picBedName }}设置
|
||||
@@ -52,11 +52,12 @@ export default class extends Vue {
|
||||
ipcRenderer.send('getPicBedConfig', this.$route.params.type)
|
||||
ipcRenderer.on('getPicBedConfig', this.getPicBeds)
|
||||
}
|
||||
|
||||
async handleConfirm () {
|
||||
// @ts-ignore
|
||||
const result = await this.$refs.configForm.validate()
|
||||
if (result !== false) {
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
[`picBed.${this.type}`]: result
|
||||
})
|
||||
const successNotification = new Notification('设置结果', {
|
||||
@@ -67,9 +68,11 @@ export default class extends Vue {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDefaultPicBed (type: string) {
|
||||
this.letPicGoSaveData({
|
||||
'picBed.current': type
|
||||
this.saveConfig({
|
||||
'picBed.current': type,
|
||||
'picBed.uploader': type
|
||||
})
|
||||
// @ts-ignore 来自mixin的数据
|
||||
this.defaultPicBed = type
|
||||
@@ -80,10 +83,12 @@ export default class extends Vue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
getPicBeds (event: IpcRendererEvent, config: any[], name: string) {
|
||||
this.config = config
|
||||
this.picBedName = name
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeListener('getPicBedConfig', this.getPicBeds)
|
||||
}
|
||||
@@ -91,6 +96,10 @@ export default class extends Vue {
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#others-view
|
||||
.setting-list
|
||||
height 425px
|
||||
overflow-y auto
|
||||
overflow-x hidden
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
:rules="{
|
||||
required: true, message: 'AccessKey不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.accessKey" placeholder="AccessKey" @keyup.native.enter="confirm('weiboForm')"></el-input>
|
||||
<el-input v-model="form.accessKey" placeholder="AccessKey" @keyup.native.enter="confirm"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定SecretKey"
|
||||
@@ -88,17 +88,19 @@ export default class extends Vue {
|
||||
options: '',
|
||||
path: ''
|
||||
}
|
||||
created () {
|
||||
const config = this.$db.get('picBed.qiniu') as IQiniuConfig
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<IQiniuConfig>('picBed.qiniu')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.qiniu.validate((valid) => {
|
||||
if (valid) {
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'picBed.qiniu': this.form
|
||||
})
|
||||
const successNotification = new Notification('设置结果', {
|
||||
|
||||
@@ -41,17 +41,19 @@ export default class extends Vue {
|
||||
form: ISMMSConfig = {
|
||||
token: ''
|
||||
}
|
||||
created () {
|
||||
const config = this.$db.get('picBed.smms.token') as (string | boolean)
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<string | boolean>('picBed.smms.token')
|
||||
if (typeof config !== 'boolean') {
|
||||
this.form.token = config
|
||||
this.form.token = config || ''
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.smms.validate((valid) => {
|
||||
if (valid) {
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'picBed.smms': this.form
|
||||
})
|
||||
const successNotification = new window.Notification('设置结果', {
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
:rules="{
|
||||
required: true, message: 'SecretId不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.secretId" placeholder="SecretId" @keyup.native.enter="confirm('weiboForm')"></el-input>
|
||||
<el-input v-model="form.secretId" placeholder="SecretId" @keyup.native.enter="confirm"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定SecretKey"
|
||||
@@ -86,9 +86,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/ConfirmButtonMixin'
|
||||
import { remote } from 'electron'
|
||||
import { OPEN_URL } from '#/events/constants'
|
||||
@Component({
|
||||
name: 'tcyun',
|
||||
mixins: [mixin]
|
||||
@@ -104,17 +105,19 @@ export default class extends Vue {
|
||||
customUrl: '',
|
||||
version: 'v4'
|
||||
}
|
||||
created () {
|
||||
const config = this.$db.get('picBed.tcyun') as ITcYunConfig
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<ITcYunConfig>('picBed.tcyun')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.tcyun.validate((valid) => {
|
||||
if (valid) {
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'picBed.tcyun': this.form
|
||||
})
|
||||
const successNotification = new window.Notification('设置结果', {
|
||||
@@ -128,8 +131,9 @@ export default class extends Vue {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
openWiki () {
|
||||
remote.shell.openExternal('https://github.com/Molunerfinn/PicGo/wiki/%E8%AF%A6%E7%BB%86%E7%AA%97%E5%8F%A3%E7%9A%84%E4%BD%BF%E7%94%A8#腾讯云cos')
|
||||
ipcRenderer.send(OPEN_URL, 'https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E8%85%BE%E8%AE%AF%E4%BA%91cos')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -79,17 +79,19 @@ export default class extends Vue {
|
||||
options: '',
|
||||
path: ''
|
||||
}
|
||||
created () {
|
||||
const config = this.$db.get('picBed.upyun') as IUpYunConfig
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<IUpYunConfig>('picBed.upyun')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.tcyun.validate((valid) => {
|
||||
if (valid) {
|
||||
this.letPicGoSaveData({
|
||||
this.saveConfig({
|
||||
'picBed.upyun': this.form
|
||||
})
|
||||
const successNotification = new Notification('设置结果', {
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
<template>
|
||||
<div id="weibo-view">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16" :offset="4">
|
||||
<div class="view-title">
|
||||
微博图床设置[已停止支持]
|
||||
</div>
|
||||
<el-form
|
||||
ref="weiboForm"
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
size="small"
|
||||
:model="form">
|
||||
<el-form-item
|
||||
label="设定用户名"
|
||||
prop="username"
|
||||
:rules="{
|
||||
required: !chooseCookie, message: '用户名不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.username" placeholder="用户名" @keyup.native.enter="confirm('weiboForm')" :disabled="chooseCookie"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定密码"
|
||||
prop="password"
|
||||
:rules="{required: !chooseCookie,messsage: '密码不能为空',trigger: 'blur'}">
|
||||
<el-input v-model="form.password" type="password" @keyup.native.enter="confirm('weiboForm')" placeholder="密码" :disabled="chooseCookie"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="使用Cookie上传"
|
||||
>
|
||||
<el-switch
|
||||
v-model="chooseCookie"
|
||||
active-text="cookie模式"
|
||||
@change="handleSwitchChange"
|
||||
></el-switch>
|
||||
<i class="el-icon-question" @click="openWiki"></i>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定Cookie"
|
||||
prop="cookie"
|
||||
:rules="{
|
||||
required: chooseCookie, message: '密码不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.cookie" @keyup.native.enter="confirm('weiboForm')" placeholder="Cookie" :disabled="!chooseCookie"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="* 图片质量">
|
||||
<el-radio-group v-model="quality">
|
||||
<el-radio label="thumbnail">缩略图</el-radio>
|
||||
<el-radio label="mw690">中等尺寸</el-radio>
|
||||
<el-radio label="large">原图</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="confirm('weiboForm')" round>确定</el-button>
|
||||
<el-button type="success" @click="setDefaultPicBed('weibo')" round :disabled="defaultPicBed === 'weibo'">设为默认图床</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import mixin from '@/utils/ConfirmButtonMixin'
|
||||
export default {
|
||||
name: 'weibo',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {
|
||||
form: {
|
||||
username: '',
|
||||
password: '',
|
||||
cookie: ''
|
||||
},
|
||||
chooseCookie: false,
|
||||
quality: 'large'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const config = this.$db.get('picBed.weibo')
|
||||
if (config) {
|
||||
this.form.username = config.username
|
||||
this.form.password = config.password
|
||||
this.quality = config.quality || 'large'
|
||||
this.form.cookie = config.cookie
|
||||
this.chooseCookie = config.chooseCookie
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
confirm (formName) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
if (valid) {
|
||||
this.letPicGoSaveData({
|
||||
'picBed.weibo': {
|
||||
username: this.form.username,
|
||||
password: this.form.password,
|
||||
quality: this.quality,
|
||||
cookie: this.form.cookie,
|
||||
chooseCookie: this.chooseCookie
|
||||
}
|
||||
})
|
||||
const successNotification = new window.Notification('设置结果', {
|
||||
body: '设置成功'
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSwitchChange () {
|
||||
this.$refs['weiboForm'].resetFields()
|
||||
},
|
||||
openWiki () {
|
||||
this.$electron.remote.shell.openExternal('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#微博图床')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.el-message
|
||||
left 60%
|
||||
#weibo-view
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
color #eee
|
||||
.el-button-group
|
||||
width 100%
|
||||
.el-button
|
||||
width 50%
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
.el-radio-group
|
||||
margin-left 25px
|
||||
.el-switch__label
|
||||
color #eee
|
||||
&.is-active
|
||||
color #409EFF
|
||||
.el-icon-question
|
||||
font-size 20px
|
||||
float right
|
||||
margin-top 9px
|
||||
color #eee
|
||||
cursor pointer
|
||||
transition .2s color ease-in-out
|
||||
&:hover
|
||||
color #409EFF
|
||||
</style>
|
||||
@@ -31,11 +31,6 @@ export default new Router({
|
||||
component: () => import(/* webpackChunkName: "Upload" */ '@/pages/Upload.vue'),
|
||||
name: 'upload'
|
||||
},
|
||||
{
|
||||
path: 'weibo',
|
||||
component: () => import(/* webpackChunkName: "Weibo" */ '@/pages/picbeds/Weibo.vue'),
|
||||
name: 'weibo'
|
||||
},
|
||||
{
|
||||
path: 'qiniu',
|
||||
component: () => import(/* webpackChunkName: "Qiniu" */ '@/pages/picbeds/Qiniu.vue'),
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { IConfig } from 'picgo/dist/src/types'
|
||||
@Component
|
||||
export default class extends Vue {
|
||||
defaultPicBed = this.$db.get('picBed.current')
|
||||
defaultPicBed = 'smms'
|
||||
async created () {
|
||||
const config = await this.getConfig<IConfig>()
|
||||
if (config) {
|
||||
this.defaultPicBed = config?.picBed?.uploader || config?.picBed?.current || 'smms'
|
||||
}
|
||||
}
|
||||
|
||||
setDefaultPicBed (type: string) {
|
||||
this.letPicGoSaveData({
|
||||
'picBed.current': type
|
||||
this.saveConfig({
|
||||
'picBed.current': type,
|
||||
'picBed.uploader': type
|
||||
})
|
||||
this.defaultPicBed = type
|
||||
const successNotification = new Notification('设置默认图床', {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/* eslint-disable camelcase */
|
||||
import {
|
||||
TALKING_DATA_APPID, TALKING_DATA_EVENT
|
||||
} from '~/universal/events/constants'
|
||||
import pkg from 'root/package.json'
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { handleTalkingDataEvent } from './common'
|
||||
const { version } = pkg
|
||||
|
||||
export const initTalkingData = () => {
|
||||
setTimeout(() => {
|
||||
const talkingDataScript = document.createElement('script')
|
||||
|
||||
talkingDataScript.src = `http://sdk.talkingdata.com/app/h5/v1?appid=${TALKING_DATA_APPID}&vn=${version}&vc=${version}`
|
||||
|
||||
const head = document.getElementsByTagName('head')[0]
|
||||
head.appendChild(talkingDataScript)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
ipcRenderer.on(TALKING_DATA_EVENT, (_, data: ITalkingDataOptions) => {
|
||||
handleTalkingDataEvent(data)
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
/* eslint-disable camelcase */
|
||||
export const handleTalkingDataEvent = (data: ITalkingDataOptions) => {
|
||||
const { EventId, Label = '', MapKv = {} } = data
|
||||
MapKv.from = window.location.href
|
||||
window.TDAPP.onEvent(EventId, Label, MapKv)
|
||||
if (isDevelopment) {
|
||||
console.log('talkingData', data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { IObject, IResult, IGetResult, IFilter } from '@picgo/store/dist/types'
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import { uuid } from 'uuidv4'
|
||||
import {
|
||||
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
|
||||
} from '#/events/constants'
|
||||
import { IGalleryDB } from '#/types/extra-vue'
|
||||
export class GalleryDB implements IGalleryDB {
|
||||
async get<T> (filter?: IFilter): Promise<IGetResult<T>> {
|
||||
const res = await this.msgHandler<IGetResult<T>>(PICGO_GET_DB, filter)
|
||||
return res
|
||||
}
|
||||
|
||||
async insert<T> (value: T): Promise<IResult<T>> {
|
||||
const res = await this.msgHandler<IResult<T>>(PICGO_INSERT_DB, value)
|
||||
return res
|
||||
}
|
||||
|
||||
async insertMany<T> (value: T[]): Promise<IResult<T>[]> {
|
||||
const res = await this.msgHandler<IResult<T>[]>(PICGO_INSERT_MANY_DB, value)
|
||||
return res
|
||||
}
|
||||
|
||||
async updateById (id: string, value: IObject): Promise<boolean> {
|
||||
const res = await this.msgHandler<boolean>(PICGO_UPDATE_BY_ID_DB, id, value)
|
||||
return res
|
||||
}
|
||||
|
||||
async getById<T> (id: string): Promise<IResult<T> | undefined> {
|
||||
const res = await this.msgHandler<IResult<T> | undefined>(PICGO_GET_BY_ID_DB, id)
|
||||
return res
|
||||
}
|
||||
|
||||
async removeById (id: string): Promise<void> {
|
||||
const res = await this.msgHandler<void>(PICGO_REMOVE_BY_ID_DB, id)
|
||||
return res
|
||||
}
|
||||
|
||||
private msgHandler<T> (method: string, ...args: any[]): Promise<T> {
|
||||
return new Promise((resolve) => {
|
||||
const callbackId = uuid()
|
||||
const callback = (event: IpcRendererEvent, data: T, returnCallbackId: string) => {
|
||||
if (returnCallbackId === callbackId) {
|
||||
resolve(data)
|
||||
ipcRenderer.removeListener(method, callback)
|
||||
}
|
||||
}
|
||||
ipcRenderer.on(method, callback)
|
||||
ipcRenderer.send(method, ...args, callbackId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default new GalleryDB()
|
||||
@@ -14,16 +14,16 @@ const isSpecialKey = (keyCode: number) => {
|
||||
|
||||
const keyDetect = (event: KeyboardEvent) => {
|
||||
const meta = process.platform === 'darwin' ? 'Cmd' : 'Super'
|
||||
let specialKey = {
|
||||
const specialKey = {
|
||||
Ctrl: event.ctrlKey,
|
||||
Shift: event.shiftKey,
|
||||
Alt: event.altKey,
|
||||
[meta]: event.metaKey
|
||||
}
|
||||
|
||||
let pressKey = []
|
||||
const pressKey = []
|
||||
|
||||
for (let i in specialKey) {
|
||||
for (const i in specialKey) {
|
||||
if (specialKey[i]) {
|
||||
pressKey.push(i)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import { PICGO_SAVE_CONFIG, PICGO_GET_CONFIG } from '#/events/constants'
|
||||
import { uuid } from 'uuidv4'
|
||||
@Component
|
||||
export default class extends Vue {
|
||||
letPicGoSaveData (data: IObj) {
|
||||
ipcRenderer.send('picgoSaveData', data)
|
||||
// support string key + value or object config
|
||||
saveConfig (config: IObj | string, value?: any) {
|
||||
if (typeof config === 'string') {
|
||||
config = {
|
||||
[config]: value
|
||||
}
|
||||
}
|
||||
ipcRenderer.send(PICGO_SAVE_CONFIG, config)
|
||||
}
|
||||
|
||||
getConfig<T> (key?: string): Promise<T | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
const callbackId = uuid()
|
||||
const callback = (event: IpcRendererEvent, config: T | undefined, returnCallbackId: string) => {
|
||||
if (returnCallbackId === callbackId) {
|
||||
resolve(config)
|
||||
ipcRenderer.removeListener(PICGO_GET_CONFIG, callback)
|
||||
}
|
||||
}
|
||||
ipcRenderer.on(PICGO_GET_CONFIG, callback)
|
||||
ipcRenderer.send(PICGO_GET_CONFIG, key, callbackId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ export default class extends Vue {
|
||||
mounted () {
|
||||
this.disableDragEvent()
|
||||
}
|
||||
|
||||
disableDragEvent () {
|
||||
window.addEventListener('dragenter', this.disableDrag, false)
|
||||
window.addEventListener('dragover', this.disableDrag)
|
||||
window.addEventListener('drop', this.disableDrag)
|
||||
}
|
||||
|
||||
disableDrag (e: DragEvent) {
|
||||
const dropzone = document.getElementById('upload-area')
|
||||
if (dropzone === null || !dropzone.contains(<Node>e.target)) {
|
||||
@@ -17,6 +19,7 @@ export default class extends Vue {
|
||||
e.dataTransfer!.dropEffect = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('dragenter', this.disableDrag, false)
|
||||
window.removeEventListener('dragover', this.disableDrag)
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
export const SHOW_INPUT_BOX = 'SHOW_INPUT_BOX'
|
||||
export const SHOW_INPUT_BOX_RESPONSE = 'SHOW_INPUT_BOX_RESPONSE'
|
||||
export const TOGGLE_SHORTKEY_MODIFIED_MODE = 'TOGGLE_SHORTKEY_MODIFIED_MODE'
|
||||
export const TALKING_DATA_APPID = '7E6832BCE3F1438696579E541DFEBFDA'
|
||||
export const TALKING_DATA_EVENT = 'TALKING_DATA_EVENT'
|
||||
export const SHOW_PRIVACY_MESSAGE = 'SHOW_PRIVACY_MESSAGE'
|
||||
export const PICGO_SAVE_CONFIG = 'PICGO_SAVE_CONFIG'
|
||||
export const PICGO_GET_CONFIG = 'PICGO_GET_CONFIG'
|
||||
export const PICGO_GET_DB = 'PICGO_GET_DB'
|
||||
export const PICGO_INSERT_DB = 'PICGO_INSERT_DB'
|
||||
export const PICGO_INSERT_MANY_DB = 'PICGO_INSERT_MANY_DB'
|
||||
export const PICGO_UPDATE_BY_ID_DB = 'PICGO_UPDATE_BY_ID_DB'
|
||||
export const PICGO_GET_BY_ID_DB = 'PICGO_GET_BY_ID_DB'
|
||||
export const PICGO_REMOVE_BY_ID_DB = 'PICGO_REMOVE_BY_ID_DB'
|
||||
export const PICGO_OPEN_FILE = 'PICGO_OPEN_FILE'
|
||||
export const OPEN_DEVTOOLS = 'OPEN_DEVTOOLS'
|
||||
export const SHOW_MINI_PAGE_MENU = 'SHOW_MINI_PAGE_MENU'
|
||||
export const SHOW_MAIN_PAGE_MENU = 'SHOW_MAIN_PAGE_MENU'
|
||||
export const SHOW_UPLOAD_PAGE_MENU = 'SHOW_UPLOAD_PAGE_MENU'
|
||||
export const SHOW_PLUGIN_PAGE_MENU = 'SHOW_PLUGIN_PAGE_MENU'
|
||||
export const MINIMIZE_WINDOW = 'MINIMIZE_WINDOW'
|
||||
export const CLOSE_WINDOW = 'CLOSE_WINDOW'
|
||||
export const OPEN_USER_STORE_FILE = 'OPEN_USER_STORE_FILE'
|
||||
export const OPEN_URL = 'OPEN_URL'
|
||||
export const RELOAD_APP = 'RELOAD_APP'
|
||||
export const PICGO_CONFIG_PLUGIN = 'PICGO_CONFIG_PLUGIN'
|
||||
export const PICGO_HANDLE_PLUGIN_ING = 'PICGO_HANDLE_PLUGIN_ING'
|
||||
export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN'
|
||||
export const PASTE_TEXT = 'PASTE_TEXT'
|
||||
export const SET_MINI_WINDOW_POS = 'SET_MINI_WINDOW_POS'
|
||||
|
||||
Vendored
+7
@@ -20,3 +20,10 @@ declare interface IWindowManager {
|
||||
deleteById: (id: number) => void
|
||||
getAvailableWindow: () => BrowserWindow
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/35074713/extending-typescript-global-object-in-node-js/44387594#44387594
|
||||
declare global {
|
||||
var PICGO_GUI_VERSION: string
|
||||
var PICGO_CORE_VERSION: string
|
||||
var notificationList: IAppNotification[]
|
||||
}
|
||||
|
||||
Vendored
+14
-3
@@ -1,14 +1,25 @@
|
||||
import VueRouter, { Route } from 'vue-router'
|
||||
import db from '#/datastore'
|
||||
import axios from 'axios'
|
||||
import { IObject, IResult, IGetResult, IFilter } from '@picgo/store/dist/types'
|
||||
|
||||
interface IGalleryDB {
|
||||
get<T>(filter?: IFilter): Promise<IGetResult<T>>
|
||||
insert<T> (value: T): Promise<IResult<T>>
|
||||
insertMany<T> (value: T[]): Promise<IResult<T>[]>
|
||||
updateById (id: string, value: IObject): Promise<boolean>
|
||||
getById<T> (id: string): Promise<IResult<T> | undefined>
|
||||
removeById (id: string): Promise<void>
|
||||
}
|
||||
|
||||
declare module 'vue/types/vue' {
|
||||
interface Vue {
|
||||
$router: VueRouter,
|
||||
$route: Route,
|
||||
$db: typeof db
|
||||
$http: typeof axios
|
||||
$builtInPicBed: string[]
|
||||
$bus: Vue
|
||||
letPicGoSaveData(data: IObj): void
|
||||
$$db: IGalleryDB
|
||||
saveConfig(data: IObj | string, value?: any): void
|
||||
getConfig<T>(key?: string): Promise<T | undefined>
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+6
@@ -10,4 +10,10 @@ declare global {
|
||||
[elem: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
interface Window {
|
||||
TDAPP: {
|
||||
onEvent: (EventId: string, Label?: string, MapKv?: IStringKeyMap) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+57
-1
@@ -15,7 +15,7 @@ declare interface ErrnoException extends Error {
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
declare var __static: string
|
||||
declare let __static: string
|
||||
|
||||
declare type ILogType = 'success' | 'info' | 'warn' | 'error'
|
||||
|
||||
@@ -45,6 +45,7 @@ interface ImgInfo {
|
||||
extname?: string
|
||||
imgUrl?: string
|
||||
id?: string
|
||||
type?: string
|
||||
[propName: string]: any
|
||||
}
|
||||
|
||||
@@ -93,6 +94,7 @@ interface IBrowserWindowOptions {
|
||||
webPreferences: {
|
||||
nodeIntegration: boolean,
|
||||
nodeIntegrationInWorker: boolean,
|
||||
contextIsolation: boolean,
|
||||
backgroundThrottling: boolean
|
||||
webSecurity?: boolean
|
||||
},
|
||||
@@ -123,6 +125,7 @@ interface IBounds {
|
||||
type ICtx = import('picgo')
|
||||
interface IPicGoPlugin {
|
||||
name: string
|
||||
fullName: string
|
||||
author: string
|
||||
description: string
|
||||
logo: string
|
||||
@@ -143,8 +146,17 @@ interface IPicGoPlugin {
|
||||
hasInstall?: boolean
|
||||
}
|
||||
|
||||
interface IPicGoPluginConfig {
|
||||
name: string
|
||||
type: string
|
||||
required: boolean
|
||||
default?: any
|
||||
[propName: string]: any
|
||||
}
|
||||
|
||||
interface IPluginMenuConfig {
|
||||
name: string
|
||||
fullName?: string
|
||||
config: any[]
|
||||
}
|
||||
|
||||
@@ -194,6 +206,14 @@ type IUploadOption = string[]
|
||||
interface IShowNotificationOption {
|
||||
title: string
|
||||
body: string
|
||||
icon?: string | import('electron').NativeImage
|
||||
}
|
||||
|
||||
interface IPrivateShowNotificationOption extends IShowNotificationOption{
|
||||
/**
|
||||
* click notification to copy the body
|
||||
*/
|
||||
clickToCopy?: boolean
|
||||
}
|
||||
|
||||
interface IShowMessageBoxOption {
|
||||
@@ -279,3 +299,39 @@ interface IUpYunConfig {
|
||||
}
|
||||
|
||||
type ILoggerType = string | Error | boolean | number | undefined
|
||||
|
||||
interface IAppNotification {
|
||||
title: string
|
||||
body: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
interface ITalkingDataOptions {
|
||||
EventId: string
|
||||
Label?: string
|
||||
MapKv?: IStringKeyMap
|
||||
}
|
||||
|
||||
interface IAnalyticsData {
|
||||
fromClipboard: boolean
|
||||
type: string
|
||||
count: number
|
||||
duration?: number // 耗时
|
||||
}
|
||||
|
||||
interface IStringKeyMap {
|
||||
[propName: string]: any
|
||||
}
|
||||
|
||||
type ILogArgvType = string | number
|
||||
|
||||
type ILogArgvTypeWithError = ILogArgvType | Error
|
||||
|
||||
interface IMiniWindowPos {
|
||||
x: number,
|
||||
y: number,
|
||||
height: number,
|
||||
width: number
|
||||
}
|
||||
|
||||
type PromiseResType<T> = T extends Promise<infer R> ? R : T
|
||||
|
||||
@@ -1 +1,32 @@
|
||||
export const isUrl = (url: string): boolean => (url.startsWith('http://') || url.startsWith('https://'))
|
||||
export const isUrlEncode = (url: string): boolean => {
|
||||
url = url || ''
|
||||
try {
|
||||
return url !== decodeURI(url)
|
||||
} catch (e) {
|
||||
// if some error caught, try to let it go
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export const handleUrlEncode = (url: string): string => {
|
||||
if (!isUrlEncode(url)) {
|
||||
url = encodeURI(url)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* streamline the full plugin name to a simple one
|
||||
* for example:
|
||||
* 1. picgo-plugin-xxx -> xxx
|
||||
* 2. @xxx/picgo-plugin-yyy -> yyy
|
||||
* @param name pluginFullName
|
||||
*/
|
||||
export const handleStreamlinePluginName = (name: string) => {
|
||||
if (/^@[^/]+\/picgo-plugin-/.test(name)) {
|
||||
return name.replace(/^@[^/]+\/picgo-plugin-/, '')
|
||||
} else {
|
||||
return name.replace(/picgo-plugin-/, '')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import db from '#/datastore'
|
||||
import { IPasteStyle } from '#/types/enum'
|
||||
|
||||
const formatCustomLink = (customLink: string, item: ImgInfo) => {
|
||||
let fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')
|
||||
let url = item.url || item.imgUrl
|
||||
const formatObj = {
|
||||
url,
|
||||
fileName
|
||||
}
|
||||
const keys = Object.keys(formatObj) as ['url', 'fileName']
|
||||
keys.forEach(item => {
|
||||
if (customLink.indexOf(`$${item}`) !== -1) {
|
||||
let reg = new RegExp(`\\$${item}`, 'g')
|
||||
customLink = customLink.replace(reg, formatObj[item])
|
||||
}
|
||||
})
|
||||
return customLink
|
||||
}
|
||||
|
||||
export default (style: IPasteStyle, item: ImgInfo) => {
|
||||
let url = item.url || item.imgUrl
|
||||
const customLink = db.get('settings.customLink') || '$url'
|
||||
const tpl = {
|
||||
'markdown': ``,
|
||||
'HTML': `<img src="${url}"/>`,
|
||||
'URL': url,
|
||||
'UBB': `[IMG]${url}[/IMG]`,
|
||||
'Custom': formatCustomLink(customLink, item)
|
||||
}
|
||||
return tpl[style]
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"target": "es2019", // https://github.com/TypeStrong/ts-loader/issues/1061
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
+42
-3
@@ -2,16 +2,26 @@ const path = require('path')
|
||||
function resolve (dir) {
|
||||
return path.join(__dirname, dir)
|
||||
}
|
||||
module.exports = {
|
||||
|
||||
const config = {
|
||||
configureWebpack: {
|
||||
devtool: 'nosources-source-map'
|
||||
},
|
||||
chainWebpack: config => {
|
||||
config.resolve.alias
|
||||
.set('@', resolve('src/renderer'))
|
||||
.set('~', resolve('src'))
|
||||
.set('root', resolve('./'))
|
||||
.set('#', resolve('src/universal'))
|
||||
// define
|
||||
// config.plugin('define')
|
||||
// .tap(args => {
|
||||
// return args
|
||||
// })
|
||||
},
|
||||
pluginOptions: {
|
||||
electronBuilder: {
|
||||
nodeIntegration: true, // will remove in the future
|
||||
customFileProtocol: 'picgo://./',
|
||||
externals: ['picgo'],
|
||||
chainWebpackMainProcess: config => {
|
||||
@@ -53,11 +63,28 @@ module.exports = {
|
||||
icon: 'build/icons/icon.icns',
|
||||
extendInfo: {
|
||||
LSUIElement: 1
|
||||
}
|
||||
},
|
||||
target: [{
|
||||
target: 'dmg',
|
||||
arch: [
|
||||
'x64',
|
||||
'arm64'
|
||||
]
|
||||
}],
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
artifactName: 'PicGo-${version}-${arch}.dmg'
|
||||
},
|
||||
win: {
|
||||
icon: 'build/icons/icon.ico',
|
||||
target: 'nsis'
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
artifactName: 'PicGo Setup ${version}-${arch}.exe',
|
||||
target: [{
|
||||
target: 'nsis',
|
||||
arch: [
|
||||
'x64',
|
||||
'ia32'
|
||||
]
|
||||
}]
|
||||
},
|
||||
nsis: {
|
||||
shortcutName: 'PicGo',
|
||||
@@ -74,3 +101,15 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
config.configureWebpack = {
|
||||
devtool: 'source-map'
|
||||
}
|
||||
// for dev main process hot reload
|
||||
config.pluginOptions.electronBuilder.mainProcessWatch = ['src/main/**/*']
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
...config
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user