Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
72387baf55 | ||
|
|
b357dfb243 | ||
|
|
ad6acd8c4f | ||
|
|
2aeca509a8 | ||
|
|
b7cbd50b17 | ||
|
|
b5ceaeda1e | ||
|
|
7e866185e9 | ||
|
|
96cdfea497 | ||
|
|
3df2253ae9 | ||
|
|
cc182b08f7 | ||
|
|
67e526f163 | ||
|
|
cd70a1a5cc | ||
|
|
f3575575ac | ||
|
|
3f3ea69cd6 | ||
|
|
32334e9bc1 | ||
|
|
96544f5475 | ||
|
|
a28c67848d | ||
|
|
23ba2338c6 | ||
|
|
024d9cf41e | ||
|
|
fd7673e4d1 | ||
|
|
fb014ddc9e | ||
|
|
962c9f2f37 | ||
|
|
4ebd76ff11 | ||
|
|
c3fe613696 | ||
|
|
ee46ab1fb7 | ||
|
|
3caeccc492 | ||
|
|
c4f0a30c51 | ||
|
|
762dc9b2cc | ||
|
|
73870a5326 | ||
|
|
5f2b7c7802 | ||
|
|
0d6fdc4727 | ||
|
|
ded7998fbc | ||
|
|
3913cef1e6 | ||
|
|
b4f10c61f0 | ||
|
|
a04a25c7e0 | ||
|
|
e1a0cbb8a1 | ||
|
|
6b57cf7981 | ||
|
|
9a8b95e356 | ||
|
|
81e6acb29b | ||
|
|
c56d4efa79 | ||
|
|
3fd95725cf | ||
|
|
c6ead5b8cd | ||
|
|
f2750e1234 | ||
|
|
9d4d605acf | ||
|
|
cd30a6c70c | ||
|
|
a33f1adeb3 | ||
|
|
1b3522e536 | ||
|
|
3418560c63 | ||
|
|
fec4360043 | ||
|
|
b8ec879e23 | ||
|
|
29a55ed855 | ||
|
|
514fc40f71 | ||
|
|
bc8e641fda | ||
|
|
4e3fa28be6 | ||
|
|
87638a2882 | ||
|
|
1d98601757 | ||
|
|
4e67a75be0 | ||
|
|
72e6e2aed5 | ||
|
|
b63ac03b84 | ||
|
|
554cc0dbf8 | ||
|
|
1274893154 | ||
|
|
f491209bd6 | ||
|
|
f21940ef23 | ||
|
|
fe9e19a84d | ||
|
|
91b397de9a | ||
|
|
d06be462ff | ||
|
|
8fcbef5b0e | ||
|
|
c5adf3b68c | ||
|
|
5a064830c6 | ||
|
|
e38e6f038c | ||
|
|
c7ea9a8628 | ||
|
|
a5b6526fa4 | ||
|
|
46a49ed9d3 | ||
|
|
9344d7d762 | ||
|
|
e81b8f428c | ||
|
|
6f1fca7f7f | ||
|
|
50f31bd438 | ||
|
|
5b0d596c6d | ||
|
|
c59e2bcc97 | ||
|
|
3c6b329927 | ||
|
|
466dbec4b7 | ||
|
|
4e02244ebe | ||
|
|
748a7fd07e | ||
|
|
4000cea7c8 | ||
|
|
8978166328 | ||
|
|
7f1769733b | ||
|
|
e5fbe750ae | ||
|
|
ed539cf206 | ||
|
|
74c7016d2c | ||
|
|
407b821aca | ||
|
|
6db86ec359 | ||
|
|
c64959a139 | ||
|
|
6122e17d75 | ||
|
|
37f1d34abc | ||
|
|
3dd20857aa | ||
|
|
4a11d9830b | ||
|
|
0165879f2d | ||
|
|
17302fe3e4 | ||
|
|
f65d2d2df4 | ||
|
|
af19de7168 | ||
|
|
9ab9e1973c | ||
|
|
a4056cbe58 | ||
|
|
69d660fe7e | ||
|
|
058e903789 | ||
|
|
53cdddf44a | ||
|
|
2315aad732 | ||
|
|
7f8c6fd60e | ||
|
|
10959f3c54 | ||
|
|
1f4f40848e | ||
|
|
7ef76b0a9b | ||
|
|
f87311760a | ||
|
|
d597477a79 | ||
|
|
2bcbf749d1 | ||
|
|
59dd2c0b55 | ||
|
|
43bace3212 | ||
|
|
649e9f0695 | ||
|
|
e8f2185bbb | ||
|
|
cc9dea5e71 | ||
|
|
aabc0028af | ||
|
|
193946c469 | ||
|
|
22c7b9a170 | ||
|
|
c5f6ae8993 | ||
|
|
bb034677bd | ||
|
|
d2eb8999c5 | ||
|
|
4cb7acfec0 | ||
|
|
188b5e5148 | ||
|
|
0abdb3cf00 | ||
|
|
8704e212bf | ||
|
|
4b92c90361 | ||
|
|
2def54ec49 | ||
|
|
1d5142da79 | ||
|
|
8e3c51d0b9 | ||
|
|
0cc3f70cd7 | ||
|
|
a71ba33156 | ||
|
|
960e01f17f | ||
|
|
86265ee5c5 | ||
|
|
a6377696d9 | ||
|
|
927d913f3b | ||
|
|
250a014fc1 | ||
|
|
5e5a5f133a | ||
|
|
78d0baef7d | ||
|
|
3490752e2f | ||
|
|
2b94fe49a2 | ||
|
|
d0fdbf1ea2 | ||
|
|
eddd39309e | ||
|
|
60ccd10e1a | ||
|
|
51cea7cdf0 | ||
|
|
f7dc41a927 | ||
|
|
ae9d8eee13 | ||
|
|
2d63f2ff42 | ||
|
|
778c910436 | ||
|
|
405548426a | ||
|
|
8ff7d15f8d | ||
|
|
5cf96d38b8 | ||
|
|
8ec128121f | ||
|
|
be6cce9501 | ||
|
|
72f2669cce | ||
|
|
cdf7e14548 | ||
|
|
0fe28ce675 | ||
|
|
9d979a2b04 | ||
|
|
9c12fb6ede | ||
|
|
8a8b97a937 | ||
|
|
1af9af74ce | ||
|
|
ca01d2c521 | ||
|
|
094b83b30b | ||
|
|
ea5b937c93 | ||
|
|
c149211def | ||
|
|
13c63ba66d | ||
|
|
dc1ba57a97 | ||
|
|
392af6c617 | ||
|
|
5909d35566 | ||
|
|
804072999e | ||
|
|
17755b9809 | ||
|
|
c3b2d7b941 | ||
|
|
4a420dd5c5 | ||
|
|
51281f345b | ||
|
|
c8b4bebe1d | ||
|
|
aea0c3661d | ||
|
|
cb32d43be4 | ||
|
|
4a0d398b4f | ||
|
|
fb5a10d792 | ||
|
|
65dfb4ac05 | ||
|
|
f309d18aed | ||
|
|
6fca0e73c0 | ||
|
|
0daafa3964 | ||
|
|
a2f1eec8d7 | ||
|
|
fc56d07ab2 | ||
|
|
cf504a0173 | ||
|
|
e0bb8480a2 | ||
|
|
d4c04ee1e6 | ||
|
|
4ab782e8dc | ||
|
|
a9ba87dc8e | ||
|
|
396faa2c60 | ||
|
|
930e4e663b | ||
|
|
ce9c2aed71 | ||
|
|
bba250b0d9 | ||
|
|
b74fe8676b | ||
|
|
8ad28f7fe5 | ||
|
|
1d91730eb9 | ||
|
|
5a25aa0b52 | ||
|
|
671c011feb | ||
|
|
62b616bfc7 | ||
|
|
2a801e6c19 | ||
|
|
313002cf9e | ||
|
|
bcb1859eaa | ||
|
|
8bc0aef482 | ||
|
|
353f553988 | ||
|
|
f48021d928 | ||
|
|
c9ecef60e3 | ||
|
|
0f4901bdbf | ||
|
|
f494e1486c | ||
|
|
1faf29732c | ||
|
|
5207326017 | ||
|
|
4d5b3fbdf6 | ||
|
|
abcd7b848d | ||
|
|
8785218b0d | ||
|
|
61a2a047c7 | ||
|
|
6aed2d2a97 | ||
|
|
46189d2f34 | ||
|
|
4d55ac941f | ||
|
|
62b0c2113c | ||
|
|
ed187f4438 | ||
|
|
372f71bcd9 | ||
|
|
415bdd31f3 | ||
|
|
5d9e0a311d | ||
|
|
1781a8902c | ||
|
|
1f95ef0ee2 | ||
|
|
a63e1717c7 | ||
|
|
1e5fa28789 | ||
|
|
3455512c47 | ||
|
|
947e49ab4a | ||
|
|
a20299edaa | ||
|
|
ffbbc74734 | ||
|
|
1b8400a458 | ||
|
|
9e2834c0f9 | ||
|
|
0b5981a27d | ||
|
|
d78de75c18 | ||
|
|
f8eceae8e4 | ||
|
|
a0c1e95b15 | ||
|
|
94f70cb8c6 | ||
|
|
f33df32312 | ||
|
|
c1604fa3cb | ||
|
|
0e5eee7876 | ||
|
|
00689a9931 | ||
|
|
06539d1513 | ||
|
|
74b94feb74 | ||
|
|
757a7d1260 | ||
|
|
2496c0b467 | ||
|
|
4a685e0474 | ||
|
|
9fdd54ca16 | ||
|
|
eaa348ad56 | ||
|
|
282b0dc25c | ||
|
|
d99e45a021 | ||
|
|
ef8bf5476c | ||
|
|
d042c93bec | ||
|
|
ad71117876 | ||
|
|
fe7f688488 | ||
|
|
65a15724a3 | ||
|
|
422aa52763 | ||
|
|
eecf98e452 | ||
|
|
975927ac68 | ||
|
|
9ef615cab4 | ||
|
|
fb236c3186 | ||
|
|
458087a94e | ||
|
|
4e8dfef6cb | ||
|
|
e1c151e310 | ||
|
|
25068a8949 | ||
|
|
4010a09fe4 | ||
|
|
f6536b1dc2 | ||
|
|
df39511cdf | ||
|
|
504690b729 | ||
|
|
937979ae3d | ||
|
|
1f373fb325 | ||
|
|
92b9fbcee8 | ||
|
|
37a784225e | ||
|
|
16fe016c70 | ||
|
|
406cfffbc9 | ||
|
|
e5d5bad115 | ||
|
|
7364ea6ddf | ||
|
|
f657026820 | ||
|
|
e8e78303a3 | ||
|
|
29fb9c742e | ||
|
|
16f518b2ad | ||
|
|
790367efd6 | ||
|
|
7e9fd9d14e | ||
|
|
9df9b547d1 | ||
|
|
26f6627b44 | ||
|
|
dc9d5d6dfb | ||
|
|
b4c760c8cd | ||
|
|
783adada58 | ||
|
|
c6f2e16b80 | ||
|
|
ca6e72d4fc | ||
|
|
3d21909d72 | ||
|
|
1d2389c9e7 | ||
|
|
a8a0b9b424 | ||
|
|
7ada9fec39 | ||
|
|
2ecb33ccb0 | ||
|
|
d1602edf65 | ||
|
|
bc31cab735 | ||
|
|
63f8cb631a | ||
|
|
6cf3a50023 | ||
|
|
5fb11f5d89 | ||
|
|
f78bfda258 | ||
|
|
21ceafe649 | ||
|
|
fecd35cb06 | ||
|
|
4a9623e49f | ||
|
|
e1ed998153 | ||
|
|
3a97d019d1 | ||
|
|
4b725930d2 | ||
|
|
7204e05601 | ||
|
|
7c4b4f28e1 | ||
|
|
f6752c6f8a | ||
|
|
ad986689e5 | ||
|
|
cfa360bb09 | ||
|
|
d87501212f | ||
|
|
493d79d33d | ||
|
|
0964c1423b | ||
|
|
ec12ba4e0e | ||
|
|
d0fdb79821 | ||
|
|
4dd6586cb4 | ||
|
|
3814125986 | ||
|
|
d1b9e0f592 |
@@ -0,0 +1,4 @@
|
||||
test/unit/coverage/**
|
||||
test/unit/*.js
|
||||
test/e2e/*.js
|
||||
dist/
|
||||
@@ -0,0 +1,25 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
globals: {
|
||||
__static: 'readonly'
|
||||
},
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
parser: "vue-eslint-parser",
|
||||
'extends': [
|
||||
'plugin:vue/essential',
|
||||
'@vue/standard',
|
||||
'@vue/typescript'
|
||||
],
|
||||
'plugins': ['@typescript-eslint'],
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
"indent": "off",
|
||||
"@typescript-eslint/indent": ["error", 2]
|
||||
},
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
custom: ["https://paypal.me/Molunerfinn"]
|
||||
@@ -0,0 +1,34 @@
|
||||
<!--
|
||||
PicGo Issue 模板
|
||||
请依照该模板来提交,否则将会被关闭。
|
||||
**提问之前请注意你看过 FAQ、配置手册以及那些被关闭的 issues。否则同样的提问也会被关闭!**
|
||||
-->
|
||||
|
||||
**声明:我已经仔细看过 [文档](https://picgo.github.io/PicGo-Doc/)、[FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md)、和搜索过已经关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed) 后依然没有找到答案,所以才发了一个新的 issue。**
|
||||
|
||||
## 问题类型
|
||||
|
||||
<!-- 你要提交的是 Bug Report 还是 Feature Request?-->
|
||||
|
||||
## PicGo 的相关信息
|
||||
|
||||
<!-- PicGo 的版本、所在平台(Mac 或者 Windows 或者 Linux) -->
|
||||
|
||||
## 问题重现
|
||||
|
||||
<!-- 如果是 Bug Report 请填写本项 -->
|
||||
<!-- 请附上相关截图 -->
|
||||
<!-- 请附上 PicGo 的相关报错日志(用文本的形式)否则无法判断原因-->
|
||||
<!-- 报错日志可以在 PicGo 设置 -> 设置日志文件 -> 点击打开 后找到 -->
|
||||
|
||||
## 功能请求
|
||||
|
||||
<!-- 如果是 Feature Request 请填写本项 -->
|
||||
<!-- 详细描述你所预想的功能或者是现有功能的改进。 -->
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
最后,喜欢 PicGo 的话不妨给它点个 star~
|
||||
如果可以的话,请我喝杯咖啡?首页有赞助二维码,谢谢你的支持!
|
||||
-->
|
||||
@@ -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-10.15]
|
||||
|
||||
# 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: |
|
||||
npm run release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
@@ -0,0 +1,19 @@
|
||||
.DS_Store
|
||||
dist/electron/*
|
||||
dist/web/*
|
||||
build/*
|
||||
!build/icons
|
||||
!build/installer.nsh
|
||||
coverage
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
npm-debug.log.*
|
||||
thumbs.db
|
||||
!.gitkeep
|
||||
yarn-error.log
|
||||
docs/dist/
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
dist_electron/
|
||||
test.js
|
||||
@@ -0,0 +1,52 @@
|
||||
# Commented sections below can be used to run tests on the CI server
|
||||
# https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing
|
||||
osx_image: xcode8.3
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: c
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
- os: linux
|
||||
env: CC=clang CXX=clang++ npm_config_clang=1
|
||||
compiler: clang
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- "$HOME/.electron"
|
||||
- "$HOME/.cache"
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libgnome-keyring-dev
|
||||
- icnsutils
|
||||
#- xvfb
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install git-lfs; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi
|
||||
install:
|
||||
#- export DISPLAY=':99.0'
|
||||
#- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
|
||||
- nvm install 10
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
- source ~/.bashrc
|
||||
- npm install -g xvfb-maybe
|
||||
- yarn
|
||||
script:
|
||||
#- xvfb-maybe node_modules/.bin/karma start test/unit/karma.conf.js
|
||||
#- yarn run pack && xvfb-maybe node_modules/.bin/mocha test/e2e
|
||||
- npm run release
|
||||
# - yarn run build:docs
|
||||
before_script:
|
||||
- git lfs pull
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
# after_script:
|
||||
# - cd docs/dist
|
||||
# - git init
|
||||
# - git config user.name "Molunerfinn"
|
||||
# - git config user.email "marksz@teamsz.xyz"
|
||||
# - git add .
|
||||
# - git commit -m "Travis build docs"
|
||||
# - git push --force --quiet "https://${GH_TOKEN}@github.com/Molunerfinn/PicGo.git" master:gh-pages
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/src/main/index.dev.js",
|
||||
"env": {
|
||||
"DEBUG_ENV": "debug"
|
||||
},
|
||||
"stopOnEntry": false,
|
||||
"args": [],
|
||||
"cwd": "${workspaceRoot}",
|
||||
// this points to the electron task runner
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
|
||||
"runtimeArgs": [
|
||||
"--nolazy"
|
||||
],
|
||||
"sourceMaps": true
|
||||
},
|
||||
{
|
||||
"name": "Attach",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"port": 5858,
|
||||
"sourceMaps": true,
|
||||
"restart": true,
|
||||
"outFiles": [],
|
||||
"localRoot": "${workspaceRoot}",
|
||||
"protocol": "inspector",
|
||||
"remoteRoot": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"eslint.enable": true,
|
||||
"eslint.alwaysShowStatus": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"vue",
|
||||
"typescriptreact"
|
||||
],
|
||||
"[stylus]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"stylusSupremacy.insertSemicolons": false,
|
||||
"stylusSupremacy.insertBraces": false,
|
||||
"stylusSupremacy.insertNewLineBetweenSelectors": true,
|
||||
"stylusSupremacy.insertParenthesisAroundIfCondition": false,
|
||||
"stylusSupremacy.alwaysUseNoneOverZero": true,
|
||||
"stylusSupremacy.alwaysUseZeroWithoutUnit": true,
|
||||
"stylusSupremacy.sortProperties": "grouped",
|
||||
"stylusSupremacy.quoteChar": "\"",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
# :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)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add autoCopy option for users to use or not ([67e526f](https://github.com/Molunerfinn/PicGo/commit/67e526f))
|
||||
* add beta-version update support ([ad6acd8](https://github.com/Molunerfinn/PicGo/commit/ad6acd8))
|
||||
* add smms-v2 support ([3f3ea69](https://github.com/Molunerfinn/PicGo/commit/3f3ea69))
|
||||
* add uploading image from URL support ([a28c678](https://github.com/Molunerfinn/PicGo/commit/a28c678))
|
||||
* finish all-select && shift multi-select ([2aeca50](https://github.com/Molunerfinn/PicGo/commit/2aeca50)), closes [#342](https://github.com/Molunerfinn/PicGo/issues/342)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* confused port number auto increasing when opening a new PicGo app ([cd70a1a](https://github.com/Molunerfinn/PicGo/commit/cd70a1a))
|
||||
* correct inputbox value && remove listener ([32334e9](https://github.com/Molunerfinn/PicGo/commit/32334e9))
|
||||
* give a hint when node.js is not installed ([7e86618](https://github.com/Molunerfinn/PicGo/commit/7e86618))
|
||||
* right-click menu upload fails with PicGo open ([96cdfea](https://github.com/Molunerfinn/PicGo/commit/96cdfea)), closes [#415](https://github.com/Molunerfinn/PicGo/issues/415)
|
||||
* some win10 upload clipboard image crash ([cc182b0](https://github.com/Molunerfinn/PicGo/commit/cc182b0))
|
||||
* travis-ci bug ([b357dfb](https://github.com/Molunerfinn/PicGo/commit/b357dfb))
|
||||
* url uploader bug ([96544f5](https://github.com/Molunerfinn/PicGo/commit/96544f5))
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* update choco install picgo ([f357557](https://github.com/Molunerfinn/PicGo/commit/f357557))
|
||||
* update docs ([fd7673e](https://github.com/Molunerfinn/PicGo/commit/fd7673e))
|
||||
* update readme ([fb014dd](https://github.com/Molunerfinn/PicGo/commit/fb014dd))
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* update funding url ([024d9cf](https://github.com/Molunerfinn/PicGo/commit/024d9cf))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.2.2 (2020-01-20)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* picgo-server upload clipboard file's result bug ([4ebd76f](https://github.com/Molunerfinn/PicGo/commit/4ebd76f))
|
||||
* releaseUrl may can't get latest version ([ee46ab1](https://github.com/Molunerfinn/PicGo/commit/ee46ab1))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.2.1 (2020-01-09)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add alias for plugin config name ([5a06483](https://github.com/Molunerfinn/PicGo/commit/5a06483))
|
||||
* add aliyun oss options ([a33f1ad](https://github.com/Molunerfinn/PicGo/commit/a33f1ad)), closes [#347](https://github.com/Molunerfinn/PicGo/issues/347)
|
||||
* **server:** add http server for uploading images by a http request ([c56d4ef](https://github.com/Molunerfinn/PicGo/commit/c56d4ef))
|
||||
* add server config settings ([6b57cf7](https://github.com/Molunerfinn/PicGo/commit/6b57cf7))
|
||||
* only shows visible pic-beds ([9d4d605](https://github.com/Molunerfinn/PicGo/commit/9d4d605)), closes [#310](https://github.com/Molunerfinn/PicGo/issues/310)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* decrease title-bar z-index when config-form dialog shows ([f2750e1](https://github.com/Molunerfinn/PicGo/commit/f2750e1))
|
||||
* **website:** website pictures error ([a5b6526](https://github.com/Molunerfinn/PicGo/commit/a5b6526))
|
||||
* add new tray icon for macOS dark-mode ([c5adf3b](https://github.com/Molunerfinn/PicGo/commit/c5adf3b)), closes [#267](https://github.com/Molunerfinn/PicGo/issues/267)
|
||||
* beforeOpen handler in windows ([cd30a6c](https://github.com/Molunerfinn/PicGo/commit/cd30a6c))
|
||||
* busApi event register first && emit later ([e1a0cbb](https://github.com/Molunerfinn/PicGo/commit/e1a0cbb))
|
||||
* enum type error ([4e3fa28](https://github.com/Molunerfinn/PicGo/commit/4e3fa28))
|
||||
* handle empty request-body ([81e6acb](https://github.com/Molunerfinn/PicGo/commit/81e6acb))
|
||||
* launch error in new structrue ([bc8e641](https://github.com/Molunerfinn/PicGo/commit/bc8e641))
|
||||
* miniWindow minimize bug ([5f2b7c7](https://github.com/Molunerfinn/PicGo/commit/5f2b7c7))
|
||||
* plugin config-form && default plugin logo ([514fc40](https://github.com/Molunerfinn/PicGo/commit/514fc40))
|
||||
* release script ([b4f10c6](https://github.com/Molunerfinn/PicGo/commit/b4f10c6))
|
||||
* removeById handler error ([c4f0a30](https://github.com/Molunerfinn/PicGo/commit/c4f0a30)), closes [#382](https://github.com/Molunerfinn/PicGo/issues/382)
|
||||
* rename page not work ([29a55ed](https://github.com/Molunerfinn/PicGo/commit/29a55ed))
|
||||
* save debug mode && PICGO_ENV into config file ([c6ead5b](https://github.com/Molunerfinn/PicGo/commit/c6ead5b))
|
||||
* server may never start ([73870a5](https://github.com/Molunerfinn/PicGo/commit/73870a5))
|
||||
* settingPage && miniPage style in windows ([3fd9572](https://github.com/Molunerfinn/PicGo/commit/3fd9572))
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* add note for windows electron mirror ([46a49ed](https://github.com/Molunerfinn/PicGo/commit/46a49ed))
|
||||
* remove weibo picbed ([e81b8f4](https://github.com/Molunerfinn/PicGo/commit/e81b8f4))
|
||||
* update installation by scoop ([91b397d](https://github.com/Molunerfinn/PicGo/commit/91b397d)), closes [#295](https://github.com/Molunerfinn/PicGo/issues/295)
|
||||
* update readme ([1b3522e](https://github.com/Molunerfinn/PicGo/commit/1b3522e))
|
||||
* update README ([f491209](https://github.com/Molunerfinn/PicGo/commit/f491209))
|
||||
* update site ([fe9e19a](https://github.com/Molunerfinn/PicGo/commit/fe9e19a))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.2.0 (2020-01-01)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add alias for plugin config name ([5a06483](https://github.com/Molunerfinn/PicGo/commit/5a06483))
|
||||
* add aliyun oss options ([a33f1ad](https://github.com/Molunerfinn/PicGo/commit/a33f1ad)), closes [#347](https://github.com/Molunerfinn/PicGo/issues/347)
|
||||
* **server:** add http server for uploading images by a http request ([c56d4ef](https://github.com/Molunerfinn/PicGo/commit/c56d4ef))
|
||||
* add server config settings ([6b57cf7](https://github.com/Molunerfinn/PicGo/commit/6b57cf7))
|
||||
* only shows visible pic-beds ([9d4d605](https://github.com/Molunerfinn/PicGo/commit/9d4d605)), closes [#310](https://github.com/Molunerfinn/PicGo/issues/310)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* beforeOpen handler in windows ([cd30a6c](https://github.com/Molunerfinn/PicGo/commit/cd30a6c))
|
||||
* **website:** website pictures error ([a5b6526](https://github.com/Molunerfinn/PicGo/commit/a5b6526))
|
||||
* add new tray icon for macOS dark-mode ([c5adf3b](https://github.com/Molunerfinn/PicGo/commit/c5adf3b)), closes [#267](https://github.com/Molunerfinn/PicGo/issues/267)
|
||||
* busApi event register first && emit later ([e1a0cbb](https://github.com/Molunerfinn/PicGo/commit/e1a0cbb))
|
||||
* decrease title-bar z-index when config-form dialog shows ([f2750e1](https://github.com/Molunerfinn/PicGo/commit/f2750e1))
|
||||
* enum type error ([4e3fa28](https://github.com/Molunerfinn/PicGo/commit/4e3fa28))
|
||||
* handle empty request-body ([81e6acb](https://github.com/Molunerfinn/PicGo/commit/81e6acb))
|
||||
* launch error in new structrue ([bc8e641](https://github.com/Molunerfinn/PicGo/commit/bc8e641))
|
||||
* plugin config-form && default plugin logo ([514fc40](https://github.com/Molunerfinn/PicGo/commit/514fc40))
|
||||
* release script ([b4f10c6](https://github.com/Molunerfinn/PicGo/commit/b4f10c6))
|
||||
* rename page not work ([29a55ed](https://github.com/Molunerfinn/PicGo/commit/29a55ed))
|
||||
* save debug mode && PICGO_ENV into config file ([c6ead5b](https://github.com/Molunerfinn/PicGo/commit/c6ead5b))
|
||||
* settingPage && miniPage style in windows ([3fd9572](https://github.com/Molunerfinn/PicGo/commit/3fd9572))
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* add note for windows electron mirror ([46a49ed](https://github.com/Molunerfinn/PicGo/commit/46a49ed))
|
||||
* remove weibo picbed ([e81b8f4](https://github.com/Molunerfinn/PicGo/commit/e81b8f4))
|
||||
* update installation by scoop ([91b397d](https://github.com/Molunerfinn/PicGo/commit/91b397d)), closes [#295](https://github.com/Molunerfinn/PicGo/issues/295)
|
||||
* update readme ([1b3522e](https://github.com/Molunerfinn/PicGo/commit/1b3522e))
|
||||
* update README ([f491209](https://github.com/Molunerfinn/PicGo/commit/f491209))
|
||||
* update site ([fe9e19a](https://github.com/Molunerfinn/PicGo/commit/fe9e19a))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.1.2 (2019-04-19)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add file-name for customurl ([c59e2bc](https://github.com/Molunerfinn/PicGo/commit/c59e2bc)), closes [#173](https://github.com/Molunerfinn/PicGo/issues/173)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* log-level filter bug ([4e02244](https://github.com/Molunerfinn/PicGo/commit/4e02244)), closes [#237](https://github.com/Molunerfinn/PicGo/issues/237)
|
||||
* log-level's reset value from 'all' -> ['all'] ([3c6b329](https://github.com/Molunerfinn/PicGo/commit/3c6b329)), closes [#240](https://github.com/Molunerfinn/PicGo/issues/240) [#237](https://github.com/Molunerfinn/PicGo/issues/237)
|
||||
* mini window hidden bug in linux ([466dbec](https://github.com/Molunerfinn/PicGo/commit/466dbec)), closes [#239](https://github.com/Molunerfinn/PicGo/issues/239)
|
||||
|
||||
|
||||
|
||||
## :tada: 2.1.1 (2019-04-16)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* upload-area can't upload images ([4000cea](https://github.com/Molunerfinn/PicGo/commit/4000cea))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.1.0 (2019-04-15)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add commandline argvs support for picgo app ([6db86ec](https://github.com/Molunerfinn/PicGo/commit/6db86ec))
|
||||
* add gui-api for remove event ([407b821](https://github.com/Molunerfinn/PicGo/commit/407b821)), closes [#201](https://github.com/Molunerfinn/PicGo/issues/201)
|
||||
* add windows context menu ([e5fbe75](https://github.com/Molunerfinn/PicGo/commit/e5fbe75))
|
||||
* add workflow for mac ([7f17697](https://github.com/Molunerfinn/PicGo/commit/7f17697))
|
||||
* support commandline -> upload images in clipboard ([74c7016](https://github.com/Molunerfinn/PicGo/commit/74c7016))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* qiniu area option from select group -> input ([c64959a](https://github.com/Molunerfinn/PicGo/commit/c64959a)), closes [#230](https://github.com/Molunerfinn/PicGo/issues/230)
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* add brew cask source ([6122e17](https://github.com/Molunerfinn/PicGo/commit/6122e17))
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* add picgo bump-version ([37f1d34](https://github.com/Molunerfinn/PicGo/commit/37f1d34))
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
## 常见问题
|
||||
|
||||
> 在使用 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://`前缀
|
||||
|
||||
通常是你的七牛图床配置里的`设定访问网址`没有加上`http://`或者`https//`头。
|
||||
|
||||
参考:[issue#79](https://github.com/Molunerfinn/PicGo/issues/79)
|
||||
|
||||
## 2. 能否支持图床远端同步删除
|
||||
|
||||
不能。有些图床(比如微博图床、SM.MS、Imgur 等)不支持后台管理,为了架构统一不支持远端删除。
|
||||
|
||||
## 3. 能否支持上传视频文件
|
||||
|
||||
目前不能。如果有人开发了相应的插件理论可以支持任意文件上传。
|
||||
|
||||
## 4. 微博图床上传之后无法显示预览图
|
||||
|
||||
通常是挂了全局代理导致的。
|
||||
|
||||
参考:[issue36](https://github.com/Molunerfinn/PicGo/issues/36)
|
||||
|
||||
## 5. 能否支持某某某图床
|
||||
|
||||
截止 v1.6,PicGo 支持了如下图床:
|
||||
|
||||
- `微博图床` v1.0
|
||||
- `七牛图床` v1.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
|
||||
- `Imgur` v1.6.0
|
||||
|
||||
所以本体内将不会再支持其他图床。需要其他图床支持可以参考目前已有的三方 [插件](https://github.com/PicGo/Awesome-PicGo),如果还是没有你所需要的图床欢迎开发一个插件供大家使用。
|
||||
|
||||
## 6. 一个图床设置多个信息
|
||||
|
||||
不能。因为目前的架构只支持一个图床一份信息。
|
||||
|
||||
## 7. GitHub 图床有时能上传,有时上传失败
|
||||
|
||||
1. GitHub 图床不支持上传同名文件,如果有同名文件上传,会报错。建议开启 `时间戳重命名` 避免同名文件。
|
||||
2. GitHub 服务器和国内 GFW 的问题会导致有时上传成功,有时上传失败,无解。想要稳定请使用付费云存储,如阿里云、腾讯云等,价格也不会贵。
|
||||
|
||||
## 8. Mac 上无法打开 PicGo 的主窗口界面
|
||||
|
||||
PicGo 在 Mac 上是一个顶部栏应用,在 dock 栏是不会有图标的。要打开主窗口,请右键或者双指点按顶部栏 PicGo 图标,选择「打开详细窗口」即可打开主窗口。
|
||||
|
||||
## 9. 上传失败,或者是服务器出错
|
||||
|
||||
1. PicGo 自带的图床都经过测试,上传出错一般都不是 PicGo 自身的原因。如果你用的是 GitHub 图床请参考上面的第 7 点。
|
||||
2. 检查 PicGo 的日志(报错日志可以在 PicGo 设置 -> 设置日志文件 -> 点击打开 后找到),看看 `[PicGo Error]` 的报错信息里有什么关键信息
|
||||
1. 先自行搜索 error 里的报错信息,往往你能百度或者谷歌出问题原因,不必开 issue。
|
||||
2. 如果有带有 `401` 、`403` 等 `40X` 状态码字样的,不用怀疑,就是你配置写错了,仔细检查配置,看看是否多了空格之类的。
|
||||
3. 如果带有 `HttpError`、`RequestError` 、 `socket hang up` 等字样的说明这是网络问题,我无法帮你解决网络问题,请检查你自己的网络,是否有代理,DNS 设置是否正常等。
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-present, Molunerfinn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,147 @@
|
||||
<div align="center">
|
||||
<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/Molunerfinn/PicGo/actions">
|
||||
<img src="https://img.shields.io/badge/code%20style-standard-green.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/actions">
|
||||
<img src="https://github.com/Molunerfinn/PicGo/workflows/Build/badge.svg" alt="">
|
||||
</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="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/releases/latest">
|
||||
<img src="https://img.shields.io/github/release/Molunerfinn/PicGo.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/PicGo/bump-version">
|
||||
<img src="https://img.shields.io/badge/picgo-convention-blue.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://gitter.im/picgo-all/PicGo?utm_source=share-link&utm_medium=link&utm_campaign=share-link">
|
||||
<img src="https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-66ae93.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## 应用概述
|
||||
|
||||
**PicGo: 一个用于快速上传图片并获取图片 URL 链接的工具**
|
||||
|
||||
PicGo 本体支持如下图床:
|
||||
|
||||
- `七牛图床` v1.0
|
||||
- `腾讯云 COS v4\v5 版本` v1.1 & v1.5.0
|
||||
- `又拍云` v1.2.0
|
||||
- `GitHub` v1.5.0
|
||||
- `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+)
|
||||
- 上传图片后自动复制链接到剪贴板
|
||||
- 支持自定义复制到剪贴板的链接格式
|
||||
- 支持修改快捷键,默认快速上传快捷键:`command+shift+p`(macOS)| `control+shift+p`(Windows\Linux)
|
||||
- 支持插件系统,已有插件支持 Gitee、青云等第三方图床
|
||||
- 更多第三方插件以及使用了 PicGo 底层的应用可以在 [Awesome-PicGo](https://github.com/PicGo/Awesome-PicGo) 找到。欢迎贡献!
|
||||
- 支持通过发送 HTTP 请求调用 PicGo 上传(v2.2.0+)
|
||||
- 更多功能等你自己去发现,同时也会不断开发新功能
|
||||
- 开发进度可以查看 [Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度
|
||||
- 欢迎加入 [官方讨论区](https://github.com/Molunerfinn/PicGo/discussions) 与我交流
|
||||
|
||||
**如果第一次使用,请参考应用 [使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html)。遇到问题了还可以看看 [FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md) 以及被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed)。**
|
||||
|
||||
## 下载安装
|
||||
|
||||
点击此处下载 [应用](https://github.com/Molunerfinn/PicGo/releases)。
|
||||
|
||||
### Windows
|
||||
|
||||
Windows 用户请下载最新版本的 `exe` 文件。
|
||||
|
||||
**还可以使用 [Scoop](https://scoop.sh/) 来安装 PicGo: `scoop bucket add helbing https://github.com/helbing/scoop-bucket` & `scoop install picgo`。 感谢 @helbing 的贡献!**
|
||||
|
||||
**还可以使用 [Chocolatey](https://chocolatey.org/) 来安装 PicGo: `choco install picgo`。 感谢 @iYato 的贡献!**
|
||||
|
||||
### macOS
|
||||
|
||||
macOS 用户请下载最新版本的 `dmg` 文件。
|
||||
|
||||
**还可以使用 [Homebrew](https://brew.sh/) 来安装 PicGo: `brew install picgo --cask`。感谢 @womeimingzi11 的贡献!**
|
||||
|
||||
### Linux
|
||||
|
||||
Linux 用户请下载 `AppImage` 文件。
|
||||
|
||||
**如果你是 Arch 类的 Linux 用户,可以直接通过 `yay -S picgo-appimage` 来安装 PicGo (若是没有则先安装 yay ,是 aur 仓库助手)。感谢 @houbaron 的贡献!**
|
||||
|
||||
## 应用截图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 开发说明
|
||||
|
||||
> 目前仅针对 Mac、Windows。Linux 平台并未测试。
|
||||
|
||||
如果你想要学习、开发、修改或自行构建 PicGo,可以依照下面的指示:
|
||||
|
||||
> 如果想学习 Electron-vue 的开发,可以查看我写的系列教程——[Electron-vue 开发实战](https://molunerfinn.com/tags/Electron-vue/)
|
||||
|
||||
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 环境。
|
||||
|
||||
### 开发模式
|
||||
|
||||
输入 `npm run electron:serve` 进入开发模式,开发模式具有热重载特性。不过需要注意的是,开发模式不稳定,会有进程崩溃的情况。此时需要:
|
||||
|
||||
```bash
|
||||
ctrl+c # 退出开发模式
|
||||
npm run electron:serve # 重新进入开发模式
|
||||
```
|
||||
|
||||
**注:Windows 开发模式运行之后会在底部任务栏的右下角应用区出现 PicGo 的应用图标。**
|
||||
|
||||
### 生产模式
|
||||
|
||||
如果你需要自行构建,可以 `npm run electron:build` 开始进行构建。构建成功后,会在 `dist_electron` 目录里出现构建成功的相应安装文件。
|
||||
|
||||
**注意**:如果你的网络环境不太好,可能会出现 `electron-builder` 下载 `electron` 二进制文件失败的情况。这个时候需要在 `npm run electron:build` 之前指定一下 `electron` 的源为国内源:
|
||||
|
||||
```bash
|
||||
export ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/"
|
||||
# 在 Windows 上,则可以使用 set ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/ (无需引号)
|
||||
npm run electron:build
|
||||
```
|
||||
|
||||
只需第一次构建的时候指定一下国内源即可。后续构建不需要特地指定。二进制文件下载在 `~/.electron/` 目录下。如果想要更新 `electron` 构建版本,可以删除 `~/.electron/` 目录,然后重新运行上一步,让 `electron-builder `去下载最新的 `electron` 二进制文件。
|
||||
|
||||
## 其他相关
|
||||
|
||||
- [vs-picgo](https://github.com/PicGo/vs-picgo):PicGo 的 VS Code 版。
|
||||
- [flutter-picgo](https://github.com/PicGo/flutter-picgo):PicGo 的手机版(支持 Android 和 iOS )。
|
||||
|
||||
## 赞助
|
||||
|
||||
如果你喜欢 PicGo 并且它对你确实有帮助,欢迎给我打赏一杯咖啡哈~
|
||||
|
||||
支付宝:
|
||||
|
||||

|
||||
|
||||
微信:
|
||||
|
||||

|
||||
|
||||
## License
|
||||
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
||||
|
||||
Copyright (c) 2017 - 2019 Molunerfinn
|
||||
@@ -0,0 +1,32 @@
|
||||
# Commented sections below can be used to run tests on the CI server
|
||||
# https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing
|
||||
version: 0.1.{build}
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
image: Visual Studio 2017
|
||||
platform:
|
||||
- x64
|
||||
|
||||
cache:
|
||||
- '%APPDATA%\npm-cache'
|
||||
- '%USERPROFILE%\.electron'
|
||||
- '%USERPROFILE%\AppData\Local\Yarn\cache'
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 12 x64
|
||||
- git reset --hard HEAD
|
||||
- yarn
|
||||
- node --version
|
||||
|
||||
build_script:
|
||||
#- yarn test
|
||||
- yarn build --win --ia32
|
||||
- yarn release
|
||||
|
||||
test: off
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,13 @@
|
||||
!macro customInstall
|
||||
SetRegView 64
|
||||
WriteRegStr HKCR "*\shell\PicGo" "" "Upload pictures w&ith PicGo"
|
||||
WriteRegStr HKCR "*\shell\PicGo" "Icon" "$INSTDIR\PicGo.exe"
|
||||
WriteRegStr HKCR "*\shell\PicGo\command" "" '"$INSTDIR\PicGo.exe" "upload" "%1"'
|
||||
SetRegView 32
|
||||
WriteRegStr HKCR "*\shell\PicGo" "" "Upload pictures w&ith PicGo"
|
||||
WriteRegStr HKCR "*\shell\PicGo" "Icon" "$INSTDIR\PicGo.exe"
|
||||
WriteRegStr HKCR "*\shell\PicGo\command" "" '"$INSTDIR\PicGo.exe" "upload" "%1"'
|
||||
!macroend
|
||||
!macro customUninstall
|
||||
DeleteRegKey HKCR "*\shell\PicGo"
|
||||
!macroend
|
||||
@@ -0,0 +1,216 @@
|
||||
<template lang='pug'>
|
||||
#app(v-cloak)
|
||||
#header
|
||||
.mask
|
||||
img.logo(src="~icons/256x256.png", alt="PicGo")
|
||||
h1.title PicGo
|
||||
small(v-if="version") {{ version }}
|
||||
h2.desc 图片上传+管理新体验
|
||||
button.download(@click="goLink('https://github.com/Molunerfinn/picgo/releases')") 免费下载
|
||||
button.download(@click="goLink('https://picgo.github.io/PicGo-Doc/zh/guide/')") 查看文档
|
||||
h3.desc
|
||||
| 基于#[a(href="https://github.com/SimulatedGREG/electron-vue" target="_blank") electron-vue]开发
|
||||
h3.desc
|
||||
| 支持macOS,Windows,Linux
|
||||
h3.desc
|
||||
| 支持#[a(href="https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E6%8F%92%E4%BB%B6%E8%AE%BE%E7%BD%AE%EF%BC%88v2-0%EF%BC%89" target="_blank") 插件系统],让PicGo更强大
|
||||
#container.container-fluid
|
||||
.row.ex-width
|
||||
img.gallery.col-xs-10.col-xs-offset-1.col-md-offset-2.col-md-8(src="https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/first.png")
|
||||
.row.ex-width.display-list
|
||||
.display-list__item(v-for="(item, index) in itemList" :key="index" :class="{ 'o-item': index % 2 !== 0 }")
|
||||
.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__desc {{ item.desc }}
|
||||
.row.ex-width.info
|
||||
.col-xs-10.col-xs-offset-1
|
||||
| ©2017 - {{ year }} #[a(href="https://github.com/Molunerfinn" target="_blank") Molunerfinn]
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: '',
|
||||
data () {
|
||||
return {
|
||||
version: '',
|
||||
year: new Date().getFullYear(),
|
||||
itemList: [
|
||||
{
|
||||
url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/second.png',
|
||||
title: '精致设计',
|
||||
desc: 'macOS系统下,支持拖拽至menubar图标实现上传。menubar app 窗口显示最新上传的5张图片以及剪贴板里的图片。点击图片自动将上传的链接复制到剪贴板。(Windows平台不支持)'
|
||||
},
|
||||
{
|
||||
url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/third.png',
|
||||
title: 'Mini小窗',
|
||||
desc: 'Windows以及Linux系统下提供一个mini悬浮窗用于用户拖拽上传,节约你宝贵的桌面空间。'
|
||||
},
|
||||
{
|
||||
url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/forth.png',
|
||||
title: '便捷管理',
|
||||
desc: '查看你的上传记录,重复使用更方便。支持点击图片大图查看。支持删除图片(仅本地记录),让界面更加干净。'
|
||||
},
|
||||
{
|
||||
url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/fifth.png',
|
||||
title: '可选图床',
|
||||
desc: '默认支持微博图床、七牛图床、腾讯云COS、又拍云、GitHub、SM.MS、阿里云OSS、Imgur。方便不同图床的上传需求。2.0版本开始更可以自己开发插件实现其他图床的上传需求。'
|
||||
},
|
||||
{
|
||||
url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/sixth.png',
|
||||
title: '多样链接',
|
||||
desc: '支持5种默认剪贴板链接格式,包括一种自定义格式,让你的文本编辑游刃有余。'
|
||||
},
|
||||
{
|
||||
url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/seventh.png',
|
||||
title: '插件系统',
|
||||
desc: '2.0版本开始支持插件系统,让PicGo发挥无限潜能,成为一个极致的效率工具。'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getVersion()
|
||||
},
|
||||
methods: {
|
||||
goLink (link) {
|
||||
window.open(link, '_blank')
|
||||
},
|
||||
async getVersion () {
|
||||
const release = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
|
||||
const res = await this.$http.get(release)
|
||||
this.version = res.data.name
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
[v-cloak]
|
||||
display none
|
||||
*
|
||||
box-sizing border-box
|
||||
body,
|
||||
html,
|
||||
h1
|
||||
margin 0
|
||||
padding 0
|
||||
font-family "Source Sans Pro","Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif
|
||||
#app
|
||||
position relative
|
||||
.mask
|
||||
position absolute
|
||||
width 100%
|
||||
height 100vh
|
||||
top 0
|
||||
left 0
|
||||
background rgba(0,0,0, 0.7)
|
||||
z-index -1
|
||||
#header
|
||||
height 100vh
|
||||
width 100%
|
||||
background-image url("https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/bg.jpeg")
|
||||
background-attachment fixed
|
||||
background-size cover
|
||||
background-position center
|
||||
text-align center
|
||||
padding 15vh
|
||||
position relative
|
||||
z-index 2
|
||||
.logo
|
||||
width 120px
|
||||
.title
|
||||
color #4BA2E2
|
||||
font-size 36px
|
||||
font-weight 300
|
||||
margin 10px auto
|
||||
text-align center
|
||||
small
|
||||
margin-left 10px
|
||||
font-size 14px
|
||||
.desc
|
||||
font-weight 400
|
||||
margin 20px auto 10px
|
||||
color #ddd
|
||||
a
|
||||
text-decoration none
|
||||
color #4BA2E2
|
||||
.download
|
||||
display inline-block
|
||||
line-height 1
|
||||
white-space nowrap
|
||||
cursor pointer
|
||||
background transparent
|
||||
border 1px solid #d8dce5
|
||||
color #ddd
|
||||
-webkit-appearance none
|
||||
text-align center
|
||||
box-sizing border-box
|
||||
outline none
|
||||
margin 20px 12px
|
||||
transition .1s
|
||||
font-weight 500
|
||||
user-select none
|
||||
padding 12px 20px
|
||||
font-size 14px
|
||||
border-radius 20px
|
||||
padding 12px 23px
|
||||
transition .2s all ease-in-out
|
||||
&:hover
|
||||
background #ddd
|
||||
color rgba(0,0,0, 0.7)
|
||||
#container
|
||||
position relative
|
||||
text-align center
|
||||
margin-top -10vh
|
||||
z-index 3
|
||||
.gallery
|
||||
margin-bottom 60px
|
||||
cursor pointer
|
||||
transition all .2s ease-in-out
|
||||
&:hover
|
||||
transform scale(1.05)
|
||||
.display-list
|
||||
&__item
|
||||
padding 48px
|
||||
text-align left
|
||||
background #2E2E2E
|
||||
overflow hidden
|
||||
&.o-item
|
||||
background #fff
|
||||
.display-list__desc
|
||||
color #2E2E2E
|
||||
img
|
||||
width 100%
|
||||
cursor pointer
|
||||
transition all .2s ease-in-out
|
||||
&:hover
|
||||
transform scale(1.05)
|
||||
&__content
|
||||
padding-top 120px
|
||||
&__title
|
||||
color #4BA2E2
|
||||
font-size 50px
|
||||
&__desc
|
||||
color #fff
|
||||
margin-top 20px
|
||||
.info
|
||||
padding 48px 0
|
||||
background #2E2E2E
|
||||
color #fff
|
||||
a
|
||||
text-decoration none
|
||||
color #fff
|
||||
@media (max-width: 768px)
|
||||
#header
|
||||
padding 10vh
|
||||
#container
|
||||
.display-list
|
||||
&__item
|
||||
padding 24px 12px
|
||||
&__content
|
||||
padding-top 30px
|
||||
&__title
|
||||
font-size 25px
|
||||
&__desc
|
||||
margin-top 12px
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
import Vue from 'vue'
|
||||
import App from './APP.vue'
|
||||
import 'melody.css'
|
||||
import axios from 'axios'
|
||||
|
||||
Vue.prototype.$http = axios
|
||||
|
||||
new Vue({
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>PicGo</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html> <html lang=en> <head> <meta charset=UTF-8> <meta name=viewport content="width=device-width,initial-scale=1"> <meta http-equiv=X-UA-Compatible content="ie=edge"> <title>PicGo</title> </head> <body> <div id=app></div> <script type="text/javascript" src="index.js"></script></body> </html>
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"name": "picgo",
|
||||
"version": "2.3.0-beta.8",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service electron:serve",
|
||||
"build": "vue-cli-service electron:build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"electron:build": "vue-cli-service electron:build",
|
||||
"electron:serve": "vue-cli-service electron:serve",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postuninstall": "electron-builder install-app-deps",
|
||||
"cz": "git-cz",
|
||||
"bump": "bump-version",
|
||||
"release": "vue-cli-service electron:build --publish always"
|
||||
},
|
||||
"main": "background.js",
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "./node_modules/cz-customizable"
|
||||
},
|
||||
"cz-customizable": {
|
||||
"config": "./node_modules/@picgo/bump-version/.cz-config.js"
|
||||
}
|
||||
},
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
"./node_modules/@picgo/bump-version/commitlint-picgo"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@picgo/store": "^1.0.3",
|
||||
"axios": "^0.19.0",
|
||||
"core-js": "^3.3.2",
|
||||
"element-ui": "^2.13.0",
|
||||
"fix-path": "^2.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"keycode": "^2.2.0",
|
||||
"lodash-id": "^0.14.0",
|
||||
"lowdb": "^1.0.0",
|
||||
"picgo": "^1.4.24",
|
||||
"qrcode.vue": "^1.7.0",
|
||||
"uuidv4": "^6.2.11",
|
||||
"vue": "^2.6.10",
|
||||
"vue-gallery": "^2.0.1",
|
||||
"vue-lazyload": "^1.2.6",
|
||||
"vue-router": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^8.2.0",
|
||||
"@picgo/bump-version": "^1.0.3",
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/lowdb": "^1.0.9",
|
||||
"@types/node": "10.17.6",
|
||||
"@types/request-promise-native": "^1.0.17",
|
||||
"@vue/cli-plugin-babel": "^4.0.0",
|
||||
"@vue/cli-plugin-eslint": "^4.0.0",
|
||||
"@vue/cli-plugin-router": "^4.0.0",
|
||||
"@vue/cli-plugin-typescript": "^4.0.0",
|
||||
"@vue/cli-service": "^4.0.0",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"@vue/eslint-config-typescript": "^4.0.0",
|
||||
"commitizen": "^4.0.3",
|
||||
"conventional-changelog": "^3.1.18",
|
||||
"cz-customizable": "^6.2.0",
|
||||
"electron": "^6.0.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-vue": "^5.0.0",
|
||||
"husky": "^3.1.0",
|
||||
"stylus": "^0.54.7",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"typescript": "~3.7.3",
|
||||
"vue-cli-plugin-electron-builder": "^1.4.2",
|
||||
"vue-property-decorator": "^8.3.0",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "12.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSServices</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSBackgroundColorName</key>
|
||||
<string>background</string>
|
||||
<key>NSBackgroundSystemColorName</key>
|
||||
<string>systemBlueColor</string>
|
||||
<key>NSIconName</key>
|
||||
<string>NSTouchBarShare</string>
|
||||
<key>NSMenuItem</key>
|
||||
<dict>
|
||||
<key>default</key>
|
||||
<string>Upload pictures with PicGo</string>
|
||||
</dict>
|
||||
<key>NSMessage</key>
|
||||
<string>runWorkflowAsService</string>
|
||||
<key>NSRequiredContext</key>
|
||||
<dict>
|
||||
<key>NSApplicationIdentifier</key>
|
||||
<string>com.apple.finder</string>
|
||||
</dict>
|
||||
<key>NSSendFileTypes</key>
|
||||
<array>
|
||||
<string>public.image</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,314 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AMApplicationBuild</key>
|
||||
<string>444.42</string>
|
||||
<key>AMApplicationVersion</key>
|
||||
<string>2.9</string>
|
||||
<key>AMDocumentVersion</key>
|
||||
<string>2</string>
|
||||
<key>actions</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>action</key>
|
||||
<dict>
|
||||
<key>AMAccepts</key>
|
||||
<dict>
|
||||
<key>Container</key>
|
||||
<string>List</string>
|
||||
<key>Optional</key>
|
||||
<true/>
|
||||
<key>Types</key>
|
||||
<array>
|
||||
<string>com.apple.cocoa.string</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>AMActionVersion</key>
|
||||
<string>2.0.3</string>
|
||||
<key>AMApplication</key>
|
||||
<array>
|
||||
<string>自动操作</string>
|
||||
</array>
|
||||
<key>AMParameterProperties</key>
|
||||
<dict>
|
||||
<key>COMMAND_STRING</key>
|
||||
<dict/>
|
||||
<key>CheckedForUserDefaultShell</key>
|
||||
<dict/>
|
||||
<key>inputMethod</key>
|
||||
<dict/>
|
||||
<key>shell</key>
|
||||
<dict/>
|
||||
<key>source</key>
|
||||
<dict/>
|
||||
</dict>
|
||||
<key>AMProvides</key>
|
||||
<dict>
|
||||
<key>Container</key>
|
||||
<string>List</string>
|
||||
<key>Types</key>
|
||||
<array>
|
||||
<string>com.apple.cocoa.string</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>ActionBundlePath</key>
|
||||
<string>/System/Library/Automator/Run Shell Script.action</string>
|
||||
<key>ActionName</key>
|
||||
<string>运行 Shell 脚本</string>
|
||||
<key>ActionParameters</key>
|
||||
<dict>
|
||||
<key>COMMAND_STRING</key>
|
||||
<string>/Applications/PicGo.app/Contents/MacOS/PicGo upload "$@" > /dev/null 2>&1 &</string>
|
||||
<key>CheckedForUserDefaultShell</key>
|
||||
<true/>
|
||||
<key>inputMethod</key>
|
||||
<integer>1</integer>
|
||||
<key>shell</key>
|
||||
<string>/bin/bash</string>
|
||||
<key>source</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
<key>BundleIdentifier</key>
|
||||
<string>com.apple.RunShellScript</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.0.3</string>
|
||||
<key>CanShowSelectedItemsWhenRun</key>
|
||||
<false/>
|
||||
<key>CanShowWhenRun</key>
|
||||
<true/>
|
||||
<key>Category</key>
|
||||
<array>
|
||||
<string>AMCategoryUtilities</string>
|
||||
</array>
|
||||
<key>Class Name</key>
|
||||
<string>RunShellScriptAction</string>
|
||||
<key>InputUUID</key>
|
||||
<string>79609224-28DD-4ADE-AA8F-5A6C68C18C18</string>
|
||||
<key>Keywords</key>
|
||||
<array>
|
||||
<string>Shell</string>
|
||||
<string>脚本</string>
|
||||
<string>命令</string>
|
||||
<string>运行</string>
|
||||
<string>Unix</string>
|
||||
</array>
|
||||
<key>OutputUUID</key>
|
||||
<string>35CD6B4C-A616-4F89-8D76-DCD3249C5B4E</string>
|
||||
<key>UUID</key>
|
||||
<string>4350A83B-E7E6-4D2B-9768-B1D676CF58F3</string>
|
||||
<key>UnlocalizedApplications</key>
|
||||
<array>
|
||||
<string>Automator</string>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>default value</key>
|
||||
<integer>0</integer>
|
||||
<key>name</key>
|
||||
<string>inputMethod</string>
|
||||
<key>required</key>
|
||||
<string>0</string>
|
||||
<key>type</key>
|
||||
<string>0</string>
|
||||
<key>uuid</key>
|
||||
<string>0</string>
|
||||
</dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>default value</key>
|
||||
<string></string>
|
||||
<key>name</key>
|
||||
<string>source</string>
|
||||
<key>required</key>
|
||||
<string>0</string>
|
||||
<key>type</key>
|
||||
<string>0</string>
|
||||
<key>uuid</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>default value</key>
|
||||
<false/>
|
||||
<key>name</key>
|
||||
<string>CheckedForUserDefaultShell</string>
|
||||
<key>required</key>
|
||||
<string>0</string>
|
||||
<key>type</key>
|
||||
<string>0</string>
|
||||
<key>uuid</key>
|
||||
<string>2</string>
|
||||
</dict>
|
||||
<key>3</key>
|
||||
<dict>
|
||||
<key>default value</key>
|
||||
<string></string>
|
||||
<key>name</key>
|
||||
<string>COMMAND_STRING</string>
|
||||
<key>required</key>
|
||||
<string>0</string>
|
||||
<key>type</key>
|
||||
<string>0</string>
|
||||
<key>uuid</key>
|
||||
<string>3</string>
|
||||
</dict>
|
||||
<key>4</key>
|
||||
<dict>
|
||||
<key>default value</key>
|
||||
<string>/bin/sh</string>
|
||||
<key>name</key>
|
||||
<string>shell</string>
|
||||
<key>required</key>
|
||||
<string>0</string>
|
||||
<key>type</key>
|
||||
<string>0</string>
|
||||
<key>uuid</key>
|
||||
<string>4</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>isViewVisible</key>
|
||||
<true/>
|
||||
<key>location</key>
|
||||
<string>449.000000:305.000000</string>
|
||||
<key>nibPath</key>
|
||||
<string>/System/Library/Automator/Run Shell Script.action/Contents/Resources/Base.lproj/main.nib</string>
|
||||
</dict>
|
||||
<key>isViewVisible</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</array>
|
||||
<key>connectors</key>
|
||||
<dict/>
|
||||
<key>workflowMetaData</key>
|
||||
<dict>
|
||||
<key>applicationBundleID</key>
|
||||
<string>com.apple.finder</string>
|
||||
<key>applicationBundleIDsByPath</key>
|
||||
<dict>
|
||||
<key>/System/Library/CoreServices/Finder.app</key>
|
||||
<string>com.apple.finder</string>
|
||||
</dict>
|
||||
<key>applicationPath</key>
|
||||
<string>/System/Library/CoreServices/Finder.app</string>
|
||||
<key>applicationPaths</key>
|
||||
<array>
|
||||
<string>/System/Library/CoreServices/Finder.app</string>
|
||||
</array>
|
||||
<key>backgroundColor</key>
|
||||
<data>
|
||||
YnBsaXN0MDDUAQIDBAUGNjdYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVy
|
||||
VCR0b3ASAAGGoKoHCBMUFR4kKC8zVSRudWxs1QkKCwwNDg8QERJWJGNsYXNz
|
||||
W05TQ29sb3JOYW1lXE5TQ29sb3JTcGFjZV1OU0NhdGFsb2dOYW1lV05TQ29s
|
||||
b3KACYADEAaAAoAEVlN5c3RlbV8QD3N5c3RlbUJsdWVDb2xvctUWFwsYCRka
|
||||
GxwOXE5TQ29tcG9uZW50c1VOU1JHQl8QEk5TQ3VzdG9tQ29sb3JTcGFjZUcw
|
||||
IDAgMSAxTxARMCAwIDAuOTk4MTg4OTcyNQAQAYAFgAnTHyAJISIjVE5TSURV
|
||||
TlNJQ0MQB4AGgAjSJQkmJ1dOUy5kYXRhTxEMSAAADEhMaW5vAhAAAG1udHJS
|
||||
R0IgWFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAA
|
||||
AAAAAAAAAAD21gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGE
|
||||
AAAAbHd0cHQAAAHwAAAAFGJrcHQAAAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoA
|
||||
AAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJUAAAAcGRtZGQAAALEAAAAiHZ1
|
||||
ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAAFG1lYXMAAAQMAAAA
|
||||
JHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJUUkMAAAQ8
|
||||
AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2Fy
|
||||
ZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAA
|
||||
AAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAA
|
||||
AAEWzFhZWiAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BY
|
||||
WVogAAAAAAAAYpkAALeFAAAY2lhZWiAAAAAAAAAkoAAAD4QAALbPZGVzYwAA
|
||||
AAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAWSUVDIGh0
|
||||
dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVm
|
||||
YXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2
|
||||
MTk2Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2lu
|
||||
ZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJl
|
||||
bmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAAE6T+ABRfLgAQzxQAA+3MAAQT
|
||||
CwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFzAAAAAAAAAAEAAAAA
|
||||
AAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAAAAAABAAA
|
||||
AAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt
|
||||
AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA
|
||||
4ADlAOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFn
|
||||
AW4BdQF8AYMBiwGSAZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQC
|
||||
HQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1
|
||||
AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kE
|
||||
BgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6
|
||||
BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0G
|
||||
rwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghG
|
||||
CFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEK
|
||||
Jwo9ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwq
|
||||
DEMMXAx1DI4MpwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQO
|
||||
fw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1
|
||||
ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QT
|
||||
xRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwWjxay
|
||||
FtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0a
|
||||
BBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1w
|
||||
HZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwh
|
||||
SCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4
|
||||
JWgllyXHJfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWsp
|
||||
nSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4W
|
||||
Lkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQz
|
||||
DTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgU
|
||||
OFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9
|
||||
oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6
|
||||
Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1J
|
||||
Y0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+T
|
||||
T91QJ1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9W
|
||||
XFapVvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0n
|
||||
XXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBk
|
||||
lGTpZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/
|
||||
bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6cZVx8HJLcqZzAXNdc7h0
|
||||
FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7wnwh
|
||||
fIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE
|
||||
44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Y
|
||||
jf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+X
|
||||
Cpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBp
|
||||
oNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyq
|
||||
j6sCq3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSc
|
||||
tRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/
|
||||
er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8yTrJuco4
|
||||
yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V
|
||||
0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE
|
||||
4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHt
|
||||
nO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH
|
||||
+lf65/t3/Af8mP0p/br+S/7c/23//4AH0ikqKyxaJGNsYXNzbmFtZVgkY2xh
|
||||
c3Nlc11OU011dGFibGVEYXRhoystLlZOU0RhdGFYTlNPYmplY3TSKSowMVxO
|
||||
U0NvbG9yU3BhY2WiMi5cTlNDb2xvclNwYWNl0ikqNDVXTlNDb2xvcqI0Ll8Q
|
||||
D05TS2V5ZWRBcmNoaXZlctE4OVRyb290gAEACAARABoAIwAtADIANwBCAEgA
|
||||
UwBaAGYAcwCBAIkAiwCNAI8AkQCTAJoArAC3AMQAygDfAOcA+wD9AP8BAQEI
|
||||
AQ0BEwEVARcBGQEeASYNcg10DXkNhA2NDZsNnw2mDa8NtA3BDcQN0Q3WDd4N
|
||||
4Q3zDfYN+wAAAAAAAAIBAAAAAAAAADoAAAAAAAAAAAAAAAAAAA39
|
||||
</data>
|
||||
<key>backgroundColorName</key>
|
||||
<string>systemBlueColor</string>
|
||||
<key>inputTypeIdentifier</key>
|
||||
<string>com.apple.Automator.fileSystemObject.image</string>
|
||||
<key>outputTypeIdentifier</key>
|
||||
<string>com.apple.Automator.nothing</string>
|
||||
<key>presentationMode</key>
|
||||
<integer>15</integer>
|
||||
<key>processesInput</key>
|
||||
<integer>0</integer>
|
||||
<key>serviceApplicationBundleID</key>
|
||||
<string>com.apple.finder</string>
|
||||
<key>serviceApplicationPath</key>
|
||||
<string>/System/Library/CoreServices/Finder.app</string>
|
||||
<key>serviceInputTypeIdentifier</key>
|
||||
<string>com.apple.Automator.fileSystemObject.image</string>
|
||||
<key>serviceOutputTypeIdentifier</key>
|
||||
<string>com.apple.Automator.nothing</string>
|
||||
<key>serviceProcessesInput</key>
|
||||
<integer>0</integer>
|
||||
<key>systemImageName</key>
|
||||
<string>NSTouchBarShare</string>
|
||||
<key>useAutomaticInputType</key>
|
||||
<integer>0</integer>
|
||||
<key>workflowTypeIdentifier</key>
|
||||
<string>com.apple.Automator.servicesMenu</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="referrer" content="never">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>PicGo</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but picgo-new doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
# require xclip(see http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script/677212#677212)
|
||||
command -v xclip >/dev/null 2>&1 || { echo >&1 "no xclip"; exit 1; }
|
||||
|
||||
# write image in clipboard to file (see http://unix.stackexchange.com/questions/145131/copy-image-from-clipboard-to-file)
|
||||
filePath=`xclip -selection clipboard -o 2>/dev/null | grep ^file:// | cut -c8-`
|
||||
if [ ! -n "$filePath" ] ;then
|
||||
if
|
||||
xclip -selection clipboard -target image/png -o >/dev/null 2>&1
|
||||
then
|
||||
xclip -selection clipboard -target image/png -o >$1 2>/dev/null
|
||||
echo $1
|
||||
else
|
||||
echo "no image"
|
||||
fi
|
||||
else
|
||||
echo $filePath
|
||||
fi
|
||||
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 497 B |
|
After Width: | Height: | Size: 915 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 823 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 777 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,26 @@
|
||||
|
||||
param($imagePath)
|
||||
|
||||
# Adapted from https://github.com/octan3/img-clipboard-dump/blob/master/dump-clipboard-png.ps1
|
||||
|
||||
Add-Type -Assembly PresentationCore
|
||||
$img = [Windows.Clipboard]::GetImage()
|
||||
|
||||
if ($img -eq $null) {
|
||||
"no image"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
if (-not $imagePath) {
|
||||
"no image"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$fcb = new-object Windows.Media.Imaging.FormatConvertedBitmap($img, [Windows.Media.PixelFormats]::Rgb24, $null, 0)
|
||||
$stream = [IO.File]::Open($imagePath, "OpenOrCreate")
|
||||
$encoder = New-Object Windows.Media.Imaging.PngBitmapEncoder
|
||||
$encoder.Frames.Add([Windows.Media.Imaging.BitmapFrame]::Create($fcb)) | out-null
|
||||
$encoder.Save($stream) | out-null
|
||||
$stream.Dispose() | out-null
|
||||
|
||||
$imagePath
|
||||
@@ -0,0 +1,45 @@
|
||||
# Adapted from https://github.com/octan3/img-clipboard-dump/blob/master/dump-clipboard-png.ps1
|
||||
param($imagePath)
|
||||
|
||||
# https://github.com/PowerShell/PowerShell/issues/7233
|
||||
# fix the output encoding bug
|
||||
[console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding
|
||||
|
||||
Add-Type -Assembly PresentationCore
|
||||
function main {
|
||||
$img = [Windows.Clipboard]::GetImage()
|
||||
|
||||
if ($img -eq $null) {
|
||||
"no image"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
if (-not $imagePath) {
|
||||
"no image"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$fcb = new-object Windows.Media.Imaging.FormatConvertedBitmap($img, [Windows.Media.PixelFormats]::Rgb24, $null, 0)
|
||||
$stream = [IO.File]::Open($imagePath, "OpenOrCreate")
|
||||
$encoder = New-Object Windows.Media.Imaging.PngBitmapEncoder
|
||||
$encoder.Frames.Add([Windows.Media.Imaging.BitmapFrame]::Create($fcb)) | out-null
|
||||
$encoder.Save($stream) | out-null
|
||||
$stream.Dispose() | out-null
|
||||
|
||||
$imagePath
|
||||
Exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
# For WIN10
|
||||
$file = Get-Clipboard -Format FileDropList
|
||||
if ($file -ne $null) {
|
||||
Convert-Path $file
|
||||
Exit 1
|
||||
}
|
||||
} catch {
|
||||
# For WIN7 WIN8 WIN10
|
||||
main
|
||||
}
|
||||
|
||||
main
|
||||
@@ -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
|
||||
@@ -0,0 +1,23 @@
|
||||
import { bootstrap } from '~/main/lifeCycle'
|
||||
|
||||
bootstrap.launchApp()
|
||||
|
||||
/**
|
||||
* Auto Updater
|
||||
*
|
||||
* Uncomment the following code below and install `electron-updater` to
|
||||
* support auto updating. Code Signing with a valid certificate is required.
|
||||
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
|
||||
*/
|
||||
|
||||
// import { autoUpdater } from 'electron-updater'
|
||||
|
||||
// autoUpdater.on('update-downloaded', () => {
|
||||
// autoUpdater.quitAndInstall()
|
||||
// })
|
||||
|
||||
// app.on('ready', () => {
|
||||
// if (process.env.NODE_ENV === 'production') {
|
||||
// autoUpdater.checkForUpdates()
|
||||
// }
|
||||
// })
|
||||
@@ -0,0 +1,40 @@
|
||||
import Vue from 'vue'
|
||||
import App from './renderer/App.vue'
|
||||
import router from './renderer/router'
|
||||
import ElementUI from 'element-ui'
|
||||
import { webFrame } from 'electron'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import VueLazyLoad from 'vue-lazyload'
|
||||
import axios from 'axios'
|
||||
import mainMixin from './renderer/utils/mainMixin'
|
||||
import bus from '@/utils/bus'
|
||||
import { initTalkingData } from './renderer/utils/analytics'
|
||||
import db from './renderer/utils/db'
|
||||
|
||||
webFrame.setVisualZoomLevelLimits(1, 1)
|
||||
webFrame.setLayoutZoomLevelLimits(0, 0)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$builtInPicBed = [
|
||||
'smms',
|
||||
'imgur',
|
||||
'qiniu',
|
||||
'tcyun',
|
||||
'upyun',
|
||||
'aliyun',
|
||||
'github'
|
||||
]
|
||||
Vue.prototype.$$db = db
|
||||
Vue.prototype.$http = axios
|
||||
Vue.prototype.$bus = bus
|
||||
|
||||
Vue.use(ElementUI)
|
||||
Vue.use(VueLazyLoad)
|
||||
Vue.mixin(mainMixin)
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
|
||||
initTalkingData()
|
||||
@@ -0,0 +1,13 @@
|
||||
# apis folder
|
||||
|
||||
## core
|
||||
|
||||
The lowest level APIs that are not dependent on each other. The upper APIs depend on them.
|
||||
|
||||
## app
|
||||
|
||||
Provide key API interfaces for PicGo application, including uploader, window management, shortcut key system, etc
|
||||
|
||||
## gui
|
||||
|
||||
GuiApi for PicGo plugins.
|
||||
@@ -0,0 +1,176 @@
|
||||
import bus from '@core/bus'
|
||||
import {
|
||||
globalShortcut
|
||||
} from 'electron'
|
||||
import logger from '@core/picgo/logger'
|
||||
import GuiApi from '../../gui'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import shortKeyService from './shortKeyService'
|
||||
import picgo from '@core/picgo'
|
||||
|
||||
class ShortKeyHandler {
|
||||
private isInModifiedMode: boolean = false
|
||||
constructor () {
|
||||
bus.on(TOGGLE_SHORTKEY_MODIFIED_MODE, flag => {
|
||||
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:'))
|
||||
.forEach(command => {
|
||||
const config = commands[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) {
|
||||
const plugin = picgo.pluginLoader.getPlugin(item)
|
||||
// if a plugin has commands
|
||||
if (plugin && plugin.commands) {
|
||||
if (typeof plugin.commands !== 'function') {
|
||||
logger.warn(`${item}'s commands is not a function`)
|
||||
continue
|
||||
}
|
||||
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
|
||||
for (let cmd of commands) {
|
||||
const command = `${item}:${cmd.name}`
|
||||
if (db.has(`settings.shortKey[${command}]`)) {
|
||||
const commandConfig = db.get(`settings.shortKey.${command}`) as IShortKeyConfig
|
||||
// if disabled, don't register #534
|
||||
if (commandConfig.enable) {
|
||||
this.registerShortKey(commandConfig, command, cmd.handle, false)
|
||||
}
|
||||
} else {
|
||||
this.registerShortKey(cmd, command, cmd.handle, true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
private registerShortKey (config: IShortKeyConfig | IPluginShortKeyConfig, command: string, handler: IShortKeyHandler, writeFlag: boolean) {
|
||||
shortKeyService.registerCommand(command, handler)
|
||||
if (config.key) {
|
||||
globalShortcut.register(config.key, () => {
|
||||
this.handler(command)
|
||||
})
|
||||
} 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}`]: {
|
||||
enable: true,
|
||||
name: config.name,
|
||||
label: config.label,
|
||||
key: config.key
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// enable or disable shortKey
|
||||
bindOrUnbindShortKey (item: IShortKeyConfig, from: string): boolean {
|
||||
const command = `${from}:${item.name}`
|
||||
if (item.enable === false) {
|
||||
globalShortcut.unregister(item.key)
|
||||
picgo.saveConfig({
|
||||
[`settings.shortKey.${command}.enable`]: false
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
if (globalShortcut.isRegistered(item.key)) {
|
||||
return false
|
||||
} else {
|
||||
picgo.saveConfig({
|
||||
[`settings.shortKey.${command}.enable`]: true
|
||||
})
|
||||
globalShortcut.register(item.key, () => {
|
||||
this.handler(command)
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
// update shortKey bindings
|
||||
updateShortKey (item: IShortKeyConfig, oldKey: string, from: string): boolean {
|
||||
const command = `${from}:${item.name}`
|
||||
if (globalShortcut.isRegistered(item.key)) return false
|
||||
globalShortcut.unregister(oldKey)
|
||||
picgo.saveConfig({
|
||||
[`settings.shortKey.${command}.key`]: item.key
|
||||
})
|
||||
globalShortcut.register(item.key, () => {
|
||||
this.handler(`${from}:${item.name}`)
|
||||
})
|
||||
return true
|
||||
}
|
||||
private async handler (command: string) {
|
||||
if (this.isInModifiedMode) {
|
||||
return
|
||||
}
|
||||
if (command.includes('picgo:')) {
|
||||
bus.emit(command)
|
||||
} else if (command.includes('picgo-plugin-')) {
|
||||
const handler = shortKeyService.getShortKeyHandler(command)
|
||||
if (handler) {
|
||||
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) {
|
||||
if (typeof plugin.commands !== 'function') {
|
||||
logger.warn(`${pluginName}'s commands is not a function`)
|
||||
return
|
||||
}
|
||||
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
|
||||
for (let cmd of commands) {
|
||||
const command = `${pluginName}:${cmd.name}`
|
||||
if (db.has(`settings.shortKey[${command}]`)) {
|
||||
const commandConfig = db.get(`settings.shortKey[${command}]`) as IShortKeyConfig
|
||||
this.registerShortKey(commandConfig, command, cmd.handle, false)
|
||||
} else {
|
||||
this.registerShortKey(cmd, command, cmd.handle, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unregisterPluginShortKey (pluginName: string) {
|
||||
const commands = db.get('settings.shortKey') as IShortKeyConfigs
|
||||
const keyList = Object.keys(commands)
|
||||
.filter(command => command.includes(pluginName))
|
||||
.map(command => {
|
||||
return {
|
||||
command,
|
||||
key: commands[command].key
|
||||
}
|
||||
}) as IKeyCommandType[]
|
||||
keyList.forEach(item => {
|
||||
globalShortcut.unregister(item.key)
|
||||
shortKeyService.unregisterCommand(item.command)
|
||||
picgo.unsetConfig('settings.shortKey', item.command)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default new ShortKeyHandler()
|
||||
@@ -0,0 +1,21 @@
|
||||
import logger from '@core/picgo/logger'
|
||||
class ShortKeyService {
|
||||
private commandList: Map<string, IShortKeyHandler> = new Map()
|
||||
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()]
|
||||
}
|
||||
}
|
||||
|
||||
export default new ShortKeyService()
|
||||
@@ -0,0 +1,276 @@
|
||||
import {
|
||||
app,
|
||||
Menu,
|
||||
Tray,
|
||||
dialog,
|
||||
clipboard,
|
||||
systemPreferences,
|
||||
Notification
|
||||
} from 'electron'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import getPicBeds from '~/main/utils/getPicBeds'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import picgo from '@core/picgo'
|
||||
import pasteTemplate from '#/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()
|
||||
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`
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
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: '退出'
|
||||
}
|
||||
])
|
||||
} 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(() => {
|
||||
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
|
||||
})
|
||||
}
|
||||
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()
|
||||
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`)
|
||||
})
|
||||
|
||||
// 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])
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
trayWindow.webContents.send('dragFiles', imgs)
|
||||
}
|
||||
})
|
||||
// toggleWindow()
|
||||
} else if (process.platform === 'linux') {
|
||||
// click事件在Ubuntu上无法触发,Unity不支持(在Mac和Windows上可以触发)
|
||||
// 需要使用 setContextMenu 设置菜单
|
||||
createContextMenu()
|
||||
tray!.setContextMenu(contextMenu)
|
||||
}
|
||||
}
|
||||
|
||||
export function createMenu () {
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
const template = [{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
|
||||
{ label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
|
||||
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
|
||||
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
|
||||
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' },
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'CmdOrCtrl+Q',
|
||||
click () {
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
// @ts-ignore
|
||||
menu = Menu.buildFromTemplate(template)
|
||||
Menu.setApplicationMenu(menu)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleWindow = (bounds: IBounds) => {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
|
||||
if (trayWindow.isVisible()) {
|
||||
trayWindow.hide()
|
||||
} else {
|
||||
trayWindow.setPosition(bounds.x - 98 + 11, bounds.y, false)
|
||||
trayWindow.webContents.send('updateFiles')
|
||||
trayWindow.show()
|
||||
trayWindow.focus()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
Notification,
|
||||
WebContents
|
||||
} from 'electron'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import uploader from '.'
|
||||
import pasteTemplate from '#/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()
|
||||
if (img !== false) {
|
||||
if (img.length > 0) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: '上传成功',
|
||||
body: img[0].imgUrl!,
|
||||
icon: img[0].imgUrl
|
||||
})
|
||||
notification.show()
|
||||
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 handleUrlEncode(img[0].imgUrl as string)
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
title: '上传不成功',
|
||||
body: '你剪贴板最新的一条记录不是图片哦'
|
||||
})
|
||||
notification.show()
|
||||
return ''
|
||||
}
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadChoosedFiles = async (webContents: WebContents, files: IFileWithPath[]): Promise<string[]> => {
|
||||
const input = files.map(item => item.path)
|
||||
const imgs = await uploader.setWebContents(webContents).upload(input)
|
||||
const result = []
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: '上传成功',
|
||||
body: imgs[i].imgUrl!,
|
||||
icon: files[i].path
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, i * 100)
|
||||
await GalleryDB.getInstance().insert(imgs[i])
|
||||
result.push(handleUrlEncode(imgs[i].imgUrl!))
|
||||
}
|
||||
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')
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
Notification,
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
WebContents
|
||||
} from 'electron'
|
||||
import dayjs from 'dayjs'
|
||||
import picgo from '@core/picgo'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import 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<void>((resolve, reject) => {
|
||||
webcontent.on('did-finish-load', () => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const windowId = window.id
|
||||
ipcMain.once(`rename${id}`, (evt: Event, newName: string) => {
|
||||
resolve(newName)
|
||||
window.close()
|
||||
})
|
||||
window.on('close', () => {
|
||||
resolve(null)
|
||||
ipcMain.removeAllListeners(`rename${id}`)
|
||||
windowManager.deleteById(windowId)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
init () {
|
||||
picgo.on('notification', message => {
|
||||
const notification = new Notification(message)
|
||||
notification.show()
|
||||
})
|
||||
|
||||
picgo.on('uploadProgress', progress => {
|
||||
this.webContents?.send('uploadProgress', progress)
|
||||
})
|
||||
picgo.on('beforeTransform', ctx => {
|
||||
if (db.get('settings.uploadNotification')) {
|
||||
const notification = new Notification({
|
||||
title: '上传进度',
|
||||
body: '正在上传'
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
})
|
||||
picgo.helper.beforeUploadPlugins.register('renameFn', {
|
||||
handle: async (ctx: IPicGo) => {
|
||||
const rename = db.get('settings.rename')
|
||||
const autoRename = db.get('settings.autoRename')
|
||||
if (autoRename || rename) {
|
||||
await Promise.all(ctx.output.map(async (item, index) => {
|
||||
let name: undefined | string | null
|
||||
let fileName: string | undefined
|
||||
if (autoRename) {
|
||||
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS') + item.extname
|
||||
} else {
|
||||
fileName = item.fileName
|
||||
}
|
||||
if (rename) {
|
||||
const window = windowManager.create(IWindowList.RENAME_WINDOW)!
|
||||
await waitForShow(window.webContents)
|
||||
window.webContents.send('rename', fileName, window.webContents.id)
|
||||
name = await waitForRename(window, window.webContents.id)
|
||||
}
|
||||
item.fileName = name || fileName
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setWebContents (webContents: WebContents) {
|
||||
this.webContents = webContents
|
||||
return this
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return output.filter(item => item.imgUrl)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(e)
|
||||
setTimeout(() => {
|
||||
showNotification({
|
||||
title: '上传失败',
|
||||
body: util.format(e.stack),
|
||||
clickToCopy: true
|
||||
})
|
||||
}, 500)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new Uploader()
|
||||
@@ -0,0 +1,24 @@
|
||||
export enum IWindowList {
|
||||
SETTING_WINDOW = 'SETTING_WINDOW',
|
||||
TRAY_WINDOW = 'TRAY_WINDOW',
|
||||
MINI_WINDOW = 'MINI_WINDOW',
|
||||
RENAME_WINDOW = 'RENAME_WINDOW'
|
||||
}
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
export const TRAY_WINDOW_URL = isDevelopment
|
||||
? (process.env.WEBPACK_DEV_SERVER_URL as string)
|
||||
: `picgo://./index.html`
|
||||
|
||||
export const SETTING_WINDOW_URL = isDevelopment
|
||||
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#main-page/upload`
|
||||
: `picgo://./index.html#main-page/upload`
|
||||
|
||||
export const MINI_WINDOW_URL = isDevelopment
|
||||
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#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`
|
||||
@@ -0,0 +1,167 @@
|
||||
import {
|
||||
IWindowList,
|
||||
SETTING_WINDOW_URL,
|
||||
TRAY_WINDOW_URL,
|
||||
MINI_WINDOW_URL,
|
||||
RENAME_WINDOW_URL
|
||||
} from './constants'
|
||||
import { IWindowListItem } from '#/types/electron'
|
||||
import bus from '@core/bus'
|
||||
import { CREATE_APP_MENU } from '@core/bus/constants'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import { app } from 'electron'
|
||||
|
||||
const windowList = new Map<IWindowList, IWindowListItem>()
|
||||
|
||||
windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
isValid: process.platform !== 'linux',
|
||||
multiple: false,
|
||||
options () {
|
||||
return {
|
||||
height: 350,
|
||||
width: 196, // 196
|
||||
show: false,
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
transparent: true,
|
||||
vibrancy: 'ultra-dark',
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: true,
|
||||
backgroundThrottling: false
|
||||
}
|
||||
}
|
||||
},
|
||||
callback (window) {
|
||||
window.loadURL(TRAY_WINDOW_URL)
|
||||
window.on('blur', () => {
|
||||
window.hide()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options () {
|
||||
const options: IBrowserWindowOptions = {
|
||||
height: 450,
|
||||
width: 800,
|
||||
show: false,
|
||||
frame: true,
|
||||
center: true,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
title: 'PicGo',
|
||||
vibrancy: 'ultra-dark',
|
||||
transparent: true,
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: true,
|
||||
webSecurity: false
|
||||
}
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
options.show = false
|
||||
options.frame = false
|
||||
options.backgroundColor = '#3f3c37'
|
||||
options.transparent = false
|
||||
options.icon = `${__static}/logo.png`
|
||||
}
|
||||
return options
|
||||
},
|
||||
callback (window, windowManager) {
|
||||
window.loadURL(SETTING_WINDOW_URL)
|
||||
window.on('closed', () => {
|
||||
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
||||
if (process.platform === 'linux') {
|
||||
process.nextTick(() => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
})
|
||||
bus.emit(CREATE_APP_MENU)
|
||||
windowManager.create(IWindowList.MINI_WINDOW)
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.MINI_WINDOW, {
|
||||
isValid: process.platform !== 'darwin',
|
||||
multiple: false,
|
||||
options () {
|
||||
let obj: IBrowserWindowOptions = {
|
||||
height: 64,
|
||||
width: 64,
|
||||
show: process.platform === 'linux',
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
skipTaskbar: true,
|
||||
resizable: false,
|
||||
transparent: process.platform !== 'linux',
|
||||
icon: `${__static}/logo.png`,
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: true
|
||||
}
|
||||
}
|
||||
|
||||
if (db.get('settings.miniWindowOntop')) {
|
||||
obj.alwaysOnTop = true
|
||||
}
|
||||
return obj
|
||||
},
|
||||
callback (window) {
|
||||
window.loadURL(MINI_WINDOW_URL)
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.RENAME_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: true,
|
||||
options () {
|
||||
let options: IBrowserWindowOptions = {
|
||||
height: 175,
|
||||
width: 300,
|
||||
show: true,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
vibrancy: 'ultra-dark',
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: true,
|
||||
backgroundThrottling: false
|
||||
}
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
options.show = true
|
||||
options.backgroundColor = '#3f3c37'
|
||||
options.autoHideMenuBar = true
|
||||
options.transparent = false
|
||||
}
|
||||
return options
|
||||
},
|
||||
async callback (window, windowManager) {
|
||||
window.loadURL(RENAME_WINDOW_URL)
|
||||
const currentWindow = windowManager.getAvailableWindow()
|
||||
if (currentWindow && currentWindow.isVisible()) {
|
||||
// bounds: { x: 821, y: 75, width: 800, height: 450 }
|
||||
const bounds = currentWindow.getBounds()
|
||||
const positionX = bounds.x + bounds.width / 2 - 150
|
||||
let positionY
|
||||
// if is the settingWindow
|
||||
if (bounds.height > 400) {
|
||||
positionY = bounds.y + bounds.height / 2 - 88
|
||||
} else { // if is the miniWindow
|
||||
positionY = bounds.y + bounds.height / 2
|
||||
}
|
||||
window.setPosition(positionX, positionY, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default windowList
|
||||
@@ -0,0 +1,75 @@
|
||||
import {
|
||||
BrowserWindow
|
||||
} from 'electron'
|
||||
import { IWindowManager, IWindowListItem } from '#/types/electron'
|
||||
import windowList from './windowList'
|
||||
import {
|
||||
IWindowList
|
||||
} from './constants'
|
||||
|
||||
class WindowManager implements IWindowManager {
|
||||
private windowMap: Map<IWindowList | string, BrowserWindow> = new Map()
|
||||
private windowIdMap: Map<number, IWindowList | string> = new Map()
|
||||
create (name: IWindowList) {
|
||||
const windowConfig: IWindowListItem = windowList.get(name)!
|
||||
if (windowConfig.isValid) {
|
||||
if (!windowConfig.multiple) {
|
||||
if (this.has(name)) return this.windowMap.get(name)!
|
||||
}
|
||||
const window = new BrowserWindow(windowConfig.options())
|
||||
const id = window.id
|
||||
if (windowConfig.multiple) {
|
||||
this.windowMap.set(`${name}_${window.id}`, window)
|
||||
this.windowIdMap.set(window.id, `${name}_${window.id}`)
|
||||
} else {
|
||||
this.windowMap.set(name, window)
|
||||
this.windowIdMap.set(window.id, name)
|
||||
}
|
||||
windowConfig.callback(window, this)
|
||||
window.on('close', () => {
|
||||
this.deleteById(id)
|
||||
})
|
||||
return window
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
get (name: IWindowList) {
|
||||
if (this.has(name)) {
|
||||
return this.windowMap.get(name)!
|
||||
} else {
|
||||
const window = this.create(name)
|
||||
return window
|
||||
}
|
||||
}
|
||||
has (name: IWindowList) {
|
||||
return this.windowMap.has(name)
|
||||
}
|
||||
// useless
|
||||
// delete (name: IWindowList) {
|
||||
// const window = this.windowMap.get(name)
|
||||
// if (window) {
|
||||
// this.windowIdMap.delete(window.id)
|
||||
// this.windowMap.delete(name)
|
||||
// }
|
||||
// }
|
||||
deleteById = (id: number) => {
|
||||
const name = this.windowIdMap.get(id)
|
||||
if (name) {
|
||||
this.windowMap.delete(name)
|
||||
this.windowIdMap.delete(id)
|
||||
}
|
||||
}
|
||||
getAvailableWindow () {
|
||||
const miniWindow = this.windowMap.get(IWindowList.MINI_WINDOW)
|
||||
if (miniWindow && miniWindow.isVisible()) {
|
||||
return miniWindow
|
||||
} else {
|
||||
const settingWindow = this.windowMap.get(IWindowList.SETTING_WINDOW)
|
||||
const trayWindow = this.windowMap.get(IWindowList.TRAY_WINDOW)
|
||||
return settingWindow || trayWindow || this.create(IWindowList.SETTING_WINDOW)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new WindowManager()
|
||||
@@ -0,0 +1,74 @@
|
||||
import bus from '.'
|
||||
import {
|
||||
UPLOAD_WITH_FILES,
|
||||
UPLOAD_WITH_FILES_RESPONSE,
|
||||
UPLOAD_WITH_CLIPBOARD_FILES,
|
||||
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
|
||||
GET_WINDOW_ID,
|
||||
GET_WINDOW_ID_REPONSE,
|
||||
GET_SETTING_WINDOW_ID,
|
||||
GET_SETTING_WINDOW_ID_RESPONSE
|
||||
} from './constants'
|
||||
|
||||
export const uploadWithClipboardFiles = (): Promise<{
|
||||
success: boolean,
|
||||
result?: string[]
|
||||
}> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
bus.once(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, (result: string) => {
|
||||
if (result) {
|
||||
return resolve({
|
||||
success: true,
|
||||
result: [result]
|
||||
})
|
||||
} else {
|
||||
return resolve({
|
||||
success: false
|
||||
})
|
||||
}
|
||||
})
|
||||
bus.emit(UPLOAD_WITH_CLIPBOARD_FILES)
|
||||
})
|
||||
}
|
||||
|
||||
export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
|
||||
success: boolean,
|
||||
result?: string[]
|
||||
}> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
bus.once(UPLOAD_WITH_FILES_RESPONSE, (result: string[]) => {
|
||||
if (result.length) {
|
||||
return resolve({
|
||||
success: true,
|
||||
result
|
||||
})
|
||||
} else {
|
||||
return resolve({
|
||||
success: false
|
||||
})
|
||||
}
|
||||
})
|
||||
bus.emit(UPLOAD_WITH_FILES, pathList)
|
||||
})
|
||||
}
|
||||
|
||||
// get available window id:
|
||||
// miniWindow or settingWindow or trayWindow
|
||||
export const getWindowId = (): Promise<number> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
bus.once(GET_WINDOW_ID_REPONSE, (id: number) => {
|
||||
resolve(id)
|
||||
})
|
||||
bus.emit(GET_WINDOW_ID)
|
||||
})
|
||||
}
|
||||
|
||||
// get settingWindow id:
|
||||
export const getSettingWindowId = (): Promise<number> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
bus.once(GET_SETTING_WINDOW_ID_RESPONSE, (id: number) => {
|
||||
resolve(id)
|
||||
})
|
||||
bus.emit(GET_SETTING_WINDOW_ID)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export const GET_WINDOW_ID = 'GET_WINDOW_ID' // get a current window
|
||||
export const GET_WINDOW_ID_REPONSE = 'GET_WINDOW_ID_REPONSE'
|
||||
export const GET_SETTING_WINDOW_ID = 'GET_SETTING_WINDOW_ID' // get setting window
|
||||
export const GET_SETTING_WINDOW_ID_RESPONSE = 'GET_SETTING_WINDOW_ID_RESPONSE'
|
||||
export const UPLOAD_WITH_FILES = 'UPLOAD_WITH_FILES'
|
||||
export const UPLOAD_WITH_FILES_RESPONSE = 'UPLOAD_WITH_FILES_RESPONSE'
|
||||
export const UPLOAD_WITH_CLIPBOARD_FILES = 'UPLOAD_WITH_CLIPBOARD_FILES'
|
||||
export const UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE = 'UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE'
|
||||
export const CREATE_APP_MENU = 'CREATE_APP_MENU'
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EventEmitter } from 'events'
|
||||
|
||||
const bus = new EventEmitter()
|
||||
|
||||
export default bus
|
||||
@@ -0,0 +1,132 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import { remote, app } from 'electron'
|
||||
import dayjs from 'dayjs'
|
||||
const APP = process.type === 'renderer' ? remote.app : app
|
||||
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 配置文件损坏,已经恢复为备份配置'
|
||||
}
|
||||
|
||||
function dbChecker () {
|
||||
if (process.type !== 'renderer') {
|
||||
if (!global.notificationList) global.notificationList = []
|
||||
// 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 = '{}'
|
||||
let 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) {
|
||||
// TODO: local logger is needed
|
||||
if (!hasCheckPath) {
|
||||
let optionsTpl = {
|
||||
title: '注意',
|
||||
body: '自定义文件解析出错,请检查路径内容是否正确'
|
||||
}
|
||||
global.notificationList.push(optionsTpl)
|
||||
hasCheckPath = true
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import Datastore from 'lowdb'
|
||||
// @ts-ignore
|
||||
import LodashId from 'lodash-id'
|
||||
import FileSync from 'lowdb/adapters/FileSync'
|
||||
import fs from 'fs-extra'
|
||||
import { dbPathChecker, dbPathDir, getGalleryDBPath } from './dbChecker'
|
||||
import { DBStore } from '@picgo/store'
|
||||
|
||||
const STORE_PATH = dbPathDir()
|
||||
|
||||
if (!fs.pathExistsSync(STORE_PATH)) {
|
||||
fs.mkdirpSync(STORE_PATH)
|
||||
}
|
||||
const CONFIG_PATH: string = dbPathChecker()
|
||||
const DB_PATH: string = getGalleryDBPath().dbPath
|
||||
|
||||
// TODO: use JSONStore with @picgo/store
|
||||
class ConfigStore {
|
||||
private db: Datastore.LowdbSync<Datastore.AdapterSync>
|
||||
constructor () {
|
||||
const adapter = new FileSync(CONFIG_PATH)
|
||||
|
||||
this.db = Datastore(adapter)
|
||||
this.db._.mixin(LodashId)
|
||||
|
||||
if (!this.db.has('picBed').value()) {
|
||||
this.db.set('picBed', {
|
||||
current: 'smms', // deprecated
|
||||
uploader: 'smms',
|
||||
smms: {
|
||||
token: ''
|
||||
}
|
||||
}).write()
|
||||
}
|
||||
|
||||
if (!this.db.has('settings.shortKey').value()) {
|
||||
this.db.set('settings.shortKey[picgo:upload]', {
|
||||
enable: true,
|
||||
key: 'CommandOrControl+Shift+P',
|
||||
name: 'upload',
|
||||
label: '快捷上传'
|
||||
}).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 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
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import PicGoCore from '~/universal/types/picgo'
|
||||
import { dbChecker, dbPathChecker } from 'apis/core/datastore/dbChecker'
|
||||
import pkg from 'root/package.json'
|
||||
// eslint-disable-next-line
|
||||
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
|
||||
const PicGo = requireFunc('picgo') as typeof PicGoCore
|
||||
|
||||
const CONFIG_PATH = dbPathChecker()
|
||||
|
||||
dbChecker()
|
||||
|
||||
const picgo = new PicGo(CONFIG_PATH)
|
||||
picgo.saveConfig({
|
||||
debug: true,
|
||||
PICGO_ENV: 'GUI'
|
||||
})
|
||||
|
||||
global.PICGO_GUI_VERSION = pkg.version
|
||||
picgo.GUI_VERSION = global.PICGO_GUI_VERSION
|
||||
|
||||
export default picgo! as PicGoCore
|
||||
@@ -0,0 +1,5 @@
|
||||
import picgo from '.'
|
||||
|
||||
const logger = picgo.log
|
||||
|
||||
export default logger
|
||||
@@ -0,0 +1,179 @@
|
||||
import {
|
||||
dialog,
|
||||
BrowserWindow,
|
||||
Notification,
|
||||
ipcMain
|
||||
} from 'electron'
|
||||
import path from 'path'
|
||||
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 { handleCopyUrl } from '~/main/utils/common'
|
||||
import {
|
||||
getWindowId,
|
||||
getSettingWindowId
|
||||
} from '@core/bus/apis'
|
||||
import {
|
||||
SHOW_INPUT_BOX
|
||||
} from '~/universal/events/constants'
|
||||
import { DBStore } from '@picgo/store'
|
||||
type PromiseResType<T> = T extends Promise<infer R> ? R : T
|
||||
|
||||
// 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()) {
|
||||
return true
|
||||
}
|
||||
settingWindow.show()
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, 1000) // TODO: a better way to wait page loaded.
|
||||
})
|
||||
}
|
||||
|
||||
private getWebcontentsByWindowId (id: number) {
|
||||
return BrowserWindow.fromId(id).webContents
|
||||
}
|
||||
|
||||
async showInputBox (options: IShowInputBoxOption = {
|
||||
title: '',
|
||||
placeholder: ''
|
||||
}) {
|
||||
await this.showSettingWindow()
|
||||
this.getWebcontentsByWindowId(this.settingWindowId)
|
||||
.send(SHOW_INPUT_BOX, options)
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
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 upload (input: IUploadOption) {
|
||||
this.windowId = await getWindowId()
|
||||
const webContents = this.getWebcontentsByWindowId(this.windowId)
|
||||
const imgs = await uploader.setWebContents(webContents).upload(input)
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: '上传成功',
|
||||
body: imgs[i].imgUrl as string,
|
||||
icon: imgs[i].imgUrl
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, i * 100)
|
||||
await GalleryDB.getInstance().insert(imgs[i])
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
webContents.send('uploadFiles', imgs)
|
||||
webContents.send('updateGallery')
|
||||
return imgs
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
showNotification (options: IShowNotificationOption = {
|
||||
title: '',
|
||||
body: ''
|
||||
}) {
|
||||
const notification = new Notification({
|
||||
title: options.title,
|
||||
body: options.body
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
|
||||
showMessageBox (options: IShowMessageBoxOption = {
|
||||
title: '',
|
||||
message: '',
|
||||
type: 'info',
|
||||
buttons: ['Yes', 'No']
|
||||
}) {
|
||||
return new Promise<IShowMessageBoxResult>(async (resolve, reject) => {
|
||||
this.windowId = await getWindowId()
|
||||
dialog.showMessageBox(
|
||||
BrowserWindow.fromId(this.windowId),
|
||||
options
|
||||
).then((res) => {
|
||||
resolve({
|
||||
result: res.response,
|
||||
checkboxChecked: res.checkboxChecked
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -0,0 +1,61 @@
|
||||
import bus from '@core/bus'
|
||||
import {
|
||||
uploadClipboardFiles,
|
||||
uploadChoosedFiles
|
||||
} from 'apis/app/uploader/apis'
|
||||
import {
|
||||
createMenu
|
||||
} from 'apis/app/system'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import {
|
||||
UPLOAD_WITH_FILES,
|
||||
UPLOAD_WITH_FILES_RESPONSE,
|
||||
UPLOAD_WITH_CLIPBOARD_FILES,
|
||||
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
|
||||
GET_WINDOW_ID,
|
||||
GET_WINDOW_ID_REPONSE,
|
||||
GET_SETTING_WINDOW_ID,
|
||||
GET_SETTING_WINDOW_ID_RESPONSE,
|
||||
CREATE_APP_MENU
|
||||
} from '@core/bus/constants'
|
||||
function initEventCenter () {
|
||||
const eventList: any = {
|
||||
'picgo:upload': uploadClipboardFiles,
|
||||
[UPLOAD_WITH_CLIPBOARD_FILES]: busCallUploadClipboardFiles,
|
||||
[UPLOAD_WITH_FILES]: busCallUploadFiles,
|
||||
[GET_WINDOW_ID]: busCallGetWindowId,
|
||||
[GET_SETTING_WINDOW_ID]: busCallGetSettingWindowId,
|
||||
[CREATE_APP_MENU]: createMenu
|
||||
}
|
||||
for (let i in eventList) {
|
||||
bus.on(i, eventList[i])
|
||||
}
|
||||
}
|
||||
|
||||
async function busCallUploadClipboardFiles () {
|
||||
const imgUrl = await uploadClipboardFiles()
|
||||
bus.emit(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, imgUrl)
|
||||
}
|
||||
|
||||
async function busCallUploadFiles (pathList: IFileWithPath[]) {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
const urls = await uploadChoosedFiles(win.webContents, pathList)
|
||||
bus.emit(UPLOAD_WITH_FILES_RESPONSE, urls)
|
||||
}
|
||||
|
||||
function busCallGetWindowId () {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
bus.emit(GET_WINDOW_ID_REPONSE, win.id)
|
||||
}
|
||||
|
||||
function busCallGetSettingWindowId () {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
bus.emit(GET_SETTING_WINDOW_ID_RESPONSE, settingWindow.id)
|
||||
}
|
||||
|
||||
export default {
|
||||
listen () {
|
||||
initEventCenter()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import {
|
||||
app,
|
||||
ipcMain,
|
||||
Notification,
|
||||
IpcMainEvent
|
||||
} 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, { 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 {
|
||||
uploadClipboardFiles,
|
||||
uploadChoosedFiles
|
||||
} from '~/main/apis/app/uploader/apis'
|
||||
import picgoCoreIPC from './picgoCoreIPC'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
|
||||
export default {
|
||||
listen () {
|
||||
picgoCoreIPC.listen()
|
||||
// from macOS tray
|
||||
ipcMain.on('uploadClipboardFiles', async () => {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
|
||||
const img = await uploader.setWebContents(trayWindow.webContents).upload()
|
||||
if (img !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: '上传成功',
|
||||
body: img[0].imgUrl!,
|
||||
// icon: file[0]
|
||||
icon: img[0].imgUrl
|
||||
})
|
||||
notification.show()
|
||||
await GalleryDB.getInstance().insert(img[0])
|
||||
trayWindow.webContents.send('clipboardFiles', [])
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
|
||||
}
|
||||
}
|
||||
trayWindow.webContents.send('uploadFiles')
|
||||
})
|
||||
|
||||
ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
|
||||
uploadClipboardFiles()
|
||||
})
|
||||
|
||||
ipcMain.on('uploadChoosedFiles', async (evt: IpcMainEvent, files: IFileWithPath[]) => {
|
||||
return uploadChoosedFiles(evt.sender, files)
|
||||
})
|
||||
|
||||
ipcMain.on('updateShortKey', (evt: IpcMainEvent, item: IShortKeyConfig, oldKey: string, from: string) => {
|
||||
const result = shortKeyHandler.updateShortKey(item, oldKey, from)
|
||||
evt.sender.send('updateShortKeyResponse', result)
|
||||
if (result) {
|
||||
const notification = new Notification({
|
||||
title: '操作成功',
|
||||
body: '你的快捷键已经修改成功'
|
||||
})
|
||||
notification.show()
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
title: '操作失败',
|
||||
body: '快捷键冲突,请重新设置'
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('bindOrUnbindShortKey', (evt: IpcMainEvent, item: IShortKeyConfig, from: string) => {
|
||||
const result = shortKeyHandler.bindOrUnbindShortKey(item, from)
|
||||
if (result) {
|
||||
const notification = new Notification({
|
||||
title: '操作成功',
|
||||
body: '你的快捷键已经修改成功'
|
||||
})
|
||||
notification.show()
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
title: '操作失败',
|
||||
body: '快捷键冲突,请重新设置'
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('updateCustomLink', () => {
|
||||
const notification = new Notification({
|
||||
title: '操作成功',
|
||||
body: '你的自定义链接格式已经修改成功'
|
||||
})
|
||||
notification.show()
|
||||
})
|
||||
|
||||
ipcMain.on('autoStart', (evt: IpcMainEvent, val: boolean) => {
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: val
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.on('openSettingWindow', () => {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.show()
|
||||
if (windowManager.has(IWindowList.MINI_WINDOW)) {
|
||||
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('openMiniWindow', () => {
|
||||
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
miniWindow.show()
|
||||
miniWindow.focus()
|
||||
settingWindow.hide()
|
||||
})
|
||||
|
||||
// from mini window
|
||||
ipcMain.on('syncPicBed', () => {
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('getPicBeds', (evt: IpcMainEvent) => {
|
||||
const picBeds = getPicBeds()
|
||||
evt.sender.send('getPicBeds', picBeds)
|
||||
evt.returnValue = picBeds
|
||||
})
|
||||
|
||||
ipcMain.on(TOGGLE_SHORTKEY_MODIFIED_MODE, (evt: IpcMainEvent, val: boolean) => {
|
||||
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, val)
|
||||
})
|
||||
|
||||
ipcMain.on('updateServer', () => {
|
||||
server.restart()
|
||||
})
|
||||
},
|
||||
dispose () {}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
import path from 'path'
|
||||
import GuiApi from 'apis/gui'
|
||||
import {
|
||||
dialog,
|
||||
shell,
|
||||
IpcMainEvent,
|
||||
ipcMain
|
||||
} from 'electron'
|
||||
import PicGoCore from '~/universal/types/picgo'
|
||||
import { 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
|
||||
} from '#/events/constants'
|
||||
|
||||
import { GalleryDB } from 'apis/core/datastore'
|
||||
import { IObject, IFilter } from '@picgo/store/dist/types'
|
||||
|
||||
// eslint-disable-next-line
|
||||
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
|
||||
// const PluginHandler = requireFunc('picgo/dist/lib/PluginHandler').default
|
||||
const STORE_PATH = path.dirname(dbPathChecker())
|
||||
// const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
|
||||
|
||||
type PicGoNotice = {
|
||||
title: string,
|
||||
body: string[]
|
||||
}
|
||||
|
||||
interface GuiMenuItem {
|
||||
label: string
|
||||
handle: (arg0: PicGoCore, arg1: GuiApi) => Promise<void>
|
||||
}
|
||||
|
||||
// get uploader or transformer config
|
||||
const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => {
|
||||
let config: any[] = []
|
||||
if (name === '') {
|
||||
return config
|
||||
} else {
|
||||
const handler = ctx.helper[type].get(name)
|
||||
if (handler) {
|
||||
if (handler.config) {
|
||||
config = handler.config(ctx)
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
||||
const handleConfigWithFunction = (config: any[]) => {
|
||||
for (let i in config) {
|
||||
if (typeof config[i].default === 'function') {
|
||||
config[i].default = config[i].default()
|
||||
}
|
||||
if (typeof config[i].choices === 'function') {
|
||||
config[i].choices = config[i].choices()
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
const getPluginList = (): IPicGoPlugin[] => {
|
||||
const pluginList = picgo.pluginLoader.getFullList()
|
||||
const list = []
|
||||
for (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: 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 list = getPluginList()
|
||||
event.sender.send('pluginList', list)
|
||||
})
|
||||
}
|
||||
|
||||
const handlePluginInstall = () => {
|
||||
ipcMain.on('installPlugin', async (event: IpcMainEvent, fullName: string) => {
|
||||
const dispose = handleNPMError()
|
||||
const res = await picgo.pluginHandler.install([fullName])
|
||||
event.sender.send('installPlugin', {
|
||||
success: res.success,
|
||||
body: fullName,
|
||||
errMsg: res.success ? '' : res.body
|
||||
})
|
||||
if (res.success) {
|
||||
shortKeyHandler.registerPluginShortKey(res.body[0])
|
||||
} else {
|
||||
showNotification({
|
||||
title: '插件安装失败',
|
||||
body: res.body as string
|
||||
})
|
||||
}
|
||||
event.sender.send('hideLoading')
|
||||
dispose()
|
||||
})
|
||||
}
|
||||
|
||||
const handlePluginUninstall = () => {
|
||||
ipcMain.on('uninstallPlugin', async (event: IpcMainEvent, msg: string) => {
|
||||
const dispose = handleNPMError()
|
||||
const res = await picgo.pluginHandler.uninstall([msg])
|
||||
if (res.success) {
|
||||
event.sender.send('uninstallSuccess', res.body[0])
|
||||
shortKeyHandler.unregisterPluginShortKey(res.body[0])
|
||||
} else {
|
||||
showNotification({
|
||||
title: '插件卸载失败',
|
||||
body: res.body as string
|
||||
})
|
||||
}
|
||||
event.sender.send('hideLoading')
|
||||
dispose()
|
||||
})
|
||||
}
|
||||
|
||||
const handlePluginUpdate = () => {
|
||||
ipcMain.on('updatePlugin', async (event: IpcMainEvent, msg: string) => {
|
||||
const dispose = handleNPMError()
|
||||
const res = await picgo.pluginHandler.update([msg])
|
||||
if (res.success) {
|
||||
event.sender.send('updateSuccess', res.body[0])
|
||||
} else {
|
||||
showNotification({
|
||||
title: '插件更新失败',
|
||||
body: res.body as string
|
||||
})
|
||||
}
|
||||
event.sender.send('hideLoading')
|
||||
dispose()
|
||||
})
|
||||
}
|
||||
|
||||
const handleNPMError = (): IDispose => {
|
||||
const handler = (msg: string) => {
|
||||
if (msg === 'NPM is not installed') {
|
||||
dialog.showMessageBox({
|
||||
title: '发生错误',
|
||||
message: '请安装Node.js并重启PicGo再继续操作',
|
||||
buttons: ['Yes']
|
||||
}).then((res) => {
|
||||
if (res.response === 0) {
|
||||
shell.openExternal('https://nodejs.org/')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
picgo.once('failed', handler)
|
||||
return () => picgo.off('failed', handler)
|
||||
}
|
||||
|
||||
const handleGetPicBedConfig = () => {
|
||||
ipcMain.on('getPicBedConfig', (event: IpcMainEvent, type: string) => {
|
||||
const name = picgo.helper.uploader.get(type)?.name || type
|
||||
if (picgo.helper.uploader.get(type)?.config) {
|
||||
const config = handleConfigWithFunction(picgo.helper.uploader.get(type)!.config(picgo))
|
||||
event.sender.send('getPicBedConfig', config, name)
|
||||
} else {
|
||||
event.sender.send('getPicBedConfig', [], name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handlePluginActions = () => {
|
||||
ipcMain.on('pluginActions', (event: IpcMainEvent, name: string, label: string) => {
|
||||
const plugin = picgo.pluginLoader.getPlugin(name)
|
||||
if (plugin?.guiMenu?.(picgo)?.length) {
|
||||
const menu: GuiMenuItem[] = plugin.guiMenu(picgo)
|
||||
menu.forEach(item => {
|
||||
if (item.label === label) {
|
||||
item.handle(picgo, GuiApi.getInstance())
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleRemoveFiles = () => {
|
||||
ipcMain.on('removeFiles', (event: IpcMainEvent, files: ImgInfo[]) => {
|
||||
setTimeout(() => {
|
||||
picgo.emit('remove', files, GuiApi.getInstance())
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
const handlePicGoSaveConfig = () => {
|
||||
ipcMain.on(PICGO_SAVE_CONFIG, (event: IpcMainEvent, data: IObj) => {
|
||||
picgo.saveConfig(data)
|
||||
})
|
||||
}
|
||||
|
||||
const handlePicGoGetConfig = () => {
|
||||
ipcMain.on(PICGO_GET_CONFIG, (event: IpcMainEvent, key: string | undefined, callbackId: string) => {
|
||||
const result = picgo.getConfig(key)
|
||||
event.sender.send(PICGO_GET_CONFIG, result, callbackId)
|
||||
})
|
||||
}
|
||||
|
||||
const handleImportLocalPlugin = () => {
|
||||
ipcMain.on('importLocalPlugin', (event: IpcMainEvent) => {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
dialog.showOpenDialog(settingWindow, {
|
||||
properties: ['openDirectory']
|
||||
}, async (filePath: string[]) => {
|
||||
if (filePath.length > 0) {
|
||||
const res = await picgo.pluginHandler.install(filePath)
|
||||
if (res.success) {
|
||||
const list = getPluginList()
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
const handleOpenFile = () => {
|
||||
ipcMain.on(PICGO_OPEN_FILE, (event: IpcMainEvent, fileName: string) => {
|
||||
const abFilePath = path.join(STORE_PATH, fileName)
|
||||
shell.openItem(abFilePath)
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
listen () {
|
||||
handleGetPluginList()
|
||||
handlePluginInstall()
|
||||
handlePluginUninstall()
|
||||
handlePluginUpdate()
|
||||
handleGetPicBedConfig()
|
||||
handlePluginActions()
|
||||
handleRemoveFiles()
|
||||
handlePicGoSaveConfig()
|
||||
handlePicGoGetConfig()
|
||||
handlePicGoGalleryDB()
|
||||
handleImportLocalPlugin()
|
||||
handleOpenFile()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
@@ -0,0 +1,191 @@
|
||||
import './errorHandler'
|
||||
import {
|
||||
app,
|
||||
globalShortcut,
|
||||
protocol,
|
||||
Notification
|
||||
} from 'electron'
|
||||
import {
|
||||
createProtocol
|
||||
} from 'vue-cli-plugin-electron-builder/lib'
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
import beforeOpen from '~/main/utils/beforeOpen'
|
||||
import fixPath from 'fix-path'
|
||||
import ipcList from '~/main/events/ipcList'
|
||||
import busEventList from '~/main/events/busEventList'
|
||||
import { IWindowList } from 'apis/app/window/constants'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import {
|
||||
updateShortKeyFromVersion212,
|
||||
migrateGalleryFromVersion230
|
||||
} from '~/main/migrate'
|
||||
import {
|
||||
uploadChoosedFiles,
|
||||
uploadClipboardFiles
|
||||
} from 'apis/app/uploader/apis'
|
||||
import {
|
||||
createTray
|
||||
} from 'apis/app/system'
|
||||
import server from '~/main/server/index'
|
||||
import updateChecker from '~/main/utils/updateChecker'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
import { getUploadFiles } from '~/main/utils/handleArgv'
|
||||
import db, { 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 async beforeReady () {
|
||||
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
|
||||
// fix the $PATH in macOS
|
||||
fixPath()
|
||||
beforeOpen()
|
||||
ipcList.listen()
|
||||
busEventList.listen()
|
||||
updateShortKeyFromVersion212(db, db.get('settings.shortKey'))
|
||||
await migrateGalleryFromVersion230(db, GalleryDB.getInstance(), picgo)
|
||||
}
|
||||
private onReady () {
|
||||
const readyFunction = async () => {
|
||||
console.log('on ready')
|
||||
createProtocol('picgo')
|
||||
if (isDevelopment && !process.env.IS_TEST) {
|
||||
// Install Vue Devtools
|
||||
try {
|
||||
await installExtension(VUEJS_DEVTOOLS)
|
||||
} catch (e) {
|
||||
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)
|
||||
createTray()
|
||||
db.set('needReload', false)
|
||||
updateChecker()
|
||||
// 不需要阻塞
|
||||
process.nextTick(() => {
|
||||
shortKeyHandler.init()
|
||||
})
|
||||
server.startup()
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
handleStartUpFiles(process.argv, process.cwd())
|
||||
}
|
||||
|
||||
if (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) => {
|
||||
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()) {
|
||||
settingWindow.restore()
|
||||
}
|
||||
settingWindow.focus()
|
||||
}
|
||||
}
|
||||
})
|
||||
app.on('activate', () => {
|
||||
createProtocol('picgo')
|
||||
if (!windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
}
|
||||
if (!windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
}
|
||||
})
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: db.get('settings.autoStart') || false
|
||||
})
|
||||
if (process.platform === 'win32') {
|
||||
app.setAppUserModelId('com.molunerfinn.picgo')
|
||||
}
|
||||
|
||||
if (process.env.XDG_CURRENT_DESKTOP && process.env.XDG_CURRENT_DESKTOP.includes('Unity')) {
|
||||
process.env.XDG_CURRENT_DESKTOP = 'Unity'
|
||||
}
|
||||
}
|
||||
private onQuit () {
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('will-quit', () => {
|
||||
globalShortcut.unregisterAll()
|
||||
bus.removeAllListeners()
|
||||
server.shutdown()
|
||||
})
|
||||
// Exit cleanly on request from parent process in development mode.
|
||||
if (isDevelopment) {
|
||||
if (process.platform === 'win32') {
|
||||
process.on('message', data => {
|
||||
if (data === 'graceful-exit') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
process.on('SIGTERM', () => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
async launchApp () {
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
await this.beforeReady()
|
||||
this.onReady()
|
||||
this.onRunning()
|
||||
this.onQuit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bootstrap = new LifeCycle()
|
||||
|
||||
export {
|
||||
bootstrap
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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 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) {
|
||||
// @ts-ignore
|
||||
shortKeyConfig['picgo:upload'] = {
|
||||
enable: true,
|
||||
key: shortKeyConfig.upload,
|
||||
name: 'upload',
|
||||
label: '快捷上传'
|
||||
}
|
||||
// @ts-ignore
|
||||
delete shortKeyConfig.upload
|
||||
db.set('settings.shortKey', shortKeyConfig)
|
||||
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 && originGallery?.length > 0) {
|
||||
if (fse.existsSync(configBakPath)) {
|
||||
fse.copyFileSync(configPath, configBakPath)
|
||||
}
|
||||
await galleryDB.insertMany(originGallery)
|
||||
picgo.saveConfig({
|
||||
uploaded: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
updateShortKeyFromVersion212,
|
||||
migrateGalleryFromVersion230
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import http from 'http'
|
||||
import routers from './routerManager'
|
||||
import {
|
||||
handleResponse,
|
||||
ensureHTTPLink
|
||||
} from './utils'
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import axios from 'axios'
|
||||
|
||||
class Server {
|
||||
private httpServer: http.Server
|
||||
private config: IServerConfig
|
||||
constructor () {
|
||||
let config = picgo.getConfig<IServerConfig>('settings.server')
|
||||
const result = this.checkIfConfigIsValid(config)
|
||||
if (result) {
|
||||
this.config = config
|
||||
} else {
|
||||
config = {
|
||||
port: 36677,
|
||||
host: '127.0.0.1',
|
||||
enable: true
|
||||
}
|
||||
this.config = config
|
||||
picgo.saveConfig({
|
||||
'settings.server': config
|
||||
})
|
||||
}
|
||||
this.httpServer = http.createServer(this.handleRequest)
|
||||
}
|
||||
private checkIfConfigIsValid (config: IObj | undefined) {
|
||||
if (config && config.port && config.host && (config.enable !== undefined)) {
|
||||
return true
|
||||
} else {
|
||||
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,
|
||||
header: {},
|
||||
body: {
|
||||
success: false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let body: string = ''
|
||||
let postObj: IObj
|
||||
request.on('data', chunk => {
|
||||
body += chunk
|
||||
})
|
||||
request.on('end', () => {
|
||||
try {
|
||||
postObj = (body === '') ? {} : JSON.parse(body)
|
||||
} catch (err) {
|
||||
logger.error(`[PicGo Server]`, err)
|
||||
return handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'Not sending data in JSON format'
|
||||
}
|
||||
})
|
||||
}
|
||||
logger.info(`[PicGo Server] get the request`, body)
|
||||
const handler = routers.getHandler(request.url!)
|
||||
handler!({
|
||||
...postObj,
|
||||
response
|
||||
})
|
||||
})
|
||||
}
|
||||
} 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}`)
|
||||
if (typeof port === 'string') {
|
||||
port = parseInt(port, 10)
|
||||
}
|
||||
this.httpServer.listen(port, this.config.host).on('error', async (err: ErrnoException) => {
|
||||
if (err.errno === 'EADDRINUSE') {
|
||||
try {
|
||||
// make sure the system has a PicGo Server instance
|
||||
await axios.post(ensureHTTPLink(`${this.config.host}:${port}/heartbeat`))
|
||||
this.shutdown(true)
|
||||
} catch (e) {
|
||||
logger.warn(`[PicGo Server] ${port} is busy, trying with port ${(port as number) + 1}`)
|
||||
// fix a bug: not write an increase number to config file
|
||||
// to solve the auto number problem
|
||||
this.listen((port as number) + 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
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()
|
||||
this.startup()
|
||||
}
|
||||
}
|
||||
|
||||
export default new Server()
|
||||
@@ -0,0 +1,20 @@
|
||||
class Router {
|
||||
private router = new Map<string, routeHandler>()
|
||||
|
||||
get (url: string, callback: routeHandler): void {
|
||||
this.router.set(url, callback)
|
||||
}
|
||||
post (url: string, callback: routeHandler): void {
|
||||
this.router.set(url, callback)
|
||||
}
|
||||
|
||||
getHandler (url: string) {
|
||||
if (this.router.has(url)) {
|
||||
return this.router.get(url)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new Router()
|
||||
@@ -0,0 +1,98 @@
|
||||
import router from './router'
|
||||
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,
|
||||
list = []
|
||||
} : {
|
||||
response: IHttpResponse,
|
||||
list?: string[]
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
if (list.length === 0) {
|
||||
// upload with clipboard
|
||||
logger.info('[PicGo Server] upload clipboard file')
|
||||
const res = await uploadClipboardFiles()
|
||||
if (res) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: [res]
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
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 win = windowManager.getAvailableWindow()
|
||||
const res = await uploadChoosedFiles(win.webContents, pathList)
|
||||
if (res.length) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: res
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/heartbeat', async ({
|
||||
response
|
||||
} : {
|
||||
response: IHttpResponse,
|
||||
}) => {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: 'alive'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,30 @@
|
||||
import logger from '@core/picgo/logger'
|
||||
|
||||
export const handleResponse = ({
|
||||
response,
|
||||
statusCode = 200,
|
||||
header = {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body = {
|
||||
success: false
|
||||
}
|
||||
} : {
|
||||
response: IHttpResponse,
|
||||
statusCode?: number,
|
||||
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()
|
||||
}
|
||||
|
||||
export const ensureHTTPLink = (url: string): string => {
|
||||
return url.startsWith('http')
|
||||
? url
|
||||
: `http://${url}`
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
|
||||
|
||||
const configPath = dbPathChecker()
|
||||
const CONFIG_DIR = path.dirname(configPath)
|
||||
|
||||
function beforeOpen () {
|
||||
if (process.platform === 'darwin') {
|
||||
resolveMacWorkFlow()
|
||||
}
|
||||
resolveClipboardImageGenerator()
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS 右键菜单
|
||||
*/
|
||||
function resolveMacWorkFlow () {
|
||||
const dest = `${os.homedir}/Library/Services/Upload pictures with PicGo.workflow`
|
||||
if (fs.existsSync(dest)) {
|
||||
return true
|
||||
} else {
|
||||
try {
|
||||
fs.copySync(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化剪贴板生成图片的脚本
|
||||
*/
|
||||
function resolveClipboardImageGenerator () {
|
||||
let clipboardFiles = getClipboardFiles()
|
||||
if (!fs.pathExistsSync(path.join(CONFIG_DIR, 'windows10.ps1'))) {
|
||||
clipboardFiles.forEach(item => {
|
||||
fs.copyFileSync(item.origin, item.dest)
|
||||
})
|
||||
} else {
|
||||
clipboardFiles.forEach(item => {
|
||||
diffFilesAndUpdate(item.origin, item.dest)
|
||||
})
|
||||
}
|
||||
|
||||
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
|
||||
try {
|
||||
let file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
|
||||
let file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
|
||||
|
||||
if (!file1 || !file2 || !file1.equals(file2)) {
|
||||
fs.copyFileSync(filePath1, filePath2)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
fs.copyFileSync(filePath1, filePath2)
|
||||
}
|
||||
}
|
||||
|
||||
function getClipboardFiles () {
|
||||
let files = [
|
||||
'/linux.sh',
|
||||
'/mac.applescript',
|
||||
'/windows.ps1',
|
||||
'/windows10.ps1',
|
||||
'/wsl.sh'
|
||||
]
|
||||
|
||||
return files.map(item => {
|
||||
return {
|
||||
origin: path.join(__static, item),
|
||||
dest: path.join(CONFIG_DIR, item)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default beforeOpen
|
||||
@@ -0,0 +1,71 @@
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { clipboard, Notification, dialog } from 'electron'
|
||||
|
||||
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, reject) => {
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import picgo from '@core/picgo'
|
||||
|
||||
const getPicBeds = () => {
|
||||
const picBedTypes = picgo.helper.uploader.getIdList()
|
||||
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,
|
||||
visible: visible ? visible.visible : true
|
||||
}
|
||||
}) as IPicBedType[]
|
||||
return picBeds
|
||||
}
|
||||
|
||||
export default getPicBeds
|
||||
@@ -0,0 +1,61 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import Logger from 'picgo/dist/src/lib/Logger'
|
||||
interface IResultFileObject {
|
||||
path: string
|
||||
}
|
||||
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 ((fileList?.length || 0) > 0) {
|
||||
const result = fileList!.map(item => {
|
||||
if (path.isAbsolute(item)) {
|
||||
return {
|
||||
path: item
|
||||
}
|
||||
} else {
|
||||
let tempPath = path.join(cwd, item)
|
||||
if (fs.existsSync(tempPath)) {
|
||||
return {
|
||||
path: tempPath
|
||||
}
|
||||
} else {
|
||||
logger.warn(`cli -> can't get file: ${tempPath}, invalid path`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}).filter(item => item !== null) as Result
|
||||
return result
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export {
|
||||
getUploadFiles
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { dialog, shell } from 'electron'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import axios from 'axios'
|
||||
import pkg from 'root/package.json'
|
||||
import { lt } from 'semver'
|
||||
const version = pkg.version
|
||||
const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
|
||||
const releaseUrlBackup = 'https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@latest/package.json'
|
||||
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
|
||||
|
||||
const checkVersion = async () => {
|
||||
let showTip = db.get('settings.showUpdateTip')
|
||||
if (showTip === undefined) {
|
||||
db.set('settings.showUpdateTip', true)
|
||||
showTip = true
|
||||
}
|
||||
if (showTip) {
|
||||
let res: any
|
||||
try {
|
||||
res = await axios.get(releaseUrl).catch(async () => {
|
||||
const result = await axios.get(releaseUrlBackup)
|
||||
return result
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
if (res.status === 200) {
|
||||
const latest = res.data.version || res.data.name
|
||||
const result = compareVersion2Update(version, latest)
|
||||
if (result) {
|
||||
dialog.showMessageBox({
|
||||
type: 'info',
|
||||
title: '发现新版本',
|
||||
buttons: ['Yes', 'No'],
|
||||
message: `发现新版本${latest},更新了很多功能,是否去下载最新的版本?`,
|
||||
checkboxLabel: '以后不再提醒',
|
||||
checkboxChecked: false
|
||||
}).then(res => {
|
||||
if (res.response === 0) { // if selected yes
|
||||
shell.openExternal(downloadUrl)
|
||||
}
|
||||
db.set('settings.showUpdateTip', !res.checkboxChecked)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// if true -> update else return false
|
||||
const compareVersion2Update = (current: string, latest: string) => {
|
||||
try {
|
||||
if (latest.includes('beta')) {
|
||||
const isCheckBetaUpdate = db.get('settings.checkBetaUpdate') !== false
|
||||
if (!isCheckBetaUpdate) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return lt(current, latest)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export default checkVersion
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'picgo'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
body,
|
||||
html
|
||||
padding 0
|
||||
margin 0
|
||||
height 100%
|
||||
font-family "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif
|
||||
#app
|
||||
user-select none
|
||||
overflow hidden
|
||||
.el-button-group
|
||||
width 100%
|
||||
.el-button
|
||||
width 50%
|
||||
</style>
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
@font-face {font-family: "iconfont";
|
||||
src: url('iconfont.eot?t=1523001890286'); /* IE9*/
|
||||
src: url('iconfont.eot?t=1523001890286#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAApcAAsAAAAADqAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kmaY21hcAAAAYAAAACMAAAB5Jz6bNVnbHlmAAACDAAABisAAAfkf7GmJ2hlYWQAAAg4AAAALwAAADYQ+dpBaGhlYQAACGgAAAAcAAAAJAfeA4lobXR4AAAIhAAAABQAAAAgH+kAAGxvY2EAAAiYAAAAEgAAABII+gbebWF4cAAACKwAAAAfAAAAIAEYAMxuYW1lAAAIzAAAAUUAAAJtPlT+fXBvc3QAAAoUAAAASAAAAF2Rted1eJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/ss4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDxfzdzwv4EhhrmBoQEozAiSAwAxwA0deJzFkdEJwzAMRE9ymoRiSv+TETpK6BQZwvSr+2QvZYz0ZLmFTpAzz/gOGRkLwAVAIg/SAfKGwPViKjVPuNa8w5M+Y4TyXExttmXfjoNpMbHp574SVmfccOdy12Ngv8TbStvjNMl5rf+V6742N5DS4BOtwX+DaeA1NgU+O5sDn6Etgc9x3wLoBwU0H794nG1VXYwcRxHu6p7unt+enf/9m9293buZde5u97w/M3Zs7xof2AoJPsLJ4JMR52AUEUgiJcLxQ2znEEIkEEQiJGOQIl0QCDlKpCgP5MXipAiJl5N44S0IAvjVQkGKBUIs9OzFEkjslGqner7pmar6vhpEEfr3n8htUkYe6qLD6JPoswgBW4a2wDEspKMeXoZggQaRL0jaSRd4p90jJyBqMz8cZKMkYpzZIKABw4VBlvZwCuPRBB+DQRgDVGrVTXep7pJXQC+njW/PPo1/CkGzU7cnq7OHVqb+oOWpV0zXrbjuyyqjVMVYsQU8FYUa1XQ2+xm1q8Ht5iHcBLOSVh/Zslo197EXR0/HS5EGsLMDXq0lfjF1qo60q9XQcyu8ZKnlqtVZ9OHKHaPsmXHyFyR/IHP9DXmP5GhdBgJ4A6IwS8csyXvAkyxqQNCAfAIyTEeZ9HkSRtkgmgAPmUQH83tiYOEBCKO9GcW3XirZ8MUJLrVwtaK0O62zgWLrG8+Tcki5A8dP5t/ITx4Hh9OwTK6eNV2wnRdvYTojOZ3t3bzTJ7a+KSY0rYOvAU3JGphhQ6UXri+diLRpn6iHbPuQSvpTLTqxdP0C1Q+1ntdt0r9zc29W5MXmed0jWyhEPfQw+gK6hJ6UGfqsnaR5RybDxz0YZVPZpOgwbyfjUZEh6xSL+bBYDHzGi/y5mJu8mmdunoWc8QnIdXpQrXnWRF6VJzIUkN6nAflIhAIA8EuPq5ppaurVn5MbhsDKtK9QQ8iFM9vYvtJIRj1Cnjj/4Ksbb/6TQfSvM0fOYyArF3vThdmPX7ilKLde2Cn87voWxlvrpwp/LozjlTjGC4ZtG8qTPyDyMSIQGJS3vtWwDUYPnyIY7FBg/JWHFcM+rWjidHZs90i3D+SNndJ3oOrw/indrNVVcWkDP3d+6zLGl7fOPze7CZuTySbM/W5ztSkNIVwUlXyEP0Cvob0DpgiwD/64wHNPAj8KZWGGgyyfKAUdRknao2lSKCDpQ/Ix3iapLJe0ZJRPyHAQNkHWawqyZH0oliV0LLswIVOYSM3ILaU1YVBsOcjmmLlJXBFAgwSSiwfvIoXHWmnRWAnOpjgL4XfNMyFXmKgtW1xlinfE1lvML5l0fXnoXvr97aeZs8itmpmugKYQsLnhirrgbFEjRjSo+HpDCybR5742XrBj3n3QUAittAdR/WhtrChO4HBDjWhiV5dMZlK93F4JAu61PSte9a2KZmCTVayKzhlRMYms0lo5wrxW/RSMa4stGzChhiq6PjXLq7O7Xs3fI3WdcuGwNn6f1koKlnsebdiVqARlTY1ZPU7a3vb6A7ZKACuawhy99aWhc1hjPNZc1yxToli44J3GlRIvHYk2Hh/JCPi4ay1bfrfu1ip9InxXxQb3adPwBCWgLOpuq1lmFMtxwyPHjG1HngLTbVUQtWG7S4aBsaYqs10eJN16ydZLoeZWFQwFrua/qwSUEEpBDhVF6m+XfEAuIhOV0SKaILQk2zicwCiRg5RBiKIM5QlKGYruK0dqLGey1ccg8EM5LbN8XHBJdjxpc/g1lKzjluNYx01ndoGCvb8PNqWzD/f3Zx/m9O4779ylc39PEFwyLsyBsGWUMBEvmy3zWRmBU3GklzfQ/97g/p3S/80JDXGjeMwNYYTO65ZVzEjJ/b/iX6I1yfyMFYOvXTAw6hXan7NTWh5KysohmRwwsQCEw8WMvO+VFfzHf2x8QnRjunMZcGhd+y5zTH9j8/PbluWDGrZyXVxa9mtC0hbt48t9UMR7wl47ybazHarYj7x76tFXGkcbGMdrR39icd1tf9+xHMu9tmGYf74/x39LfkVOo20ZJGky/wB1Pn4XKUt5yK+SfOEsTbK5kKTgChxnbnFxLpjhfIjLJJLikJWX048daKtTCKvA5RkmUK7oPntWq9i60X2o6vsYjDLFnhss6UIFJap2Lo4D68uU6tfunXti+XvVuubzZ7SqMHSJDzyJjyjxPD/RBFfC2hz+GGPa9b+f+2of/8GRfVF/ZJiStkmnNl2wqFn1mowGK55uPvDo4mofnG+qLul85uuvzt5+xi/9D7xt/l+4o3TOPvVD2JDl+g9+5y00AHicY2BkYGAA4vdhFhPj+W2+MnCzMIDAtbfeSgj6/wIWBuYEIJeDgQkkCgAriQovAHicY2BkYGBu+N/AEMPCAAJAkpEBFXAAAEcOAnF4nGNhYGBgfsnAwMKAHQMAGtcBCQAAAAAAdgDiAYoCogMIA1oD8gAAeJxjYGRgYOBgOMDAxgACTEDMBYQMDP/BfAYAHGwB5QB4nGWPTU7DMBCFX/oHpBKqqGCH5AViASj9EatuWFRq911036ZOmyqJI8et1ANwHo7ACTgC3IA78EgnmzaWx9+8eWNPANzgBx6O3y33kT1cMjtyDRe4F65TfxBukF+Em2jjVbhF/U3YxzOmwm10YXmD17hi9oR3YQ8dfAjXcI1P4Tr1L+EG+Vu4iTv8CrfQ8erCPuZeV7iNRy/2x1YvnF6p5UHFockikzm/gple75KFrdLqnGtbxCZTg6BfSVOdaVvdU+zXQ+ciFVmTqgmrOkmMyq3Z6tAFG+fyUa8XiR6EJuVYY/62xgKOcQWFJQ6MMUIYZIjK6Og7VWb0r7FDwl57Vj3N53RbFNT/c4UBAvTPXFO6stJ5Ok+BPV8bUnV0K27LnpQ0kV7NSRKyQl7WtlRC6gE2ZVeOEXpc0Yk/KGdI/wAJWm7IAAAAeJxtyMEOQDAQBNCZslX+UhE2kbaSbvD3JK7e8cHhM+BfoGPDlkLPjoE9ePlV62ZRzkVjljrdlryVPY+zHJrUxMpbwANFHA6a') format('woff'),
|
||||
url('iconfont.ttf?t=1523001890286') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
|
||||
url('iconfont.svg?t=1523001890286#iconfont') format('svg'); /* iOS 4.1- */
|
||||
}
|
||||
|
||||
[class*=" el-icon-ui"], [class^=el-icon-ui] {
|
||||
font-family:"iconfont" !important;
|
||||
font-size:16px;
|
||||
font-style:normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.el-icon-ui-github:before { content: "\e7ab"; }
|
||||
|
||||
.el-icon-ui-weibo:before { content: "\e61c"; }
|
||||
|
||||
.el-icon-ui-tcyun:before { content: "\e64c"; }
|
||||
|
||||
.el-icon-ui-upload:before { content: "\e61b"; }
|
||||
|
||||
.el-icon-ui-qiniu:before { content: "\e601"; }
|
||||
|
||||
.el-icon-ui-upyun:before { content: "\e602"; }
|
||||
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div id="choose-pic-bed">
|
||||
<span>选择 {{ label }} 作为你默认图床:</span>
|
||||
<el-switch
|
||||
v-model="value"
|
||||
@change="choosePicBed"
|
||||
>
|
||||
</el-switch>
|
||||
</div>
|
||||
</template>
|
||||
<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
|
||||
}
|
||||
}
|
||||
choosePicBed (val: string) {
|
||||
this.saveConfig({
|
||||
'picBed.current': this.type,
|
||||
'picBed.uploader': this.type
|
||||
})
|
||||
this.$emit('update:choosed', this.type)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div id="config-form">
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
:model="ruleForm"
|
||||
ref="form"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item
|
||||
v-for="(item, index) in configList"
|
||||
:label="item.alias || item.name"
|
||||
:required="item.required"
|
||||
:prop="item.name"
|
||||
:key="item.name + index"
|
||||
>
|
||||
<el-input
|
||||
v-if="item.type === 'input' || item.type === 'password'"
|
||||
:type="item.type === 'password' ? 'password' : 'input'"
|
||||
v-model="ruleForm[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
></el-input>
|
||||
<el-select
|
||||
v-else-if="item.type === 'list'"
|
||||
v-model="ruleForm[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:key="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="item.type === 'checkbox'"
|
||||
v-model="ruleForm[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
multiple
|
||||
collapse-tags
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:key="choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<el-switch
|
||||
v-else-if="item.type === 'confirm'"
|
||||
v-model="ruleForm[item.name]"
|
||||
active-text="yes"
|
||||
inactive-text="no"
|
||||
>
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
<slot></slot>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import {
|
||||
Component,
|
||||
Vue,
|
||||
Prop,
|
||||
Watch
|
||||
} from 'vue-property-decorator'
|
||||
import { cloneDeep, union } from 'lodash'
|
||||
|
||||
@Component({
|
||||
name: 'config-form'
|
||||
})
|
||||
export default class extends Vue {
|
||||
@Prop() private config!: any[]
|
||||
@Prop() readonly type!: 'uploader' | 'transformer' | 'plugin'
|
||||
@Prop() readonly id!: string
|
||||
configList = []
|
||||
ruleForm = {}
|
||||
@Watch('config', {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
handleConfigChange (val: any) {
|
||||
this.handleConfig(val)
|
||||
}
|
||||
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
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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 = await this.getConfig<IPicGoPluginConfig>(this.getConfigType())
|
||||
if (val.length > 0 && config) {
|
||||
this.configList = cloneDeep(val).map((item: any) => {
|
||||
let defaultValue = item.default !== undefined
|
||||
? item.default : item.type === 'checkbox'
|
||||
? [] : null
|
||||
if (item.type === 'checkbox') {
|
||||
const defaults = item.choices.filter((i: any) => {
|
||||
return i.checked
|
||||
}).map((i: any) => i.value)
|
||||
defaultValue = union(defaultValue, defaults)
|
||||
}
|
||||
if (config && config[item.name] !== undefined) {
|
||||
defaultValue = config[item.name]
|
||||
}
|
||||
this.$set(this.ruleForm, item.name, defaultValue)
|
||||
return item
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#config-form
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
.el-button-group
|
||||
width 100%
|
||||
.el-button
|
||||
width 50%
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
.el-radio-group
|
||||
margin-left 25px
|
||||
.el-switch__label
|
||||
&.is-active
|
||||
color #409EFF
|
||||
</style>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="inputBoxOptions.title || '输入框'"
|
||||
:visible.sync="showInputBoxVisible"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<el-input
|
||||
v-model="inputBoxValue"
|
||||
:placeholder="inputBoxOptions.placeholder"></el-input>
|
||||
<span slot="footer">
|
||||
<el-button @click="handleInputBoxCancel" round>取消</el-button>
|
||||
<el-button type="primary" @click="handleInputBoxConfirm" round>确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { remote, ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import {
|
||||
SHOW_INPUT_BOX,
|
||||
SHOW_INPUT_BOX_RESPONSE
|
||||
} from '~/universal/events/constants'
|
||||
@Component({
|
||||
name: 'input-box-dialog'
|
||||
})
|
||||
export default class extends Vue {
|
||||
inputBoxValue = ''
|
||||
showInputBoxVisible = false
|
||||
inputBoxOptions = {
|
||||
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)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<div id="main-page">
|
||||
<div class="fake-title-bar" :class="{ 'darwin': os === 'darwin' }">
|
||||
<div class="fake-title-bar__title">
|
||||
PicGo - {{ version }}
|
||||
</div>
|
||||
<div class="handle-bar" v-if="os !== 'darwin'">
|
||||
<i class="el-icon-minus" @click="minimizeWindow"></i>
|
||||
<i class="el-icon-circle-plus-outline" @click="openMiniWindow"></i>
|
||||
<i class="el-icon-close" @click="closeWindow"></i>
|
||||
</div>
|
||||
</div>
|
||||
<el-row style="padding-top: 22px;" class="main-content">
|
||||
<el-col :span="5" class="side-bar-menu">
|
||||
<el-menu
|
||||
class="picgo-sidebar"
|
||||
:default-active="defaultActive"
|
||||
@select="handleSelect"
|
||||
:unique-opened="true"
|
||||
>
|
||||
<el-menu-item index="upload">
|
||||
<i class="el-icon-upload"></i>
|
||||
<span slot="title">上传区</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="gallery">
|
||||
<i class="el-icon-picture"></i>
|
||||
<span slot="title">相册</span>
|
||||
</el-menu-item>
|
||||
<el-submenu
|
||||
index="sub-menu"
|
||||
>
|
||||
<template slot="title">
|
||||
<i class="el-icon-menu"></i>
|
||||
<span>图床设置</span>
|
||||
</template>
|
||||
<template
|
||||
v-for="item in picBed"
|
||||
>
|
||||
<el-menu-item
|
||||
v-if="item.visible"
|
||||
:index="`picbeds-${item.type}`"
|
||||
:key="item.type"
|
||||
>
|
||||
<!-- <i :class="`el-icon-ui-${item.type}`"></i> -->
|
||||
<span slot="title">{{ item.name }}</span>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-submenu>
|
||||
<el-menu-item index="setting">
|
||||
<i class="el-icon-setting"></i>
|
||||
<span slot="title">PicGo设置</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="plugin">
|
||||
<i class="el-icon-share"></i>
|
||||
<span slot="title">插件设置</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
<i class="el-icon-info setting-window" @click="openDialog"></i>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="19"
|
||||
:offset="5"
|
||||
style="height: 428px"
|
||||
class="main-wrapper"
|
||||
:class="{ 'darwin': os === 'darwin' }">
|
||||
<transition name="picgo-fade" mode="out-in">
|
||||
<keep-alive>
|
||||
<router-view v-if="$route.meta.keepAlive"></router-view>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
<transition name="picgo-fade" mode="out-in">
|
||||
<router-view :key="$route.path" v-if="!$route.meta.keepAlive"></router-view>
|
||||
</transition>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog
|
||||
title="赞助PicGo"
|
||||
:visible.sync="visible"
|
||||
width="70%"
|
||||
top="10vh"
|
||||
>
|
||||
PicGo是免费开源的软件,如果你喜欢它,对你有帮助,不妨请我喝杯咖啡?
|
||||
<el-row class="support">
|
||||
<el-col :span="12">
|
||||
<img src="https://user-images.githubusercontent.com/12621342/34188165-e7cdf372-e56f-11e7-8732-1338c88b9bb7.jpg" alt="支付宝">
|
||||
<div class="support-title">支付宝</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<img src="https://user-images.githubusercontent.com/12621342/34188201-212cda84-e570-11e7-9b7a-abb298699d85.jpg" alt="支付宝">
|
||||
<div class="support-title">微信</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
class="qrcode-dialog"
|
||||
top="3vh"
|
||||
width="60%"
|
||||
title="图床配置二维码"
|
||||
:visible.sync="qrcodeVisible"
|
||||
:modal-append-to-body="false"
|
||||
lock-scroll
|
||||
>
|
||||
<el-form
|
||||
label-position="left"
|
||||
label-width="70px"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item
|
||||
label="选择图床"
|
||||
>
|
||||
<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 class="qrcode-container">
|
||||
<qrcode-vue
|
||||
v-show="choosedPicBedForQRCode.length > 0"
|
||||
:size="280"
|
||||
:value="picBedConfigString"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<input-box-dialog />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
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,
|
||||
clipboard
|
||||
} from 'electron'
|
||||
// import db from '#/datastore'
|
||||
import mixin from '@/utils/mixin'
|
||||
import InputBoxDialog from '@/components/InputBoxDialog.vue'
|
||||
import {
|
||||
SHOW_PRIVACY_MESSAGE
|
||||
} from '~/universal/events/constants'
|
||||
import { IConfig } from 'picgo/dist/src/types/index'
|
||||
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()
|
||||
}
|
||||
}
|
||||
@Component({
|
||||
name: 'main-page',
|
||||
mixins: [mixin],
|
||||
components: {
|
||||
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
|
||||
os = ''
|
||||
picBed: IPicBedType[] = []
|
||||
qrcodeVisible = false
|
||||
picBedConfigString = ''
|
||||
choosedPicBedForQRCode: string[] = []
|
||||
created () {
|
||||
this.os = process.platform
|
||||
this.buildMenu()
|
||||
ipcRenderer.send('getPicBeds')
|
||||
ipcRenderer.on('getPicBeds', this.getPicBeds)
|
||||
}
|
||||
|
||||
@Watch('choosedPicBedForQRCode')
|
||||
choosedPicBedForQRCodeChange (val: string[], oldVal: 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleSelect (index: string) {
|
||||
const type = index.match(/picbeds-/)
|
||||
if (type === null) {
|
||||
this.$router.push({
|
||||
name: index
|
||||
})
|
||||
} else {
|
||||
const picBed = index.replace(/picbeds-/, '')
|
||||
if (this.$builtInPicBed.includes(picBed)) {
|
||||
this.$router.push({
|
||||
name: picBed
|
||||
})
|
||||
} else {
|
||||
this.$router.push({
|
||||
name: 'others',
|
||||
params: {
|
||||
type: picBed
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
minimizeWindow () {
|
||||
const window = BrowserWindow.getFocusedWindow()
|
||||
window!.minimize()
|
||||
}
|
||||
closeWindow () {
|
||||
const window = BrowserWindow.getFocusedWindow()
|
||||
if (process.platform === 'linux') {
|
||||
window!.hide()
|
||||
} else {
|
||||
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
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '生成图床配置二维码',
|
||||
click () {
|
||||
_this.qrcodeVisible = true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '隐私协议',
|
||||
click () {
|
||||
ipcRenderer.send(SHOW_PRIVACY_MESSAGE)
|
||||
}
|
||||
}
|
||||
]
|
||||
this.menu = Menu.buildFromTemplate(template)
|
||||
}
|
||||
openDialog () {
|
||||
// this.menu!.popup(remote.getCurrentWindow())
|
||||
this.menu!.popup()
|
||||
}
|
||||
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) {
|
||||
next((vm: this) => {
|
||||
vm.defaultActive = to.name
|
||||
})
|
||||
}
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
$darwinBg = transparentify(#172426, #000, 0.7)
|
||||
.picgo-fade
|
||||
&-enter,
|
||||
&-leave,
|
||||
&-leave-active
|
||||
opacity 0
|
||||
&-enter-active,
|
||||
&-leave-active
|
||||
transition all 100ms linear
|
||||
.view-title
|
||||
color #eee
|
||||
font-size 20px
|
||||
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
|
||||
width 100%
|
||||
text-align center
|
||||
color #eee
|
||||
font-size 12px
|
||||
line-height h
|
||||
position fixed
|
||||
z-index 100
|
||||
&.darwin
|
||||
background transparent
|
||||
background-image linear-gradient(
|
||||
to right,
|
||||
transparent 0%,
|
||||
transparent 167px,
|
||||
$darwinBg 167px,
|
||||
$darwinBg 100%
|
||||
)
|
||||
.fake-title-bar__title
|
||||
padding-left 167px
|
||||
.handle-bar
|
||||
position absolute
|
||||
top 2px
|
||||
right 4px
|
||||
z-index 10000
|
||||
-webkit-app-region no-drag
|
||||
i
|
||||
cursor pointer
|
||||
font-size 16px
|
||||
margin-left 5px
|
||||
.el-icon-minus
|
||||
&:hover
|
||||
color #409EFF
|
||||
.el-icon-close
|
||||
&:hover
|
||||
color #F15140
|
||||
.el-icon-circle-plus-outline
|
||||
&:hover
|
||||
color #69C282
|
||||
.main-wrapper
|
||||
&.darwin
|
||||
background $darwinBg
|
||||
.side-bar-menu
|
||||
position fixed
|
||||
height calc(100vh - 22px)
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
width 170px
|
||||
.el-icon-info.setting-window
|
||||
position fixed
|
||||
bottom 4px
|
||||
left 4px
|
||||
cursor poiter
|
||||
color #878d99
|
||||
transition .2s all ease-in-out
|
||||
&:hover
|
||||
color #409EFF
|
||||
.el-menu
|
||||
border-right none
|
||||
background transparent
|
||||
width 170px
|
||||
&-item
|
||||
color #eee
|
||||
position relative
|
||||
&:focus,
|
||||
&:hover
|
||||
color #fff
|
||||
background transparent
|
||||
&.is-active
|
||||
color active-color = #409EFF
|
||||
&:before
|
||||
content ''
|
||||
position absolute
|
||||
width 3px
|
||||
height 20px
|
||||
right 0
|
||||
top 18px
|
||||
background active-color
|
||||
.el-submenu__title
|
||||
span
|
||||
color #eee
|
||||
&:hover
|
||||
background transparent
|
||||
span
|
||||
color #fff
|
||||
.el-submenu
|
||||
.el-menu-item
|
||||
min-width 166px
|
||||
&.is-active
|
||||
&:before
|
||||
top 16px
|
||||
.main-content
|
||||
padding-top 22px
|
||||
position relative
|
||||
z-index 10
|
||||
.el-dialog__body
|
||||
padding 20px
|
||||
.support
|
||||
text-align center
|
||||
&-title
|
||||
text-align center
|
||||
color #878d99
|
||||
.align-center
|
||||
input
|
||||
text-align center
|
||||
*::-webkit-scrollbar
|
||||
width 8px
|
||||
height 8px
|
||||
*::-webkit-scrollbar-thumb
|
||||
border-radius 4px
|
||||
background #6f6f6f
|
||||
*::-webkit-scrollbar-track
|
||||
background-color transparent
|
||||
</style>
|
||||
@@ -0,0 +1,533 @@
|
||||
<template>
|
||||
<div id="gallery-view">
|
||||
<div class="view-title">
|
||||
相册 - {{ filterList.length }} <i class="el-icon-caret-bottom" @click="toggleHandleBar" :class="{'active': handleBarActive}"></i>
|
||||
</div>
|
||||
<transition name="el-zoom-in-top">
|
||||
<el-row v-show="handleBarActive">
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-row class="handle-bar" :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-select
|
||||
v-model="choosedPicBed"
|
||||
multiple
|
||||
collapse-tags
|
||||
size="mini"
|
||||
style="width: 100%"
|
||||
placeholder="请选择显示的图床">
|
||||
<el-option
|
||||
v-for="item in picBed"
|
||||
:key="item.type"
|
||||
:label="item.name"
|
||||
:value="item.type">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-select
|
||||
v-model="pasteStyle"
|
||||
size="mini"
|
||||
style="width: 100%"
|
||||
@change="handlePasteStyleChange"
|
||||
placeholder="请选择粘贴的格式">
|
||||
<el-option
|
||||
v-for="(value, key) in pasteStyleMap"
|
||||
:key="key"
|
||||
:label="key"
|
||||
:value="value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="handle-bar" :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-input
|
||||
placeholder="搜索"
|
||||
size="mini"
|
||||
v-model="searchText">
|
||||
<i slot="suffix" class="el-input__icon el-icon-close" v-if="searchText" @click="cleanSearch" style="cursor: pointer"></i>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="item-base copy round" :class="{ active: isMultiple(choosedList)}" @click="multiCopy">
|
||||
复制
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="item-base delete round" :class="{ active: isMultiple(choosedList)}" @click="multiRemove">
|
||||
删除
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="item-base all-pick round" :class="{ active: filterList.length > 0}" @click="toggleSelectAll">
|
||||
{{ isAllSelected ? '取消' : '全选' }}
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</transition>
|
||||
<el-row class="gallery-list" :class="{ small: handleBarActive }">
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-row :gutter="16">
|
||||
<gallerys
|
||||
:images="filterList"
|
||||
:index="idx"
|
||||
@close="handleClose"
|
||||
:options="options"
|
||||
></gallerys>
|
||||
<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)"
|
||||
>
|
||||
<img v-lazy="item.imgUrl" class="gallery-list__item-img">
|
||||
</div>
|
||||
<div class="gallery-list__tool-panel">
|
||||
<i class="el-icon-document" @click="copy(item)"></i>
|
||||
<i class="el-icon-edit-outline" @click="openDialog(item)"></i>
|
||||
<i class="el-icon-delete" @click="remove(item.id)"></i>
|
||||
<el-checkbox v-model="choosedList[item.id]" class="pull-right" @change="(val) => handleChooseImage(val, index)"></el-checkbox>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
title="修改图片URL"
|
||||
width="500px"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<el-input v-model="imgInfo.imgUrl"></el-input>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="confirmModify">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
// @ts-ignore
|
||||
import gallerys from 'vue-gallery'
|
||||
import pasteStyle from '#/utils/pasteTemplate'
|
||||
import { IPasteStyle } from '#/types/enum'
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
import { IResult } from '@picgo/store/dist/types'
|
||||
import {
|
||||
ipcRenderer,
|
||||
clipboard,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
@Component({
|
||||
name: 'gallery',
|
||||
components: {
|
||||
gallerys
|
||||
}
|
||||
})
|
||||
export default class extends Vue {
|
||||
images: ImgInfo[] = []
|
||||
idx: null | number = null
|
||||
options = {
|
||||
titleProperty: 'fileName',
|
||||
urlProperty: 'imgUrl',
|
||||
closeOnSlideClick: true
|
||||
}
|
||||
dialogVisible = false
|
||||
imgInfo = {
|
||||
id: '',
|
||||
imgUrl: ''
|
||||
}
|
||||
choosedList: IObjT<boolean> = {}
|
||||
choosedPicBed: string[] = []
|
||||
lastChoosed: number = -1
|
||||
isShiftKeyPress: boolean = false
|
||||
searchText = ''
|
||||
handleBarActive = false
|
||||
pasteStyle = ''
|
||||
pasteStyleMap = {
|
||||
Markdown: 'markdown',
|
||||
HTML: 'HTML',
|
||||
URL: 'URL',
|
||||
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()
|
||||
}
|
||||
}
|
||||
async created () {
|
||||
ipcRenderer.on('updateGallery', (event: IpcRendererEvent) => {
|
||||
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
|
||||
}
|
||||
}
|
||||
get filterList () {
|
||||
return this.getGallery()
|
||||
}
|
||||
get isAllSelected () {
|
||||
const values = Object.values(this.choosedList)
|
||||
if (values.length === 0) {
|
||||
return false
|
||||
} else {
|
||||
return this.filterList.every(item => {
|
||||
return this.choosedList[item.id!]
|
||||
})
|
||||
}
|
||||
}
|
||||
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
|
||||
this.picBed = picBeds
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
for (let i = min + 1; i < max; i++) {
|
||||
const id = this.filterList[i].id!
|
||||
this.$set(this.choosedList, id, true)
|
||||
}
|
||||
}
|
||||
this.lastChoosed = index
|
||||
}
|
||||
}
|
||||
clearChoosedList () {
|
||||
this.isShiftKeyPress = false
|
||||
Object.keys(this.choosedList).forEach(key => {
|
||||
this.choosedList[key] = false
|
||||
})
|
||||
this.lastChoosed = -1
|
||||
}
|
||||
zoomImage (index: number) {
|
||||
this.idx = index
|
||||
this.changeZIndexForGallery(true)
|
||||
}
|
||||
changeZIndexForGallery (isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
// @ts-ignore
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 101
|
||||
} else {
|
||||
// @ts-ignore
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 10
|
||||
}
|
||||
}
|
||||
handleClose () {
|
||||
this.idx = null
|
||||
this.changeZIndexForGallery(false)
|
||||
}
|
||||
async copy (item: ImgInfo) {
|
||||
const style = await this.getConfig<IPasteStyle>('settings.pasteStyle') || IPasteStyle.MARKDOWN
|
||||
const customLink = await this.getConfig<string>('settings.customLink')
|
||||
const copyLink = pasteStyle(style, item, customLink)
|
||||
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(async () => {
|
||||
const file = await this.$$db.getById(id)
|
||||
await this.$$db.removeById(id)
|
||||
ipcRenderer.send('removeFiles', [file])
|
||||
const obj = {
|
||||
title: '操作结果',
|
||||
body: '删除成功'
|
||||
}
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
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
|
||||
}
|
||||
async confirmModify () {
|
||||
await this.$$db.updateById(this.imgInfo.id, {
|
||||
imgUrl: this.imgInfo.imgUrl
|
||||
})
|
||||
const obj = {
|
||||
title: '修改图片URL成功',
|
||||
body: this.imgInfo.imgUrl,
|
||||
icon: this.imgInfo.imgUrl
|
||||
}
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
this.dialogVisible = false
|
||||
this.updateGallery()
|
||||
}
|
||||
choosePicBed (type: string) {
|
||||
let 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
|
||||
if (multiRemoveNumber) {
|
||||
this.$confirm(`将在相册中移除刚才选中的 ${multiRemoveNumber} 张图片,是否继续?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
let 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 = await this.$$db.getById<ImgInfo>(key)
|
||||
if (file) {
|
||||
files.push(file)
|
||||
await this.$$db.removeById(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearChoosedList()
|
||||
this.choosedList = {} // 只有删除才能将这个置空
|
||||
const obj = {
|
||||
title: '操作结果',
|
||||
body: '删除成功'
|
||||
}
|
||||
ipcRenderer.send('removeFiles', files)
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
this.updateGallery()
|
||||
}).catch(() => {
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
async multiCopy () {
|
||||
if (Object.values(this.choosedList).some(item => item)) {
|
||||
const copyString: string[] = []
|
||||
const style = await this.getConfig<IPasteStyle>('settings.pasteStyle') || IPasteStyle.MARKDOWN
|
||||
const customLink = await this.getConfig<string>('settings.customLink')
|
||||
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
|
||||
const imageIDList = Object.keys(this.choosedList)
|
||||
for (let i = 0; i < imageIDList.length; i++) {
|
||||
const key = imageIDList[i]
|
||||
if (this.choosedList[key]) {
|
||||
const item = await this.$$db.getById<ImgInfo>(key)
|
||||
if (item) {
|
||||
copyString.push(pasteStyle(style, item, customLink))
|
||||
this.choosedList[key] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
const obj = {
|
||||
title: '批量复制链接成功',
|
||||
body: copyString.join('\n')
|
||||
}
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
clipboard.writeText(copyString.join('\n'))
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
toggleHandleBar () {
|
||||
this.handleBarActive = !this.handleBarActive
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.view-title
|
||||
color #eee
|
||||
font-size 20px
|
||||
text-align center
|
||||
margin 10px auto
|
||||
.sub-title
|
||||
font-size 14px
|
||||
.el-icon-caret-bottom
|
||||
cursor: pointer
|
||||
transition all .2s ease-in-out
|
||||
&.active
|
||||
transform: rotate(180deg)
|
||||
#gallery-view
|
||||
height 100%
|
||||
.item-base
|
||||
background #2E2E2E
|
||||
text-align center
|
||||
padding 5px 0
|
||||
cursor pointer
|
||||
font-size 13px
|
||||
transition all .2s ease-in-out
|
||||
height: 28px
|
||||
box-sizing: border-box
|
||||
&.copy
|
||||
cursor not-allowed
|
||||
background #49B1F5
|
||||
&.active
|
||||
cursor pointer
|
||||
background #1B9EF3
|
||||
color #fff
|
||||
&.delete
|
||||
cursor not-allowed
|
||||
background #F47466
|
||||
&.active
|
||||
cursor pointer
|
||||
background #F15140
|
||||
color #fff
|
||||
&.all-pick
|
||||
cursor not-allowed
|
||||
background #69C282
|
||||
&.active
|
||||
cursor pointer
|
||||
background #44B363
|
||||
color #fff
|
||||
#gallery-view
|
||||
position relative
|
||||
.round
|
||||
border-radius 14px
|
||||
.pull-right
|
||||
float right
|
||||
.gallery-list
|
||||
height 360px
|
||||
box-sizing border-box
|
||||
padding 8px 0
|
||||
overflow-y auto
|
||||
overflow-x hidden
|
||||
position absolute
|
||||
top: 38px
|
||||
transition all .2s ease-in-out .1s
|
||||
width 100%
|
||||
&.small
|
||||
height: 287px
|
||||
top: 113px
|
||||
&__img
|
||||
height 150px
|
||||
position relative
|
||||
margin-bottom 16px
|
||||
&__item
|
||||
width 100%
|
||||
height 120px
|
||||
transition all .2s ease-in-out
|
||||
cursor pointer
|
||||
margin-bottom 8px
|
||||
overflow hidden
|
||||
display flex
|
||||
&-fake
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
opacity 0
|
||||
width 100%
|
||||
z-index -1
|
||||
&:hover
|
||||
transform scale(1.1)
|
||||
&-img
|
||||
width 100%
|
||||
object-fit fill
|
||||
&__tool-panel
|
||||
color #ddd
|
||||
i
|
||||
cursor pointer
|
||||
transition all .2s ease-in-out
|
||||
&.el-icon-document
|
||||
&:hover
|
||||
color #49B1F5
|
||||
&.el-icon-edit-outline
|
||||
&:hover
|
||||
color #69C282
|
||||
&.el-icon-delete
|
||||
&:hover
|
||||
color #F15140
|
||||
.handle-bar
|
||||
color #ddd
|
||||
margin-bottom 10px
|
||||
.el-input__inner
|
||||
border-radius 14px
|
||||
</style>
|
||||