Compare commits

..
Author SHA1 Message Date
PiEgg 2d3e779e0b 🎉 Release: v2.3.0-beta.8 2021-08-13 20:38:50 +08:00
PiEgg 20d3cf987c 🐛 Fix: settings bug
ISSUES CLOSED: #710
2021-08-13 00:19:43 +08:00
PiEgg ae692632a5 🐛 Fix: upload clipboard images via http should return list
ISSUES CLOSED: #721
2021-08-12 22:17:14 +08:00
PiEgg 019efd354c 🎉 Release: v2.3.0-beta.7 2021-08-01 17:04:21 +08:00
PiEgg 7030f7a764 Feature: finish custom config path
ISSUES CLOSED: #255
2021-08-01 17:02:54 +08:00
PiEgg 96a63ea11a 🐛 Fix: bug of gallery db for plugin 2021-08-01 15:44:59 +08:00
PiEgg f1eb7f4d70 🐛 Fix: gallery db bug 2021-08-01 14:50:25 +08:00
PiEgg 6ddd660d89 Feature: add gallery db 2021-07-27 11:58:24 +08:00
PiEgg c70c3aff78 🚧 WIP: add gallery db 2021-07-27 00:15:11 +08:00
PiEgg 76964ff1a5 🚧 WIP: gallery db in progress 2021-07-25 23:25:36 +08:00
PiEgg 12cecc27e7 🐛 Fix: multiple uploading in the same time will cause rename failed 2021-07-14 23:57:09 +08:00
PiEgg bdf523a060 🐛 Fix: windows ia32 && x64 build options 2021-07-14 23:46:33 +08:00
PiEgg 49e5f343f4 🐛 Fix: enable plugin should reload
ISSUES CLOSED: #659
2021-07-10 19:38:05 +08:00
PiEgg 1657542144 Feature: add win32 support
ISSUES CLOSED: #632
2021-07-10 00:36:31 +08:00
PiEgg 8e5e9ec59a 🔨 Refactor: move guiApi to singleton 2021-07-10 00:25:34 +08:00
PiEgg 06b67e50b9 🐛 Fix: multiple uploading in the same time will cause output conflict
ISSUES CLOSED: #666
2021-05-09 10:52:14 +08:00
PiEgg cf895deb8d 🔨 Refactor: picgo server the way of uploading 2021-05-07 01:04:17 +08:00
PiEgg ab762ef465 🐛 Fix: uploader error in linux
ISSUES CLOSED: #627
2021-04-25 23:29:30 +08:00
PiEgg 92022a6e34 🐛 Fix: use uploader first 2021-04-24 18:16:13 +08:00
PiEgg a312d06b62 🎉 Release: v2.3.0-beta.6 2021-04-24 18:12:46 +08:00
PiEgg a1b70b4cb1 🐛 Fix: disable plugin need reload app 2021-04-24 18:11:50 +08:00
PiEgg 2d9e9c0a00 Feature: add logs for picgo-server
ISSUES CLOSED: #627
2021-04-24 17:56:56 +08:00
PiEgg a657c51bb5 🐛 Fix: dev error with install vue-devtools
ISSUES CLOSED: #653, #658
2021-04-24 12:14:15 +08:00
PiEgg 321e33967c 🐛 Fix: windows cli uploading bug
ISSUES CLOSED: #657
2021-04-24 00:44:49 +08:00
PiEgg 0a986c8933 🐛 Fix: default picBed using picBed.uploader instead of picBed.current 2021-04-21 23:20:21 +08:00
PiEgg 69e1dc8128 📦 Chore: fix eslint error notice in indent 2021-04-16 10:12:11 +08:00
PiEgg b05139fc9a 🐛 Fix: add plugin install failed notice 2021-04-11 11:11:22 +08:00
PiEgg 992ff35936 Feature: add privacy policy 2021-04-10 21:01:55 +08:00
PiEgg 06d40ef7f6 🐛 Fix: fix analytics value 2021-04-05 22:43:23 +08:00
PiEgg f53639153e Feature: add baidu tongji for analytics 2021-04-05 17:47:06 +08:00
PiEgg 3fd6e4e4c8 📦 Chore: add main process hot reload 2021-04-05 16:09:27 +08:00
PiEgg ce8f41516c 🎉 Release: v2.3.0-beta.5 2021-04-04 17:53:41 +08:00
PiEgg f0e1fa12d7 Feature: add local plugin support && npm registry/proxy support 2021-04-04 17:52:50 +08:00
PiEgg 3cad5f378d 🔨 Refactor: upload module && notification module 2021-03-28 17:44:20 +08:00
f9d6191d5e 🔨 Refactor: improve others picbed config ui to support scroll (#635)
Co-authored-by: jiawei <jiawei@parim.net>
2021-03-01 11:25:26 +08:00
PiEgg 51d80a6a1d 🐛 Fix: default github placeholder 2021-01-13 20:28:03 +08:00
刘沛源andGitHub 0fe3ade5c3 Feature: 为Linux系统适配桌面图标栏(Tray) (#603) 2020-12-29 09:59:50 +08:00
PiEgg 2129f86266 🔨 Refactor: plugin name -> fullName to support scope packages 2020-12-26 23:39:48 +08:00
PiEgg 064f37d1a6 📦 Chore: change travis-ci -> GitHub Actions 2020-12-20 11:44:38 +08:00
PiEgg 48592ec9b5 🎉 Release: v2.3.0-beta.4 2020-12-19 20:59:56 +08:00
PiEgg 33fdb16ded 🐛 Fix: disabled plugin won't be shown in plugin page 2020-12-19 20:52:01 +08:00
Huiyi.FYJandGitHub bd2311bf50 📝 Docs: update install command by Homebrew (#599) 2020-12-16 10:59:58 +08:00
PiEgg e793599360 📝 Docs: update discussions in doc 2020-12-09 11:50:00 +08:00
PiEgg ce2b5cdeab 🐛 Fix: url encode before uploading by url
ISSUES CLOSED: #581
2020-11-10 10:47:35 +08:00
偕臧andGitHub 2151857c89 📝 Docs: update README.md (#555)
aurman is easy to cause misunderstanding, but yay is a command that can be run directly (aur's most popular assistant)
2020-09-30 17:39:05 +08:00
PiEgg 32b8b976be Feature(config): auto configuration backup & fallback to avoid main process crash
ISSUES CLOSED: #568
2020-09-30 17:32:35 +08:00
PiEgg eab014dc88 Feature: add global value for PicGo get GUI_VERSION & CORE_VERSION 2020-09-28 19:41:02 +08:00
PiEgg bb90e174f3 📝 Docs: rm gitads 2020-09-02 14:27:20 +08:00
PiEgg 7b5e5ef2ed 🐛 Fix: shortKeyConfig maybe undefined
ISSUES CLOSED: #557
2020-08-22 22:15:46 +08:00
PiEgg 92ff282d2f 📝 Docs: add flutter-picgo 2020-08-02 11:48:22 +08:00
PiEgg 2d42381f47 📝 Docs: add gitads 2020-07-13 19:27:51 +08:00
PiEgg 6e855384e0 🎉 Release: v2.3.0-beta.3 2020-07-12 22:02:58 +08:00
PiEgg 21d3942d2b 🐛 Fix: choose default picBed failure
ISSUES CLOSED: #537
2020-07-12 22:01:37 +08:00
PiEgg 4f0809e6b3 🐛 Fix: shortkey disabled failure
ISSUES CLOSED: #534
2020-07-12 18:19:31 +08:00
PiEgg f40a1bb981 🎉 Release: v2.3.0-beta.2 2020-07-12 15:14:15 +08:00
PiEgg 5f87018af8 🐛 Fix: initialize db bugs 2020-07-12 15:13:21 +08:00
Molunerfinn 7fabc47bd2 Feature: add qrcode for picbeds' config 2020-06-30 16:17:18 +08:00
Molunerfinn db71139f5f 🐛 Fix: encoding the result of picgo-server 2020-06-30 09:53:35 +08:00
Molunerfinn 1c5880ab7c 📝 Docs: update readme 2020-06-28 15:57:36 +08:00
65 changed files with 2249 additions and 947 deletions
+3 -1
View File
@@ -15,7 +15,9 @@ module.exports = {
'plugins': ['@typescript-eslint'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
"indent": "off",
"@typescript-eslint/indent": ["error", 2]
},
parserOptions: {
parser: '@typescript-eslint/parser'
+55
View File
@@ -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 }}
+137
View File
@@ -1,3 +1,140 @@
# :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)
+21 -24
View File
@@ -2,11 +2,11 @@
<img src="https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/New%20LOGO-150.png" alt="">
<h1>PicGo</h1>
<blockquote>图片上传+管理新体验 </blockquote>
<a href="https://github.com/feross/standard">
<a href="https://github.com/Molunerfinn/PicGo/actions">
<img src="https://img.shields.io/badge/code%20style-standard-green.svg?style=flat-square" alt="">
</a>
<a href="https://travis-ci.org/Molunerfinn/PicGo/builds">
<img src="https://img.shields.io/travis/Molunerfinn/PicGo.svg?style=flat-square" alt="">
<a href="https://github.com/Molunerfinn/PicGo/actions">
<img src="https://github.com/Molunerfinn/PicGo/workflows/Build/badge.svg" alt="">
</a>
<a href="https://github.com/Molunerfinn/PicGo/releases">
<img src="https://img.shields.io/github/downloads/Molunerfinn/PicGo/total.svg?style=flat-square" alt="">
@@ -24,43 +24,40 @@
## 应用概述
**PicGo: 一个用于快速上传图片并获取图片URL链接的工具**
**PicGo: 一个用于快速上传图片并获取图片 URL 链接的工具**
PicGo 本体支持如下图床:
- ~~`微博图床` v1.0~~ **微博图床从 2019 年 4 月开始进行防盗链,不建议继续使用**
- `七牛图床` v1.0
- `腾讯云 COS v4\v5版本` v1.1 & v1.5.0
- `腾讯云 COS v4\v5 版本` v1.1 & v1.5.0
- `又拍云` v1.2.0
- `GitHub` v1.5.0
- ~~`SM.MS` v1.5.1~~ **由于官方不再支持V1版本,暂时请使用[smms-user](https://github.com/xlzy520/picgo-plugin-smms-user)插件**
- `SM.MS V2` v2.3.0-beta.0
- `阿里云 OSS` v1.6.0
- `Imgur` v1.6.0
**本体不再增加默认的图床支持。你可以自行开发第三方图床插件。详见 [PicGo-Core](https://picgo.github.io/PicGo-Core-Doc/)**
## 特色功能
- 支持拖拽图片上传
- 支持快捷键上传剪贴板里第一张图片
- WindowsmacOS支持右键图片文件通过菜单上传 (v2.1.0+)
- WindowsmacOS 支持右键图片文件通过菜单上传 (v2.1.0+)
- 上传图片后自动复制链接到剪贴板
- 支持自定义复制到剪贴板的链接格式
- 支持修改快捷键,默认快速上传快捷键:`command+shift+p`macOS| `control+shift+p`Windows\Linux)
- 支持插件系统,已有插件支持Gitee、青云等第三方图床
- 支持插件系统,已有插件支持 Gitee、青云等第三方图床
- 更多第三方插件以及使用了 PicGo 底层的应用可以在 [Awesome-PicGo](https://github.com/PicGo/Awesome-PicGo) 找到。欢迎贡献!
- 支持通过发送HTTP请求调用 PicGo 上传(v2.2.0+)
- 支持通过发送 HTTP 请求调用 PicGo 上传(v2.2.0+)
- 更多功能等你自己去发现,同时也会不断开发新功能
- 开发进度可以查看 [Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度
- 欢迎加入[官方Gitter频道](https://gitter.im/picgo-all/PicGo?utm_source=share-link&utm_medium=link&utm_campaign=share-link)与我交流
- 欢迎加入 [官方讨论区](https://github.com/Molunerfinn/PicGo/discussions) 与我交流
**如果第一次使用,请参考应用[使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html)。遇到问题了还可以看看 [FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md) 以及被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed)。**
**如果第一次使用,请参考应用 [使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html)。遇到问题了还可以看看 [FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md) 以及被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed)。**
## 下载安装
点击此处下载[应用](https://github.com/Molunerfinn/PicGo/releases)。
点击此处下载 [应用](https://github.com/Molunerfinn/PicGo/releases)。
### Windows
@@ -74,14 +71,13 @@ Windows 用户请下载最新版本的 `exe` 文件。
macOS 用户请下载最新版本的 `dmg` 文件。
**还可以使用 `brew cask` 来安装 PicGo: `brew cask install picgo`。感谢 @womeimingzi11 的贡献!**
**还可以使用 [Homebrew](https://brew.sh/) 来安装 PicGo: `brew install picgo --cask`。感谢 @womeimingzi11 的贡献!**
### Linux
Linux用户请下载 `AppImage` 文件。
**如果你是 Arch 类的 Linux 用户,可以直接通过 `aurman -S picgo-appimage` 来安装 PicGo。感谢 @houbaron 的贡献!**
Linux 用户请下载 `AppImage` 文件。
**如果你是 Arch 类的 Linux 用户,可以直接通过 `yay -S picgo-appimage` 来安装 PicGo (若是没有则先安装 yay ,是 aur 仓库助手)。感谢 @houbaron 的贡献!**
## 应用截图
@@ -97,9 +93,9 @@ Linux用户请下载 `AppImage` 文件。
> 如果想学习 Electron-vue 的开发,可以查看我写的系列教程——[Electron-vue 开发实战](https://molunerfinn.com/tags/Electron-vue/)
1. 你需要有 Node、Git环境,了解 npm 的相关知识。
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` 将导致未知错误!**
3. `yarn` 下载依赖。注意如果你没有 `yarn`,请去 [官网](https://classic.yarnpkg.com/en/docs/install) 下载安装后再使用。 **用 `npm install` 将导致未知错误!**
4. Mac 需要有 Xcode 环境,Windows 需要有 VS 环境。
### 开发模式
@@ -111,7 +107,7 @@ ctrl+c # 退出开发模式
npm run electron:serve # 重新进入开发模式
```
**注:Windows开发模式运行之后会在底部任务栏的右下角应用区出现PicGo的应用图标。**
**注:Windows 开发模式运行之后会在底部任务栏的右下角应用区出现 PicGo 的应用图标。**
### 生产模式
@@ -129,7 +125,8 @@ npm run electron:build
## 其他相关
- [vs-picgo](https://github.com/Spades-S/vs-picgo)PicGo 的 VS Code 版。
- [vs-picgo](https://github.com/PicGo/vs-picgo)PicGo 的 VS Code 版。
- [flutter-picgo](https://github.com/PicGo/flutter-picgo)PicGo 的手机版(支持 Android 和 iOS )。
## 赞助
@@ -147,4 +144,4 @@ npm run electron:build
[MIT](http://opensource.org/licenses/MIT)
Copyright (c) 2017 - 2019 Molunerfinn
Copyright (c) 2017 - 2019 Molunerfinn
+2 -1
View File
@@ -26,6 +26,7 @@ install:
build_script:
#- yarn test
- npm run release
- yarn build --win --ia32
- yarn release
test: off
+6 -2
View File
@@ -1,6 +1,6 @@
{
"name": "picgo",
"version": "2.3.0-beta.1",
"version": "2.3.0-beta.8",
"private": true,
"scripts": {
"dev": "vue-cli-service electron:serve",
@@ -34,6 +34,7 @@
]
},
"dependencies": {
"@picgo/store": "^1.0.3",
"axios": "^0.19.0",
"core-js": "^3.3.2",
"element-ui": "^2.13.0",
@@ -42,7 +43,9 @@
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"picgo": "^1.4.10",
"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",
@@ -67,6 +70,7 @@
"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",
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# grab the paths
scriptPath=$(echo $0 | awk '{ print substr( $0, 1, length($0)-6 ) }')"windows10.ps1"
imagePath=$(echo $1 | awk '{ print substr( $0, 1, length($0)-18 ) }')
imageName=$(echo $1 | awk '{ print substr( $0, length($0)-17, length($0) ) }')
# run the powershell script
res=$(powershell.exe -noprofile -noninteractive -nologo -sta -executionpolicy unrestricted -file $(wslpath -w $scriptPath) $(wslpath -w $imagePath)"\\"$imageName)
# note that there is a return symbol in powershell result
noImage=$(echo "no image\r")
# check whether image exists
if [ "$res" = "$noImage" ] ;then
echo "no image"
else
echo $(wslpath -u $res)
fi
+5 -3
View File
@@ -1,7 +1,6 @@
import Vue from 'vue'
import App from './renderer/App.vue'
import router from './renderer/router'
import db from '#/datastore/index'
import ElementUI from 'element-ui'
import { webFrame } from 'electron'
import 'element-ui/lib/theme-chalk/index.css'
@@ -9,6 +8,8 @@ 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)
@@ -16,7 +17,6 @@ webFrame.setLayoutZoomLevelLimits(0, 0)
Vue.config.productionTip = false
Vue.prototype.$builtInPicBed = [
'smms',
'weibo',
'imgur',
'qiniu',
'tcyun',
@@ -24,7 +24,7 @@ Vue.prototype.$builtInPicBed = [
'aliyun',
'github'
]
Vue.prototype.$db = db
Vue.prototype.$$db = db
Vue.prototype.$http = axios
Vue.prototype.$bus = bus
@@ -36,3 +36,5 @@ new Vue({
router,
render: h => h(App)
}).$mount('#app')
initTalkingData()
+16 -8
View File
@@ -4,7 +4,7 @@ import {
} from 'electron'
import logger from '@core/picgo/logger'
import GuiApi from '../../gui'
import db from '#/datastore'
import db from '~/main/apis/core/datastore'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import shortKeyService from './shortKeyService'
import picgo from '@core/picgo'
@@ -24,14 +24,18 @@ class ShortKeyHandler {
const commands = db.get('settings.shortKey') as IShortKeyConfigs
Object.keys(commands)
.filter(item => item.includes('picgo:'))
.map(command => {
.forEach(command => {
const config = commands[command]
globalShortcut.register(config.key, () => {
this.handler(command)
})
// if disabled, don't register #534
if (config.enable) {
globalShortcut.register(config.key, () => {
this.handler(command)
})
}
})
}
private initPluginsShortKey () {
// get enabled plugin
const pluginList = picgo.pluginLoader.getList()
for (let item of pluginList) {
const plugin = picgo.pluginLoader.getPlugin(item)
@@ -46,7 +50,10 @@ class ShortKeyHandler {
const command = `${item}:${cmd.name}`
if (db.has(`settings.shortKey[${command}]`)) {
const commandConfig = db.get(`settings.shortKey.${command}`) as IShortKeyConfig
this.registerShortKey(commandConfig, command, cmd.handle, false)
// if disabled, don't register #534
if (commandConfig.enable) {
this.registerShortKey(commandConfig, command, cmd.handle, false)
}
} else {
this.registerShortKey(cmd, command, cmd.handle, true)
}
@@ -65,6 +72,8 @@ class ShortKeyHandler {
} else {
logger.warn(`${command} do not provide a key to bind`)
}
// if the configfile already had this command
// then writeFlag -> false
if (writeFlag) {
picgo.saveConfig({
[`settings.shortKey.${command}`]: {
@@ -121,8 +130,7 @@ class ShortKeyHandler {
} else if (command.includes('picgo-plugin-')) {
const handler = shortKeyService.getShortKeyHandler(command)
if (handler) {
const guiApi = new GuiApi()
return handler(picgo, guiApi)
return handler(picgo, GuiApi.getInstance())
}
} else {
logger.warn(`can not find command: ${command}`)
+200 -133
View File
@@ -9,47 +9,177 @@ import {
} from 'electron'
import uploader from 'apis/app/uploader'
import getPicBeds from '~/main/utils/getPicBeds'
import db from '#/datastore'
import db, { GalleryDB } from '~/main/apis/core/datastore'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import picgo from '@core/picgo'
import pasteTemplate from '#/utils/pasteTemplate'
import pkg from 'root/package.json'
import { handleCopyUrl } from '~/main/utils/common'
import { privacyManager } from '~/main/utils/privacyManager'
let contextMenu: Menu | null
let menu: Menu | null
let tray: Tray | null
export function createContextMenu () {
const picBeds = getPicBeds()
const submenu = picBeds.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: db.get('picBed.current') === item.type,
click () {
picgo.saveConfig({
'picBed.current': item.type
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
if (process.platform === 'darwin' || process.platform === 'win32') {
const submenu = picBeds.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: db.get('picBed.current') === item.type,
click () {
picgo.saveConfig({
'picBed.current': item.type,
'picBed.uploader': item.type
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
}
}
}
})
contextMenu = Menu.buildFromTemplate([
{
label: '关于',
click () {
dialog.showMessageBox({
title: 'PicGo',
message: 'PicGo',
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
})
})
contextMenu = Menu.buildFromTemplate([
{
label: '关于',
click () {
dialog.showMessageBox({
title: 'PicGo',
message: 'PicGo',
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
})
}
},
{
label: '打开详细窗口',
click () {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
settingWindow!.show()
settingWindow!.focus()
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
},
{
label: '选择默认图床',
type: 'submenu',
// @ts-ignore
submenu
},
// @ts-ignore
{
label: '打开更新助手',
type: 'checkbox',
checked: db.get('settings.showUpdateTip'),
click () {
const value = db.get('settings.showUpdateTip')
db.set('settings.showUpdateTip', !value)
}
},
{
label: '隐私协议',
click () {
privacyManager.show(false)
}
},
{
label: '重启应用',
click () {
app.relaunch()
app.exit(0)
}
},
// @ts-ignore
{
role: 'quit',
label: '退出'
}
},
{
label: '打开详细窗口',
click () {
])
} else if (process.platform === 'linux') {
// TODO 图床选择功能
// 由于在Linux难以像在Mac和Windows上那样在点击时构造ContextMenu
// 暂时取消这个选单,避免引起和设置中启用的图床不一致
// TODO 重启应用功能
// 目前的实现无法正常工作
contextMenu = Menu.buildFromTemplate([
{
label: '打开详细窗口',
click () {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
settingWindow!.show()
settingWindow!.focus()
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
},
// @ts-ignore
{
label: '打开更新助手',
type: 'checkbox',
checked: db.get('settings.showUpdateTip'),
click () {
const value = db.get('settings.showUpdateTip')
db.set('settings.showUpdateTip', !value)
}
},
{
label: '关于应用',
click () {
dialog.showMessageBox({
title: 'PicGo',
message: 'PicGo',
buttons: ['Ok'],
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
})
}
},
// @ts-ignore
{
role: 'quit',
label: '退出'
}
])
}
}
export function createTray () {
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
tray = new Tray(menubarPic)
// click事件在Mac和Windows上可以触发(在Ubuntu上无法触发,Unity不支持)
if (process.platform === 'darwin' || process.platform === 'win32') {
tray.on('right-click', () => {
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
}
createContextMenu()
tray!.popUpContextMenu(contextMenu!)
})
tray.on('click', (event, bounds) => {
if (process.platform === 'darwin') {
toggleWindow(bounds)
setTimeout(() => {
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()
@@ -57,116 +187,53 @@ export function createContextMenu () {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
},
{
label: '选择默认图床',
type: 'submenu',
// @ts-ignore
submenu
},
// @ts-ignore
{
label: '打开更新助手',
type: 'checkbox',
checked: db.get('settings.showUpdateTip'),
click () {
const value = db.get('settings.showUpdateTip')
db.set('settings.showUpdateTip', !value)
}
},
{
label: '重启应用',
click () {
app.relaunch()
app.exit(0)
}
},
// @ts-ignore
{
role: 'quit',
label: '退出'
}
])
}
})
export function createTray () {
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
tray = new Tray(menubarPic)
tray.on('right-click', () => {
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
}
createContextMenu()
tray!.popUpContextMenu(contextMenu!)
})
tray.on('click', (event, bounds) => {
if (process.platform === 'darwin') {
toggleWindow(bounds)
setTimeout(() => {
let img = clipboard.readImage()
let obj: ImgInfo[] = []
if (!img.isEmpty()) {
// 从剪贴板来的图片默认转为png
// @ts-ignore
const imgUrl = 'data:image/png;base64,' + Buffer.from(img.toPNG(), 'binary').toString('base64')
obj.push({
width: img.getSize().width,
height: img.getSize().height,
imgUrl
tray.on('drag-enter', () => {
if (systemPreferences.isDarkMode()) {
tray!.setImage(`${__static}/upload-dark.png`)
} else {
tray!.setImage(`${__static}/upload.png`)
}
})
tray.on('drag-end', () => {
tray!.setImage(`${__static}/menubar.png`)
})
// drop-files only be supported in macOS
// so the tray window must be available
tray.on('drop-files', async (event: Event, files: string[]) => {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
const imgs = await uploader
.setWebContents(trayWindow.webContents)
.upload(files)
if (imgs !== false) {
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl!,
icon: files[i]
})
setTimeout(() => {
notification.show()
}, i * 100)
await GalleryDB.getInstance().insert(imgs[i])
}
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('clipboardFiles', obj)
}, 0)
} else {
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
handleCopyUrl(pasteText.join('\n'))
trayWindow.webContents.send('dragFiles', imgs)
}
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
settingWindow!.show()
settingWindow!.focus()
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
})
tray.on('drag-enter', () => {
if (systemPreferences.isDarkMode()) {
tray!.setImage(`${__static}/upload-dark.png`)
} else {
tray!.setImage(`${__static}/upload.png`)
}
})
tray.on('drag-end', () => {
tray!.setImage(`${__static}/menubar.png`)
})
tray.on('drop-files', async (event: Event, files: string[]) => {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
const imgs = await uploader
.setWebContents(trayWindow.webContents)
.upload(files)
if (imgs !== false) {
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[i]))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl!,
icon: files[i]
})
setTimeout(() => {
notification.show()
}, i * 100)
db.insert('uploaded', imgs[i])
}
handleCopyUrl(pasteText.join('\n'))
trayWindow.webContents.send('dragFiles', imgs)
}
})
// toggleWindow()
})
// toggleWindow()
} else if (process.platform === 'linux') {
// click事件在Ubuntu上无法触发,Unity不支持(在Mac和Windows上可以触发)
// 需要使用 setContextMenu 设置菜单
createContextMenu()
tray!.setContextMenu(contextMenu)
}
}
export function createMenu () {
+14 -11
View File
@@ -6,29 +6,31 @@ import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import uploader from '.'
import pasteTemplate from '#/utils/pasteTemplate'
import db from '#/datastore'
import 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 trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
handleCopyUrl(pasteTemplate(pasteStyle, img[0]))
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl!,
icon: img[0].imgUrl
})
notification.show()
db.insert('uploaded', img[0])
trayWindow.webContents.send('clipboardFiles', [])
trayWindow.webContents.send('uploadFiles', img)
await GalleryDB.getInstance().insert(img[0])
// trayWindow just be created in mac/windows, not in linux
trayWindow?.webContents?.send('clipboardFiles', [])
trayWindow?.webContents?.send('uploadFiles', img)
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
}
return img[0].imgUrl as string
return handleUrlEncode(img[0].imgUrl as string)
} else {
const notification = new Notification({
title: '上传不成功',
@@ -50,7 +52,7 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[i]))
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl!,
@@ -59,11 +61,12 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
setTimeout(() => {
notification.show()
}, i * 100)
db.insert('uploaded', imgs[i])
result.push(imgs[i].imgUrl!)
await GalleryDB.getInstance().insert(imgs[i])
result.push(handleUrlEncode(imgs[i].imgUrl!))
}
handleCopyUrl(pasteText.join('\n'))
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('uploadFiles', imgs)
// trayWindow just be created in mac/windows, not in linux
windowManager.get(IWindowList.TRAY_WINDOW)?.webContents?.send('uploadFiles', imgs)
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
}
+51 -28
View File
@@ -1,5 +1,4 @@
import {
app,
Notification,
BrowserWindow,
ipcMain,
@@ -7,12 +6,17 @@ import {
} from 'electron'
import dayjs from 'dayjs'
import picgo from '@core/picgo'
import db from '#/datastore'
import db from '~/main/apis/core/datastore'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import util from 'util'
import { IPicGo } from 'picgo/dist/src/types'
import { showNotification, calcDurationRange } from '~/main/utils/common'
import { TALKING_DATA_EVENT } from '~/universal/events/constants'
import logger from '@core/picgo/logger'
const waitForShow = (webcontent: WebContents) => {
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
webcontent.on('did-finish-load', () => {
resolve()
})
@@ -34,8 +38,23 @@ const waitForRename = (window: BrowserWindow, id: number): Promise<string|null>
})
}
const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) => {
const data: ITalkingDataOptions = {
EventId: 'upload',
Label: options.type,
MapKv: {
by: options.fromClipboard ? 'clipboard' : 'files', // 上传剪贴板图片还是选择的文文件
count: options.count, // 上传的数量
duration: calcDurationRange(options.duration || 0), // 上传耗时
type: options.type
}
}
webContents.send(TALKING_DATA_EVENT, data)
}
class Uploader {
private webContents: WebContents | null = null
// private uploading: boolean = false
constructor () {
this.init()
}
@@ -59,7 +78,7 @@ class Uploader {
}
})
picgo.helper.beforeUploadPlugins.register('renameFn', {
handle: async ctx => {
handle: async (ctx: IPicGo) => {
const rename = db.get('settings.rename')
const autoRename = db.get('settings.autoRename')
if (autoRename || rename) {
@@ -67,7 +86,7 @@ class Uploader {
let name: undefined | string | null
let fileName: string | undefined
if (autoRename) {
fileName = dayjs().add(index, 'second').format('YYYYMMDDHHmmss') + item.extname
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS') + item.extname
} else {
fileName = item.fileName
}
@@ -89,30 +108,34 @@ class Uploader {
return this
}
upload (img?: IUploadOption): Promise<ImgInfo[]|false> {
picgo.upload(img)
return new Promise((resolve) => {
picgo.once('finished', ctx => {
if (ctx.output.every((item: ImgInfo) => item.imgUrl)) {
resolve(ctx.output)
} else {
resolve(false)
async upload (img?: IUploadOption): Promise<ImgInfo[]|false> {
try {
const startTime = Date.now()
const output = await picgo.upload(img)
if (Array.isArray(output) && output.some((item: ImgInfo) => item.imgUrl)) {
if (this.webContents) {
handleTalkingData(this.webContents, {
fromClipboard: !img,
type: db.get('picBed.uploader') || db.get('picBed.current') || 'smms',
count: img ? img.length : 1,
duration: Date.now() - startTime
} as IAnalyticsData)
}
picgo.removeAllListeners('failed')
})
picgo.once('failed', ctx => {
setTimeout(() => {
const notification = new Notification({
title: '上传失败',
body: '请检查配置和上传的文件是否符合要求'
})
notification.show()
}, 500)
picgo.removeAllListeners('finished')
resolve(false)
})
})
return output.filter(item => item.imgUrl)
} else {
return false
}
} catch (e) {
logger.error(e)
setTimeout(() => {
showNotification({
title: '上传失败',
body: util.format(e.stack),
clickToCopy: true
})
}, 500)
return false
}
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ import {
import { IWindowListItem } from '#/types/electron'
import bus from '@core/bus'
import { CREATE_APP_MENU } from '@core/bus/constants'
import db from '#/datastore'
import db from '~/main/apis/core/datastore'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { app } from 'electron'
+132
View File
@@ -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
}
@@ -2,34 +2,34 @@ import Datastore from 'lowdb'
// @ts-ignore
import LodashId from 'lodash-id'
import FileSync from 'lowdb/adapters/FileSync'
import path from 'path'
import fs from 'fs-extra'
import { remote, app } from 'electron'
import { dbPathChecker, dbPathDir, getGalleryDBPath } from './dbChecker'
import { DBStore } from '@picgo/store'
const APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData')
const STORE_PATH = dbPathDir()
if (process.type !== 'renderer') {
if (!fs.pathExistsSync(STORE_PATH)) {
fs.mkdirpSync(STORE_PATH)
}
if (!fs.pathExistsSync(STORE_PATH)) {
fs.mkdirpSync(STORE_PATH)
}
const CONFIG_PATH: string = dbPathChecker()
const DB_PATH: string = getGalleryDBPath().dbPath
class DB {
// TODO: use JSONStore with @picgo/store
class ConfigStore {
private db: Datastore.LowdbSync<Datastore.AdapterSync>
constructor () {
const adapter = new FileSync(path.join(STORE_PATH, '/data.json'))
const adapter = new FileSync(CONFIG_PATH)
this.db = Datastore(adapter)
this.db._.mixin(LodashId)
if (!this.db.has('uploaded').value()) {
this.db.set('uploaded', []).write()
}
if (!this.db.has('picBed').value()) {
this.db.set('picBed', {
current: 'weibo'
current: 'smms', // deprecated
uploader: 'smms',
smms: {
token: ''
}
}).write()
}
@@ -69,6 +69,27 @@ class DB {
// @ts-ignore
return this.read().get(key).removeById(id).write()
}
getConfigPath () {
return CONFIG_PATH
}
}
export default new DB()
export default new ConfigStore()
// v2.3.0 add gallery db
class GalleryDB {
private static instance: DBStore
private constructor () {
console.log('init gallery db')
}
public static getInstance (): DBStore {
if (!GalleryDB.instance) {
GalleryDB.instance = new DBStore(DB_PATH, 'gallery')
}
return GalleryDB.instance
}
}
export {
GalleryDB
}
+8 -6
View File
@@ -1,14 +1,13 @@
import PicGoCore from '~/universal/types/picgo'
import {
app
} from 'electron'
import path from 'path'
import { dbChecker, dbPathChecker } from 'apis/core/datastore/dbChecker'
import pkg from 'root/package.json'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo') as typeof PicGoCore
const STORE_PATH = app.getPath('userData')
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
const CONFIG_PATH = dbPathChecker()
dbChecker()
const picgo = new PicGo(CONFIG_PATH)
picgo.saveConfig({
@@ -16,4 +15,7 @@ picgo.saveConfig({
PICGO_ENV: 'GUI'
})
global.PICGO_GUI_VERSION = pkg.version
picgo.GUI_VERSION = global.PICGO_GUI_VERSION
export default picgo! as PicGoCore
+60 -4
View File
@@ -4,7 +4,9 @@ import {
Notification,
ipcMain
} from 'electron'
import db from '#/datastore'
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'
@@ -15,11 +17,23 @@ import {
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)
@@ -27,7 +41,7 @@ class GuiApi implements IGuiApi {
return true
}
settingWindow.show()
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000) // TODO: a better way to wait page loaded.
@@ -69,7 +83,7 @@ class GuiApi implements IGuiApi {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[i]))
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl as string,
@@ -78,7 +92,7 @@ class GuiApi implements IGuiApi {
setTimeout(() => {
notification.show()
}, i * 100)
db.insert('uploaded', imgs[i])
await GalleryDB.getInstance().insert(imgs[i])
}
handleCopyUrl(pasteText.join('\n'))
webContents.send('uploadFiles', imgs)
@@ -118,6 +132,48 @@ class GuiApi implements IGuiApi {
})
})
}
/**
* get picgo config/data path
*/
async getConfigPath () {
const currentConfigPath = dbPathChecker()
const galleryDBPath = getGalleryDBPath().dbPath
return {
defaultConfigPath,
currentConfigPath,
galleryDBPath
}
}
get galleryDB (): DBStore {
return new Proxy<DBStore>(GalleryDB.getInstance(), {
get (target, prop: keyof DBStore) {
if (prop === 'removeById') {
return new Proxy(GalleryDB.getInstance().removeById, {
apply (target, ctx, args) {
return new Promise((resolve) => {
const guiApi = GuiApi.getInstance()
guiApi.showMessageBox({
title: '警告',
message: '有插件正在试图删除一些相册图片,是否继续',
type: 'info',
buttons: ['Yes', 'No']
}).then(res => {
if (res.result === 0) {
resolve(Reflect.apply(target, ctx, args))
} else {
resolve(undefined)
}
})
})
}
})
}
return Reflect.get(target, prop)
}
})
}
}
export default GuiApi
+6 -4
View File
@@ -8,12 +8,14 @@ import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import uploader from 'apis/app/uploader'
import pasteTemplate from '#/utils/pasteTemplate'
import db from '#/datastore'
import db, { GalleryDB } from '~/main/apis/core/datastore'
import server from '~/main/server'
import getPicBeds from '~/main/utils/getPicBeds'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import bus from '@core/bus'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import {
TOGGLE_SHORTKEY_MODIFIED_MODE
} from '#/events/constants'
import {
uploadClipboardFiles,
uploadChoosedFiles
@@ -30,7 +32,7 @@ export default {
const img = await uploader.setWebContents(trayWindow.webContents).upload()
if (img !== false) {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
handleCopyUrl(pasteTemplate(pasteStyle, img[0]))
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl!,
@@ -38,7 +40,7 @@ export default {
icon: img[0].imgUrl
})
notification.show()
db.insert('uploaded', img[0])
await GalleryDB.getInstance().insert(img[0])
trayWindow.webContents.send('clipboardFiles', [])
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
+205 -96
View File
@@ -4,18 +4,37 @@ import {
dialog,
shell,
IpcMainEvent,
ipcMain,
app
ipcMain
} from 'electron'
import PicGoCore from '~/universal/types/picgo'
import { IPicGoHelperType } from '#/types/enum'
import shortKeyHandler from '../apis/app/shortKey/shortKeyHandler'
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 = app.getPath('userData')
const STORE_PATH = path.dirname(dbPathChecker())
// const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
type PicGoNotice = {
@@ -56,109 +75,119 @@ const handleConfigWithFunction = (config: any[]) => {
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 pluginList = picgo.pluginLoader.getList()
const list = []
for (let i in pluginList) {
const plugin = picgo.pluginLoader.getPlugin(pluginList[i])
const pluginPath = path.join(STORE_PATH, `/node_modules/${pluginList[i]}`)
const pluginPKG = requireFunc(path.join(pluginPath, 'package.json'))
const uploaderName = plugin.uploader || ''
const transformerName = plugin.transformer || ''
let menu = []
if (plugin.guiMenu) {
menu = plugin.guiMenu(picgo)
}
let gui = false
if (pluginPKG.keywords && pluginPKG.keywords.length > 0) {
if (pluginPKG.keywords.includes('picgo-gui-plugin')) {
gui = true
}
}
const obj: IPicGoPlugin = {
name: pluginList[i].replace(/picgo-plugin-/, ''),
author: pluginPKG.author.name || pluginPKG.author,
description: pluginPKG.description,
logo: 'file://' + path.join(pluginPath, 'logo.png').split(path.sep).join('/'),
version: pluginPKG.version,
gui,
config: {
plugin: {
name: pluginList[i].replace(/picgo-plugin-/, ''),
config: plugin.config ? handleConfigWithFunction(plugin.config(picgo)) : []
},
uploader: {
name: uploaderName,
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.uploader, picgo))
},
transformer: {
name: transformerName,
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.transformer, picgo))
}
},
enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`),
homepage: pluginPKG.homepage ? pluginPKG.homepage : '',
guiMenu: menu,
ing: false
}
list.push(obj)
}
const list = getPluginList()
event.sender.send('pluginList', list)
picgo.cmd.program.removeAllListeners()
})
}
const handlePluginInstall = () => {
ipcMain.on('installPlugin', async (event: IpcMainEvent, msg: string) => {
ipcMain.on('installPlugin', async (event: IpcMainEvent, fullName: string) => {
const dispose = handleNPMError()
picgo.once('installSuccess', (notice: PicGoNotice) => {
event.sender.send('installSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
shortKeyHandler.registerPluginShortKey(notice.body[0])
picgo.removeAllListeners('installFailed')
dispose()
const res = await picgo.pluginHandler.install([fullName])
event.sender.send('installPlugin', {
success: res.success,
body: fullName,
errMsg: res.success ? '' : res.body
})
picgo.once('installFailed', () => {
picgo.removeAllListeners('installSuccess')
dispose()
})
await picgo.pluginHandler.install([msg])
picgo.cmd.program.removeAllListeners()
if (res.success) {
shortKeyHandler.registerPluginShortKey(res.body[0])
} else {
showNotification({
title: '插件安装失败',
body: res.body as string
})
}
event.sender.send('hideLoading')
dispose()
})
}
const handlePluginUninstall = () => {
ipcMain.on('uninstallPlugin', async (event: IpcMainEvent, msg: string) => {
const dispose = handleNPMError()
picgo.once('uninstallSuccess', (notice: PicGoNotice) => {
event.sender.send('uninstallSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
shortKeyHandler.unregisterPluginShortKey(notice.body[0])
picgo.removeAllListeners('uninstallFailed')
dispose()
})
picgo.once('uninstallFailed', () => {
picgo.removeAllListeners('uninstallSuccess')
dispose()
})
await picgo.pluginHandler.uninstall([msg])
picgo.cmd.program.removeAllListeners()
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()
picgo.once('updateSuccess', (notice: { body: string[], title: string }) => {
event.sender.send('updateSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
picgo.removeAllListeners('updateFailed')
dispose()
})
picgo.once('updateFailed', () => {
picgo.removeAllListeners('updateSuccess')
dispose()
})
await picgo.pluginHandler.update([msg])
picgo.cmd.program.removeAllListeners()
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()
})
}
@@ -189,19 +218,17 @@ const handleGetPicBedConfig = () => {
} else {
event.sender.send('getPicBedConfig', [], name)
}
picgo.cmd.program.removeAllListeners()
})
}
const handlePluginActions = () => {
ipcMain.on('pluginActions', (event: IpcMainEvent, name: string, label: string) => {
const plugin = picgo.pluginLoader.getPlugin(`picgo-plugin-${name}`)
const guiApi = new GuiApi()
if (plugin.guiMenu && plugin.guiMenu(picgo).length > 0) {
const plugin = picgo.pluginLoader.getPlugin(name)
if (plugin?.guiMenu?.(picgo)?.length) {
const menu: GuiMenuItem[] = plugin.guiMenu(picgo)
menu.forEach(item => {
if (item.label === label) {
item.handle(picgo, guiApi)
item.handle(picgo, GuiApi.getInstance())
}
})
}
@@ -210,19 +237,97 @@ const handlePluginActions = () => {
const handleRemoveFiles = () => {
ipcMain.on('removeFiles', (event: IpcMainEvent, files: ImgInfo[]) => {
const guiApi = new GuiApi()
setTimeout(() => {
picgo.emit('remove', files, guiApi)
picgo.emit('remove', files, GuiApi.getInstance())
}, 500)
})
}
const handlePicGoSaveData = () => {
ipcMain.on('picgoSaveData', (event: IpcMainEvent, data: IObj) => {
const handlePicGoSaveConfig = () => {
ipcMain.on(PICGO_SAVE_CONFIG, (event: IpcMainEvent, data: IObj) => {
picgo.saveConfig(data)
})
}
const handlePicGoGetConfig = () => {
ipcMain.on(PICGO_GET_CONFIG, (event: IpcMainEvent, key: string | undefined, callbackId: string) => {
const result = picgo.getConfig(key)
event.sender.send(PICGO_GET_CONFIG, result, callbackId)
})
}
const handleImportLocalPlugin = () => {
ipcMain.on('importLocalPlugin', (event: IpcMainEvent) => {
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()
@@ -232,6 +337,10 @@ export default {
handleGetPicBedConfig()
handlePluginActions()
handleRemoveFiles()
handlePicGoSaveData()
handlePicGoSaveConfig()
handlePicGoGetConfig()
handlePicGoGalleryDB()
handleImportLocalPlugin()
handleOpenFile()
}
}
+33
View File
@@ -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)
})
+58 -33
View File
@@ -1,12 +1,14 @@
import './errorHandler'
import {
app,
globalShortcut,
protocol
protocol,
Notification
} from 'electron'
import {
createProtocol,
installVueDevtools
createProtocol
} from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import beforeOpen from '~/main/utils/beforeOpen'
import fixPath from 'fix-path'
import ipcList from '~/main/events/ipcList'
@@ -14,7 +16,8 @@ import busEventList from '~/main/events/busEventList'
import { IWindowList } from 'apis/app/window/constants'
import windowManager from 'apis/app/window/windowManager'
import {
updateShortKeyFromVersion212
updateShortKeyFromVersion212,
migrateGalleryFromVersion230
} from '~/main/migrate'
import {
uploadChoosedFiles,
@@ -27,12 +30,33 @@ import server from '~/main/server/index'
import updateChecker from '~/main/utils/updateChecker'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import { getUploadFiles } from '~/main/utils/handleArgv'
import db from '#/datastore'
import db, { GalleryDB } from '~/main/apis/core/datastore'
import bus from '@core/bus'
import { privacyManager } from '~/main/utils/privacyManager'
import logger from 'apis/core/picgo/logger'
import picgo from 'apis/core/picgo'
const isDevelopment = process.env.NODE_ENV !== 'production'
const handleStartUpFiles = (argv: string[], cwd: string) => {
const files = getUploadFiles(argv, cwd, logger)
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动
if (files === null) {
logger.info('cli -> uploading file from clipboard')
uploadClipboardFiles()
} else {
logger.info('cli -> uploading files from cli', ...files.map(item => item.path))
const win = windowManager.getAvailableWindow()
uploadChoosedFiles(win.webContents, files)
}
return true
} else {
return false
}
}
class LifeCycle {
private beforeReady () {
private async beforeReady () {
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
// fix the $PATH in macOS
fixPath()
@@ -40,23 +64,27 @@ class LifeCycle {
ipcList.listen()
busEventList.listen()
updateShortKeyFromVersion212(db, db.get('settings.shortKey'))
await migrateGalleryFromVersion230(db, GalleryDB.getInstance(), picgo)
}
private onReady () {
app.on('ready', async () => {
const readyFunction = async () => {
console.log('on ready')
createProtocol('picgo')
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installVueDevtools()
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)
if (process.platform === 'darwin' || process.platform === 'win32') {
createTray()
}
createTray()
db.set('needReload', false)
updateChecker()
// 不需要阻塞
@@ -65,29 +93,28 @@ class LifeCycle {
})
server.startup()
if (process.env.NODE_ENV !== 'development') {
let files = getUploadFiles()
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动
if (files === null) {
uploadClipboardFiles()
} else {
const win = windowManager.getAvailableWindow()
uploadChoosedFiles(win.webContents, files)
}
handleStartUpFiles(process.argv, process.cwd())
}
if (global.notificationList?.length > 0) {
while (global.notificationList.length) {
const option = global.notificationList.pop()
const notice = new Notification(option!)
notice.show()
}
}
})
}
if (!app.isReady()) {
app.on('ready', readyFunction)
} else {
readyFunction()
}
}
private onRunning () {
app.on('second-instance', (event, commandLine, workingDirectory) => {
let files = getUploadFiles(commandLine, workingDirectory)
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动
if (files === null) {
uploadClipboardFiles()
} else {
const win = windowManager.getAvailableWindow()
uploadChoosedFiles(win.webContents, files)
}
} else {
logger.info('detect second instance')
const result = handleStartUpFiles(commandLine, workingDirectory)
if (!result) {
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
if (settingWindow.isMinimized()) {
@@ -135,23 +162,21 @@ class LifeCycle {
process.on('message', data => {
if (data === 'graceful-exit') {
app.quit()
server.shutdown()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
server.shutdown()
})
}
}
}
launchApp () {
async launchApp () {
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
this.beforeReady()
await this.beforeReady()
this.onReady()
this.onRunning()
this.onQuit()
+38 -10
View File
@@ -1,9 +1,22 @@
import DB from '#/datastore'
import { DBStore } from '@picgo/store'
import ConfigStore from '~/main/apis/core/datastore'
import path from 'path'
import fse from 'fs-extra'
import PicGoCore from '#/types/picgo'
// from v2.1.2
const updateShortKeyFromVersion212 = (db: typeof DB, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
let needUpgrade = false
const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
// #557 极端情况可能会出现配置不存在,需要重新写入
if (shortKeyConfig === undefined) {
const defaultShortKeyConfig = {
enable: true,
key: 'CommandOrControl+Shift+P',
name: 'upload',
label: '快捷上传'
}
db.set('settings.shortKey[picgo:upload]', defaultShortKeyConfig)
return true
}
if (shortKeyConfig.upload) {
needUpgrade = true
// @ts-ignore
shortKeyConfig['picgo:upload'] = {
enable: true,
@@ -11,16 +24,31 @@ const updateShortKeyFromVersion212 = (db: typeof DB, shortKeyConfig: IShortKeyCo
name: 'upload',
label: '快捷上传'
}
// @ts-ignore
delete shortKeyConfig.upload
}
if (needUpgrade) {
db.set('settings.shortKey', shortKeyConfig)
return shortKeyConfig
} else {
return false
return true
}
return false
}
const migrateGalleryFromVersion230 = async (configDB: typeof ConfigStore, galleryDB: DBStore, picgo: PicGoCore) => {
const originGallery: ImgInfo[] = configDB.get('uploaded')
const configPath = configDB.getConfigPath()
const configBakPath = path.join(path.dirname(configPath), 'config.bak.json')
// migrate gallery from config to gallery db
if (originGallery && originGallery?.length > 0) {
if (fse.existsSync(configBakPath)) {
fse.copyFileSync(configPath, configBakPath)
}
await galleryDB.insertMany(originGallery)
picgo.saveConfig({
uploaded: []
})
}
}
export {
updateShortKeyFromVersion212
updateShortKeyFromVersion212,
migrateGalleryFromVersion230
}
+5
View File
@@ -39,6 +39,7 @@ class Server {
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,
@@ -57,6 +58,7 @@ class Server {
try {
postObj = (body === '') ? {} : JSON.parse(body)
} catch (err) {
logger.error(`[PicGo Server]`, err)
return handleResponse({
response,
body: {
@@ -65,6 +67,7 @@ class Server {
}
})
}
logger.info(`[PicGo Server] get the request`, body)
const handler = routers.getHandler(request.url!)
handler!({
...postObj,
@@ -73,6 +76,7 @@ class Server {
})
}
} else {
logger.warn(`[PicGo Server] don't support [${request.method}] method`)
response.statusCode = 404
response.end()
}
@@ -99,6 +103,7 @@ class Server {
})
}
startup () {
console.log('startup', this.config.enable)
if (this.config.enable) {
this.listen(this.config.port)
}
+28 -13
View File
@@ -1,12 +1,16 @@
import router from './router'
import {
uploadWithClipboardFiles,
uploadWithFiles
} from '@core/bus/apis'
import {
handleResponse
} from './utils'
import logger from '@core/picgo/logger'
import windowManager from 'apis/app/window/windowManager'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import path from 'path'
import { dbPathDir } from 'apis/core/datastore/dbChecker'
const STORE_PATH = dbPathDir()
const LOG_PATH = path.join(STORE_PATH, 'picgo.log')
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
router.post('/upload', async ({
response,
@@ -18,39 +22,50 @@ router.post('/upload', async ({
try {
if (list.length === 0) {
// upload with clipboard
const res = await uploadWithClipboardFiles()
if (res.success) {
logger.info('[PicGo Server] upload clipboard file')
const res = await uploadClipboardFiles()
if (res) {
handleResponse({
response,
body: {
success: true,
result: res.result
result: [res]
}
})
} else {
handleResponse({
response
response,
body: {
success: false,
message: errorMessage
}
})
}
} else {
logger.info('[PicGo Server] upload files in list')
// upload with files
const pathList = list.map(item => {
return {
path: item
}
})
const res = await uploadWithFiles(pathList)
if (res.success) {
const win = windowManager.getAvailableWindow()
const res = await uploadChoosedFiles(win.webContents, pathList)
if (res.length) {
handleResponse({
response,
body: {
success: true,
result: res.result
result: res
}
})
} else {
handleResponse({
response
response,
body: {
success: false,
message: errorMessage
}
})
}
}
@@ -60,7 +75,7 @@ router.post('/upload', async ({
response,
body: {
success: false,
message: err
message: errorMessage
}
})
}
+5
View File
@@ -1,3 +1,5 @@
import logger from '@core/picgo/logger'
export const handleResponse = ({
response,
statusCode = 200,
@@ -13,6 +15,9 @@ export const handleResponse = ({
header?: IObj,
body?: any
}) => {
if (body?.success === false) {
logger.warn('[PicGo Server] upload failed, see picgo.log for more detail ↑')
}
response.writeHead(statusCode, header)
response.write(JSON.stringify(body))
response.end()
+15 -9
View File
@@ -1,10 +1,10 @@
import fs from 'fs-extra'
import path from 'path'
import os from 'os'
import { remote, app } from 'electron'
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
const APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData')
const configPath = dbPathChecker()
const CONFIG_DIR = path.dirname(configPath)
function beforeOpen () {
if (process.platform === 'darwin') {
@@ -34,7 +34,7 @@ function resolveMacWorkFlow () {
*/
function resolveClipboardImageGenerator () {
let clipboardFiles = getClipboardFiles()
if (!fs.pathExistsSync(path.join(STORE_PATH, 'windows10.ps1'))) {
if (!fs.pathExistsSync(path.join(CONFIG_DIR, 'windows10.ps1'))) {
clipboardFiles.forEach(item => {
fs.copyFileSync(item.origin, item.dest)
})
@@ -45,10 +45,15 @@ function resolveClipboardImageGenerator () {
}
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
let file1 = fs.readFileSync(filePath1)
let file2 = fs.readFileSync(filePath2)
try {
let file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
let file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
if (!file1.equals(file2)) {
if (!file1 || !file2 || !file1.equals(file2)) {
fs.copyFileSync(filePath1, filePath2)
}
} catch (e) {
console.error(e)
fs.copyFileSync(filePath1, filePath2)
}
}
@@ -58,13 +63,14 @@ function resolveClipboardImageGenerator () {
'/linux.sh',
'/mac.applescript',
'/windows.ps1',
'/windows10.ps1'
'/windows10.ps1',
'/wsl.sh'
]
return files.map(item => {
return {
origin: path.join(__static, item),
dest: path.join(STORE_PATH, item)
dest: path.join(CONFIG_DIR, item)
}
})
}
+65 -2
View File
@@ -1,8 +1,71 @@
import db from '#/datastore'
import { clipboard } from 'electron'
import db from '~/main/apis/core/datastore'
import { clipboard, Notification, dialog } from 'electron'
export 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
}
+43 -23
View File
@@ -1,35 +1,55 @@
import path from 'path'
import fs from 'fs-extra'
type ClipboardFileObject = {
import Logger from 'picgo/dist/src/lib/Logger'
interface IResultFileObject {
path: string
}
type Result = ClipboardFileObject[]
const getUploadFiles = (argv = process.argv, cwd = process.cwd()) => {
let files = argv.slice(1)
if (files.length > 0 && files[0] === 'upload') {
if (files.length === 1) {
type Result = IResultFileObject[]
interface IHandleArgvResult {
success: boolean
fileList?: string[]
}
const handleArgv = (argv: string[]): IHandleArgvResult => {
const uploadIndex = argv.indexOf('upload')
if (uploadIndex !== -1) {
const fileList = argv.slice(1 + uploadIndex)
return {
success: true,
fileList
}
}
return {
success: false
}
}
const getUploadFiles = (argv = process.argv, cwd = process.cwd(), logger: Logger) => {
const { success, fileList } = handleArgv(argv)
if (!success) {
return []
} else {
if (fileList?.length === 0) {
return null // for uploading images in clipboard
} else if (files.length > 1) {
files = argv.slice(1)
let result: Result = []
if (files.length > 0) {
result = files.map(item => {
if (path.isAbsolute(item)) {
} else if ((fileList?.length || 0) > 0) {
const result = fileList!.map(item => {
if (path.isAbsolute(item)) {
return {
path: item
}
} else {
let tempPath = path.join(cwd, item)
if (fs.existsSync(tempPath)) {
return {
path: item
path: tempPath
}
} else {
let tempPath = path.join(cwd, item)
if (fs.existsSync(tempPath)) {
return {
path: tempPath
}
} else {
return null
}
logger.warn(`cli -> can't get file: ${tempPath}, invalid path`)
return null
}
}).filter(item => item !== null) as Result
}
}
}).filter(item => item !== null) as Result
return result
}
}
+61
View File
@@ -0,0 +1,61 @@
import db from '~/main/apis/core/datastore'
import { ipcMain } from 'electron'
import { showMessageBox } from '~/main/utils/common'
import { SHOW_PRIVACY_MESSAGE } from '~/universal/events/constants'
class PrivacyManager {
async init () {
ipcMain.on(SHOW_PRIVACY_MESSAGE, () => {
this.show(false)
})
if (db.get('settings.privacyEnsure') !== true) {
const res = await this.show(true)
// cancel
if (res.result === 1) {
return false
} else {
db.set('settings.privacyEnsure', true)
return true
}
}
return true
}
async show (showCancel = true) {
const res = await showMessageBox({
type: 'info',
buttons: showCancel ? ['Yes', 'No'] : ['Yes'],
title: '隐私协议',
message: `
本软件尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更优质的服务,本软件会按照本隐私权政策的规定使用和收集您的一些行为信息。您在同意本软件服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本软件服务使用协议不可分割的一部分,如果不同意将无法使用。本协议会定期更新。
1.适用范围
a)在您使用本软件时,本软件会记录的您对本软件的一些操作行为信息,包括但不限于您使用本软件进行文件上传的耗时、类型、数量等信息。
2.信息的使用
a)在获得您的使用数据之后,本软件会将其上传至数据分析服务器,以便分析数据后,提供给您更好的服务。
3.信息披露
a)本软件不会将您的信息披露给不受信任的第三方。
b)根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露;
c)如您出现违反中国有关法律、法规或者相关规则的情况,需要向第三方披露;
4.信息安全
a)本软件不会收集您的个人信息、密钥信息等隐私信息,所收集的信息仅仅作为改善软件、优化体验、了解软件日活等用途。
`
})
return res
}
}
const privacyManager = new PrivacyManager()
export {
privacyManager
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { dialog, shell } from 'electron'
import db from '#/datastore'
import db from '~/main/apis/core/datastore'
import axios from 'axios'
import pkg from 'root/package.json'
import { lt } from 'semver'
+19 -22
View File
@@ -8,30 +8,27 @@
</el-switch>
</div>
</template>
<script>
export default {
name: 'choose-pic-bed',
props: {
type: String,
label: String
},
data () {
return {
value: false
}
},
created () {
if (this.type === this.$db.get('picBed.current')) {
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component({
name: 'choose-pic-bed'
})
export default class extends Vue {
value = false
@Prop() type!: string
@Prop() label!: string
async created () {
const current = await this.getConfig<string>('picBed.current')
if (this.type === current) {
this.value = true
}
},
methods: {
choosePicBed (val) {
this.letPicGoSaveData({
'picBed.current': this.type
})
this.$emit('update:choosed', this.type)
}
}
choosePicBed (val: string) {
this.saveConfig({
'picBed.current': this.type,
'picBed.uploader': this.type
})
this.$emit('update:choosed', this.type)
}
}
</script>
+36 -16
View File
@@ -72,7 +72,7 @@ import { cloneDeep, union } from 'lodash'
})
export default class extends Vue {
@Prop() private config!: any[]
@Prop() readonly type!: string
@Prop() readonly type!: 'uploader' | 'transformer' | 'plugin'
@Prop() readonly id!: string
configList = []
ruleForm = {}
@@ -81,9 +81,42 @@ export default class extends Vue {
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 = this.$db.get(`picBed.${this.id}`)
if (val.length > 0) {
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'
@@ -102,19 +135,6 @@ export default class extends Vue {
})
}
}
async validate () {
return new Promise((resolve, reject) => {
// @ts-ignore
this.$refs.form.validate((valid: boolean) => {
if (valid) {
resolve(this.ruleForm)
} else {
resolve(false)
return false
}
})
})
}
}
</script>
<style lang='stylus'>
+100 -58
View File
@@ -92,45 +92,75 @@
</el-row>
</el-dialog>
<el-dialog
title="自定义链接格式"
:visible.sync="customLinkVisible"
class="qrcode-dialog"
top="3vh"
width="60%"
title="图床配置二维码"
:visible.sync="qrcodeVisible"
:modal-append-to-body="false"
lock-scroll
>
<el-form
label-position="top"
:model="customLink"
ref="customLink"
:rules="rules"
label-position="left"
label-width="70px"
size="mini"
>
<el-form-item
label="用占位符$url来表示url的位置"
prop="value"
label="选择图床"
>
<el-input
class="align-center"
v-model="customLink.value"
:autofocus="true"
></el-input>
<el-select
v-model="choosedPicBedForQRCode"
multiple
collapse-tags
>
<el-option
v-for="item in picBed"
:key="item.type"
:label="item.name"
:value="item.type"
></el-option>
</el-select>
<el-button
v-show="choosedPicBedForQRCode.length > 0"
type="primary"
round
class="copy-picbed-config"
@click="handleCopyPicBedConfig"
>
复制图床配置
</el-button>
</el-form-item>
</el-form>
<div>
[]($url)
<div class="qrcode-container">
<qrcode-vue
v-show="choosedPicBedForQRCode.length > 0"
:size="280"
:value="picBedConfigString"
/>
</div>
<span slot="footer">
<el-button @click="cancelCustomLink">取消</el-button>
<el-button type="primary" @click="confirmCustomLink">确定</el-button>
</span>
</el-dialog>
<input-box-dialog />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { Component, Vue, Watch } from 'vue-property-decorator'
import QrcodeVue from 'qrcode.vue'
import pick from 'lodash/pick'
import pkg from 'root/package.json'
import keyDetect from '@/utils/key-binding'
import { remote, ipcRenderer, IpcRendererEvent } from 'electron'
import db from '#/datastore'
import {
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)) {
@@ -143,7 +173,8 @@ const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) =>
name: 'main-page',
mixins: [mixin],
components: {
InputBoxDialog
InputBoxDialog,
QrcodeVue
}
})
export default class extends Vue {
@@ -153,25 +184,29 @@ export default class extends Vue {
visible = false
keyBindingVisible = false
customLinkVisible = false
customLink = {
value: db.get('customLink') || '$url'
}
rules = {
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
}
os = ''
shortKey: IShortKeyMap = {
upload: db.get('shortKey.upload')
}
picBed: IPicBedType[] = []
qrcodeVisible = false
picBedConfigString = ''
choosedPicBedForQRCode: string[] = []
created () {
this.os = process.platform
this.buildMenu()
ipcRenderer.send('getPicBeds')
ipcRenderer.on('getPicBeds', this.getPicBeds)
}
@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) {
@@ -200,7 +235,11 @@ export default class extends Vue {
}
closeWindow () {
const window = BrowserWindow.getFocusedWindow()
window!.close()
if (process.platform === 'linux') {
window!.hide()
} else {
window!.close()
}
}
buildMenu () {
const _this = this
@@ -220,6 +259,18 @@ export default class extends Vue {
click () {
_this.visible = true
}
},
{
label: '生成图床配置二维码',
click () {
_this.qrcodeVisible = true
}
},
{
label: '隐私协议',
click () {
ipcRenderer.send(SHOW_PRIVACY_MESSAGE)
}
}
]
this.menu = Menu.buildFromTemplate(template)
@@ -228,32 +279,13 @@ export default class extends Vue {
// this.menu!.popup(remote.getCurrentWindow())
this.menu!.popup()
}
keyDetect (type: string, event: KeyboardEvent) {
this.shortKey[type] = keyDetect(event).join('+')
}
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = db.get('shortKey')
}
cancelCustomLink () {
this.customLinkVisible = false
this.customLink.value = db.get('customLink') || '$url'
}
confirmCustomLink () {
// @ts-ignore
this.$refs.customLink.validate((valid: boolean) => {
if (valid) {
db.set('customLink', this.customLink.value)
this.customLinkVisible = false
ipcRenderer.send('updateCustomLink')
} else {
return false
}
})
}
openMiniWindow () {
ipcRenderer.send('openMiniWindow')
}
handleCopyPicBedConfig () {
clipboard.writeText(this.picBedConfigString)
this.$message.success('图床配置复制成功')
}
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
this.picBed = picBeds
}
@@ -283,6 +315,16 @@ $darwinBg = transparentify(#172426, #000, 0.7)
text-align center
margin 10px auto
#main-page
.qrcode-dialog
.qrcode-container
display flex
justify-content center
.el-dialog__body
padding-top 10px
.copy-picbed-config
margin-left 10px
.el-input__inner
border-radius 14px
.fake-title-bar
-webkit-app-region drag
height h = 22px
+73 -70
View File
@@ -71,12 +71,12 @@
<el-col :span="20" :offset="2">
<el-row :gutter="16">
<gallerys
:images="images"
:images="filterList"
:index="idx"
@close="handleClose"
:options="options"
></gallerys>
<el-col :span="6" v-for="(item, index) in images" :key="item.id" class="gallery-list__img">
<el-col :span="6" v-for="(item, index) in filterList" :key="item.id" class="gallery-list__img">
<div
class="gallery-list__item"
@click="zoomImage(index)"
@@ -111,7 +111,9 @@
// @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,
@@ -153,18 +155,23 @@ export default class extends Vue {
picBed: IPicBedType[] = []
@Watch('$route')
handleRouteUpdate (to: any, from: any) {
console.log(to, from)
if (from.name === 'gallery') {
this.clearChoosedList()
}
if (to.name === 'gallery') {
this.updateGallery()
}
}
created () {
async created () {
ipcRenderer.on('updateGallery', (event: IpcRendererEvent) => {
this.$nextTick(() => {
this.filterList = this.getGallery()
this.$nextTick(async () => {
this.updateGallery()
})
})
ipcRenderer.send('getPicBeds')
ipcRenderer.on('getPicBeds', this.getPicBeds)
this.updateGallery()
}
mounted () {
document.addEventListener('keydown', this.handleDetectShiftKey)
@@ -178,9 +185,6 @@ export default class extends Vue {
get filterList () {
return this.getGallery()
}
set filterList (val) {
this.images = val
}
get isAllSelected () {
const values = Object.values(this.choosedList)
if (values.length === 0) {
@@ -194,37 +198,28 @@ export default class extends Vue {
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
this.picBed = picBeds
}
getGallery () {
if (this.choosedPicBed.length > 0) {
let arr: ImgInfo[] = []
this.choosedPicBed.forEach(item => {
let obj: IObj = {
type: item
}
if (this.searchText) {
obj.fileName = this.searchText
}
// @ts-ignore
arr = arr.concat(this.$db.read().get('uploaded').filter(obj => {
return obj.fileName.indexOf(this.searchText) !== -1 && obj.type === item
}).reverse().value())
})
this.images = arr
getGallery (): ImgInfo[] {
if (this.searchText || this.choosedPicBed.length > 0) {
return this.images
.filter(item => {
let isInChoosedPicBed = true
let isIncludesSearchText = true
if (this.choosedPicBed.length > 0) {
isInChoosedPicBed = this.choosedPicBed.some(type => type === item.type)
}
if (this.searchText) {
isIncludesSearchText = item.fileName?.includes(this.searchText) || false
}
return isIncludesSearchText && isInChoosedPicBed
})
} else {
if (this.searchText) {
let data = this.$db.read().get('uploaded')
// @ts-ignore
.filter(item => {
return item.fileName.indexOf(this.searchText) !== -1
}).reverse().value()
this.images = data
} else {
// @ts-ignore
this.images = this.$db.read().get('uploaded').slice().reverse().value()
}
return this.images
}
return this.images
}
async updateGallery () {
this.images = (await this.$$db.get({ orderBy: 'desc' })).data
}
@Watch('filterList')
handleFilterListChange () {
this.clearChoosedList()
@@ -267,9 +262,10 @@ export default class extends Vue {
this.idx = null
this.changeZIndexForGallery(false)
}
copy (item: ImgInfo) {
const style = this.$db.get('settings.pasteStyle') || 'markdown'
const copyLink = pasteStyle(style, item)
async copy (item: ImgInfo) {
const 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,
@@ -286,9 +282,9 @@ export default class extends Vue {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const file = this.$db.getById('uploaded', id)
this.$db.removeById('uploaded', id)
}).then(async () => {
const file = await this.$$db.getById(id)
await this.$$db.removeById(id)
ipcRenderer.send('removeFiles', [file])
const obj = {
title: '操作结果',
@@ -298,7 +294,7 @@ export default class extends Vue {
myNotification.onclick = () => {
return true
}
this.getGallery()
this.updateGallery()
}).catch((e) => {
console.log(e)
return true
@@ -309,12 +305,10 @@ export default class extends Vue {
this.imgInfo.imgUrl = item.imgUrl as string
this.dialogVisible = true
}
confirmModify () {
this.$db.read().get('uploaded')
// @ts-ignore
.getById(this.imgInfo.id)
.assign({ imgUrl: this.imgInfo.imgUrl })
.write()
async confirmModify () {
await this.$$db.updateById(this.imgInfo.id, {
imgUrl: this.imgInfo.imgUrl
})
const obj = {
title: '修改图片URL成功',
body: this.imgInfo.imgUrl,
@@ -325,7 +319,7 @@ export default class extends Vue {
return true
}
this.dialogVisible = false
this.getGallery()
this.updateGallery()
}
choosePicBed (type: string) {
let idx = this.choosedPicBed.indexOf(type)
@@ -355,18 +349,21 @@ export default class extends Vue {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let files: ImgInfo[] = []
Object.keys(this.choosedList).forEach(key => {
}).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 = this.$db.getById('uploaded', key)
files.push(file)
this.$db.removeById('uploaded', key)
const file = await this.$$db.getById<ImgInfo>(key)
if (file) {
files.push(file)
await this.$$db.removeById(key)
}
}
})
}
this.clearChoosedList()
this.choosedList = {} // 只有删除才能将这个置空
this.getGallery()
const obj = {
title: '操作结果',
body: '删除成功'
@@ -376,23 +373,29 @@ export default class extends Vue {
myNotification.onclick = () => {
return true
}
this.updateGallery()
}).catch(() => {
return true
})
}
}
multiCopy () {
async multiCopy () {
if (Object.values(this.choosedList).some(item => item)) {
const copyString: string[] = []
const style = this.$db.get('settings.pasteStyle') || 'markdown'
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.
Object.keys(this.choosedList).forEach(key => {
const imageIDList = Object.keys(this.choosedList)
for (let i = 0; i < imageIDList.length; i++) {
const key = imageIDList[i]
if (this.choosedList[key]) {
const item = this.$db.getById('uploaded', key)
copyString.push(pasteStyle(style, item))
this.choosedList[key] = false
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')
@@ -407,11 +410,11 @@ export default class extends Vue {
toggleHandleBar () {
this.handleBarActive = !this.handleBarActive
}
getPasteStyle () {
this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
}
handlePasteStyleChange (val: string) {
this.$db.set('settings.pasteStyle', val)
// getPasteStyle () {
// this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
// }
async handlePasteStyleChange (val: string) {
this.saveConfig('settings.pasteStyle', val)
this.pasteStyle = val
}
beforeDestroy () {
+13 -5
View File
@@ -23,7 +23,7 @@ import {
IpcRendererEvent,
remote
} from 'electron'
import path from 'path'
import { SHOW_PRIVACY_MESSAGE } from '~/universal/events/constants'
@Component({
name: 'mini-page',
mixins: [mixin]
@@ -136,16 +136,18 @@ export default class extends Vue {
openContextMenu () {
this.menu!.popup()
}
buildMenu () {
async buildMenu () {
const _this = this
const current = await this.getConfig('picBed.current')
const submenu = this.picBed.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: this.$db.get('picBed.current') === item.type,
checked: current === item.type,
click () {
_this.letPicGoSaveData({
'picBed.current': item.type
_this.saveConfig({
'picBed.current': item.type,
'picBed.uploader': item.type
})
ipcRenderer.send('syncPicBed')
}
@@ -175,6 +177,12 @@ export default class extends Vue {
remote.BrowserWindow.getFocusedWindow()!.hide()
}
},
{
label: '隐私协议',
click () {
ipcRenderer.send(SHOW_PRIVACY_MESSAGE)
}
},
{
label: '重启应用',
click () {
+109 -53
View File
@@ -32,7 +32,7 @@
<el-button type="primary" round size="mini" @click="customLinkVisible = true">点击设置</el-button>
</el-form-item>
<el-form-item
label="设置代理"
label="设置代理和镜像地址"
>
<el-button type="primary" round size="mini" @click="proxyVisible = true">点击设置</el-button>
</el-form-item>
@@ -183,19 +183,20 @@
</span>
</el-dialog>
<el-dialog
title="设置代理"
title="设置代理和镜像地址"
:visible.sync="proxyVisible"
:modal-append-to-body="false"
width="70%"
>
<el-form
label-position="right"
:model="customLink"
ref="customLink"
:rules="rules"
label-width="80px"
label-width="120px"
>
<el-form-item
label="代理地址"
label="上传代理"
>
<el-input
v-model="proxy"
@@ -203,6 +204,24 @@
placeholder="例如:http://127.0.0.1:1080"
></el-input>
</el-form-item>
<el-form-item
label="插件安装代理"
>
<el-input
v-model="npmProxy"
:autofocus="true"
placeholder="例如:http://127.0.0.1:1080"
></el-input>
</el-form-item>
<el-form-item
label="插件镜像地址"
>
<el-input
v-model="npmRegistry"
:autofocus="true"
placeholder="例如:https://registry.npm.taobao.org/"
></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="cancelProxy" round>取消</el-button>
@@ -319,13 +338,14 @@
<script lang="ts">
import keyDetect from '@/utils/key-binding'
import pkg from 'root/package.json'
import path from 'path'
import { IConfig } from 'picgo/dist/src/types/index'
import { PICGO_OPEN_FILE } from '#/events/constants'
import {
ipcRenderer,
remote
} from 'electron'
import { Component, Vue } from 'vue-property-decorator'
import db from '#/datastore'
// import db from '#/datastore'
const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
const releaseUrlBackup = 'https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@latest/package.json'
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
@@ -336,30 +356,22 @@ const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) =>
return callback()
}
}
let logLevel = db.get('settings.logLevel')
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
@Component({
name: 'picgo-setting'
})
export default class extends Vue {
form: ISettingForm = {
updateHelper: db.get('settings.showUpdateTip'),
updateHelper: false,
showPicBedList: [],
autoStart: db.get('settings.autoStart') || false,
rename: db.get('settings.rename') || false,
autoRename: db.get('settings.autoRename') || false,
uploadNotification: db.get('settings.uploadNotification') || false,
miniWindowOntop: db.get('settings.miniWindowOntop') || false,
logLevel,
autoCopyUrl: db.get('settings.autoCopy') === undefined ? true : db.get('settings.autoCopy'),
checkBetaUpdate: db.get('settings.checkBetaUpdate') === undefined ? true : db.get('settings.checkBetaUpdate')
autoStart: false,
rename: false,
autoRename: false,
uploadNotification: false,
miniWindowOntop: false,
logLevel: ['all'],
autoCopyUrl: true,
checkBetaUpdate: true
}
picBed: IPicBedType[] = []
logFileVisible = false
@@ -369,12 +381,14 @@ export default class extends Vue {
serverVisible = false
proxyVisible = false
customLink = {
value: db.get('settings.customLink') || '$url'
value: '$url'
}
shortKey: IShortKeyMap = {
upload: db.get('settings.shortKey.upload')
upload: ''
}
proxy = db.get('picBed.proxy') || ''
proxy = ''
npmRegistry = ''
npmProxy = ''
rules = {
value: [
{ validator: customLinkRule, trigger: 'blur' }
@@ -388,7 +402,7 @@ export default class extends Vue {
warn: '提醒-Warn',
none: '不记录日志-None'
}
server = db.get('settings.server') || {
server = {
port: 36677,
host: '127.0.0.1',
enable: true
@@ -408,7 +422,47 @@ export default class extends Vue {
this.os = process.platform
ipcRenderer.send('getPicBeds')
ipcRenderer.on('getPicBeds', this.getPicBeds)
this.initData()
}
async initData () {
const config = (await this.getConfig<IConfig>())!
if (config !== undefined) {
const settings = config.settings || {}
const picBed = config.picBed
this.form.updateHelper = settings.showUpdateTip || false
this.form.autoStart = settings.autoStart || false
this.form.rename = settings.rename || false
this.form.autoRename = settings.autoRename || false
this.form.uploadNotification = settings.uploadNotification || false
this.form.miniWindowOntop = settings.miniWindowOntop || false
this.form.logLevel = this.initLogLevel(settings.logLevel || [])
this.form.autoCopyUrl = settings.autoCopy === undefined ? true : settings.autoCopy
this.form.checkBetaUpdate = settings.checkBetaUpdate === undefined ? true : settings.checkBetaUpdate
this.customLink.value = settings.customLink || '$url'
this.shortKey.upload = settings.shortKey.upload
this.proxy = picBed.proxy || ''
this.npmRegistry = settings.registry || ''
this.npmProxy = settings.proxy || ''
this.server = settings.server || {
port: 36677,
host: '127.0.0.1',
enable: true
}
}
}
initLogLevel (logLevel: string | string[]) {
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
return logLevel
}
getPicBeds (event: Event, picBeds: IPicBedType[]) {
this.picBed = picBeds
this.form.showPicBedList = this.picBed.map(item => {
@@ -418,10 +472,7 @@ export default class extends Vue {
}) as string[]
}
openFile (file: string) {
const { app, shell } = remote
const STORE_PATH = app.getPath('userData')
const FILE = path.join(STORE_PATH, `/${file}`)
shell.openItem(FILE)
ipcRenderer.send(PICGO_OPEN_FILE, file)
}
openLogSetting () {
this.logFileVisible = true
@@ -429,15 +480,15 @@ export default class extends Vue {
keyDetect (type: string, event: KeyboardEvent) {
this.shortKey[type] = keyDetect(event).join('+')
}
cancelCustomLink () {
async cancelCustomLink () {
this.customLinkVisible = false
this.customLink.value = db.get('settings.customLink') || '$url'
this.customLink.value = await this.getConfig<string>('settings.customLink') || '$url'
}
confirmCustomLink () {
// @ts-ignore
this.$refs.customLink.validate((valid: boolean) => {
if (valid) {
db.set('settings.customLink', this.customLink.value)
this.saveConfig('settings.customLink', this.customLink.value)
this.customLinkVisible = false
ipcRenderer.send('updateCustomLink')
} else {
@@ -445,14 +496,16 @@ export default class extends Vue {
}
})
}
cancelProxy () {
async cancelProxy () {
this.proxyVisible = false
this.proxy = db.get('picBed.proxy') || undefined
this.proxy = await this.getConfig<string>('picBed.proxy') || ''
}
confirmProxy () {
this.proxyVisible = false
this.letPicGoSaveData({
'picBed.proxy': this.proxy
this.saveConfig({
'picBed.proxy': this.proxy,
'settings.proxy': this.npmProxy,
'settings.registry': this.npmRegistry
})
const successNotification = new Notification('设置代理', {
body: '设置成功'
@@ -462,10 +515,10 @@ export default class extends Vue {
}
}
updateHelperChange (val: boolean) {
db.set('settings.showUpdateTip', val)
this.saveConfig('settings.showUpdateTip', val)
}
checkBetaUpdateChange (val: boolean) {
db.set('settings.checkBetaUpdate', val)
this.saveConfig('settings.checkBetaUpdate', val)
}
handleShowPicBedListChange (val: string[]) {
const list = this.picBed.map(item => {
@@ -476,22 +529,22 @@ export default class extends Vue {
}
return item
})
this.letPicGoSaveData({
this.saveConfig({
'picBed.list': list
})
ipcRenderer.send('getPicBeds')
}
handleAutoStartChange (val: boolean) {
db.set('settings.autoStart', val)
this.saveConfig('settings.autoStart', val)
ipcRenderer.send('autoStart', val)
}
handleRename (val: boolean) {
this.letPicGoSaveData({
this.saveConfig({
'settings.rename': val
})
}
handleAutoRename (val: boolean) {
this.letPicGoSaveData({
this.saveConfig({
'settings.autoRename': val
})
}
@@ -533,14 +586,16 @@ export default class extends Vue {
this.checkUpdateVisible = false
}
handleUploadNotification (val: boolean) {
db.set('settings.uploadNotification', val)
this.saveConfig({
'settings.uploadNotification': val
})
}
handleMiniWindowOntop (val: boolean) {
db.set('settings.miniWindowOntop', val)
this.saveConfig('settings.miniWindowOntop', val)
this.$message.info('需要重启生效')
}
handleAutoCopyUrl (val: boolean) {
db.set('settings.autoCopy', val)
this.saveConfig('settings.autoCopy', val)
const successNotification = new Notification('设置自动复制链接', {
body: '设置成功'
})
@@ -552,7 +607,7 @@ export default class extends Vue {
if (this.form.logLevel.length === 0) {
return this.$message.error('请选择日志记录等级')
}
this.letPicGoSaveData({
this.saveConfig({
'settings.logLevel': this.form.logLevel
})
const successNotification = new Notification('设置日志', {
@@ -563,9 +618,9 @@ export default class extends Vue {
}
this.logFileVisible = false
}
cancelLogLevelSetting () {
async cancelLogLevelSetting () {
this.logFileVisible = false
let logLevel = db.get('settings.logLevel')
let logLevel = await this.getConfig<string | string[]>('settings.logLevel')
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
@@ -576,8 +631,9 @@ export default class extends Vue {
this.form.logLevel = logLevel
}
confirmServerSetting () {
// @ts-ignore
this.server.port = parseInt(this.server.port, 10)
this.letPicGoSaveData({
this.saveConfig({
'settings.server': this.server
})
const successNotification = new Notification('设置PicGo-Server', {
@@ -589,9 +645,9 @@ export default class extends Vue {
this.serverVisible = false
ipcRenderer.send('updateServer')
}
cancelServerSetting () {
async cancelServerSetting () {
this.serverVisible = false
this.server = db.get('settings.server') || {
this.server = await this.getConfig('settings.server') || {
port: 36677,
host: '127.0.0.1',
enable: true
+86 -49
View File
@@ -1,7 +1,13 @@
<template>
<div id="plugin-view">
<div class="view-title">
插件设置 - <i class="el-icon-goods" @click="goAwesomeList"></i>
插件设置 -
<el-tooltip :content="pluginListToolTip" placement="right">
<i class="el-icon-goods" @click="goAwesomeList"></i>
</el-tooltip>
<el-tooltip :content="importLocalPluginToolTip" placement="left">
<i class="el-icon-download" @click="handleImportLocalPlugin"/>
</el-tooltip>
</div>
<el-row class="handle-bar" :class="{ 'cut-width': pluginList.length > 6 }">
<el-input
@@ -13,7 +19,7 @@
</el-input>
</el-row>
<el-row :gutter="10" class="plugin-list" v-loading="loading">
<el-col :span="12" v-for="item in pluginList" :key="item.name">
<el-col :span="12" v-for="item in pluginList" :key="item.fullName">
<div class="plugin-item" :class="{ 'darwin': os === 'darwin' }">
<div class="cli-only-badge" v-if="!item.gui" title="CLI only">CLI</div>
<img class="plugin-item__logo" :src="item.logo"
@@ -106,6 +112,7 @@ import {
remote,
IpcRendererEvent
} from 'electron'
import { handleStreamlinePluginName } from '~/universal/utils/common'
const { Menu } = remote
@Component({
@@ -125,6 +132,8 @@ export default class extends Vue {
pluginNameList: string[] = []
loading = true
needReload = false
pluginListToolTip = '插件列表'
importLocalPluginToolTip = '导入本地插件'
id = ''
os = ''
defaultLogo: string = 'this.src="https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@dev/public/roundLogo.png"'
@@ -155,26 +164,32 @@ export default class extends Vue {
document.querySelector('.main-content.el-row').style.zIndex = 10
}
}
created () {
async created () {
this.os = process.platform
ipcRenderer.on('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => {
this.pluginList = list
this.pluginNameList = list.map(item => item.name)
ipcRenderer.on('hideLoading', () => {
this.loading = false
})
ipcRenderer.on('installSuccess', (evt: IpcRendererEvent, plugin: string) => {
ipcRenderer.on('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => {
this.pluginList = list
this.pluginNameList = list.map(item => item.fullName)
this.loading = false
})
ipcRenderer.on('installPlugin', (evt: IpcRendererEvent, { success, body }: {
success: boolean,
body: string
}) => {
this.loading = false
this.pluginList.forEach(item => {
if (item.name === plugin) {
if (item.fullName === body) {
item.ing = false
item.hasInstall = true
item.hasInstall = success
}
})
})
ipcRenderer.on('updateSuccess', (evt: IpcRendererEvent, plugin: string) => {
this.loading = false
this.pluginList.forEach(item => {
if (item.name === plugin) {
if (item.fullName === plugin) {
item.ing = false
item.hasInstall = true
}
@@ -186,7 +201,7 @@ export default class extends Vue {
ipcRenderer.on('uninstallSuccess', (evt: IpcRendererEvent, plugin: string) => {
this.loading = false
this.pluginList = this.pluginList.filter(item => {
if (item.name === plugin) { // restore Uploader & Transformer after uninstalling
if (item.fullName === plugin) { // restore Uploader & Transformer after uninstalling
if (item.config.transformer.name) {
this.handleRestoreState('transformer', item.config.transformer.name)
}
@@ -195,32 +210,33 @@ export default class extends Vue {
}
this.getPicBeds()
}
return item.name !== plugin
return item.fullName !== plugin
})
this.pluginNameList = this.pluginNameList.filter(item => item !== plugin)
})
this.getPluginList()
this.getSearchResult = debounce(this.getSearchResult, 50)
this.needReload = this.$db.get('needReload')
this.needReload = await this.getConfig<boolean>('needReload') || false
}
buildContextMenu (plugin: IPicGoPlugin) {
async buildContextMenu (plugin: IPicGoPlugin) {
const _this = this
let menu = [{
label: '启用插件',
enabled: !plugin.enabled,
click () {
_this.letPicGoSaveData({
[`picgoPlugins.picgo-plugin-${plugin.name}`]: true
_this.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: true
})
plugin.enabled = true
_this.getPicBeds()
_this.needReload = true
}
}, {
label: '禁用插件',
enabled: plugin.enabled,
click () {
_this.letPicGoSaveData({
[`picgoPlugins.picgo-plugin-${plugin.name}`]: false
_this.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: false
})
plugin.enabled = false
_this.getPicBeds()
@@ -230,28 +246,30 @@ export default class extends Vue {
if (plugin.config.uploader.name) {
_this.handleRestoreState('uploader', plugin.config.uploader.name)
}
_this.needReload = true
}
}, {
label: '卸载插件',
click () {
_this.uninstallPlugin(plugin.name)
_this.uninstallPlugin(plugin.fullName)
}
}, {
label: '更新插件',
click () {
_this.updatePlugin(plugin.name)
_this.updatePlugin(plugin.fullName)
}
}]
for (let i in plugin.config) {
if (plugin.config[i].config.length > 0) {
const obj = {
label: `配置${i} - ${plugin.config[i].name}`,
label: `配置${i} - ${plugin.config[i].fullName || plugin.config[i].name}`,
click () {
_this.currentType = i
_this.configName = plugin.config[i].name
_this.configName = plugin.config[i].fullName || plugin.config[i].name
_this.dialogVisible = true
_this.config = plugin.config[i].config
}
},
enabled: plugin.enabled
}
menu.push(obj)
}
@@ -259,7 +277,7 @@ export default class extends Vue {
// handle transformer
if (plugin.config.transformer.name) {
let currentTransformer = this.$db.get('picBed.transformer') || 'path'
let currentTransformer = await this.getConfig<string>('picBed.transformer') || 'path'
let pluginTransformer = plugin.config.transformer.name
const obj = {
label: `${currentTransformer === pluginTransformer ? '禁用' : '启用'}transformer - ${plugin.config.transformer.name}`,
@@ -280,7 +298,7 @@ export default class extends Vue {
menu.push({
label: i.label,
click () {
ipcRenderer.send('pluginActions', plugin.name, i.label)
ipcRenderer.send('pluginActions', plugin.fullName, i.label)
}
})
}
@@ -303,13 +321,13 @@ export default class extends Vue {
type: 'warning'
}).then(() => {
item.ing = true
ipcRenderer.send('installPlugin', item.name)
ipcRenderer.send('installPlugin', item.fullName)
}).catch(() => {
console.log('Install canceled')
})
} else {
item.ing = true
ipcRenderer.send('installPlugin', item.name)
ipcRenderer.send('installPlugin', item.fullName)
}
}
uninstallPlugin (val: string) {
@@ -318,22 +336,26 @@ export default class extends Vue {
item.ing = true
}
})
this.loading = true
ipcRenderer.send('uninstallPlugin', val)
}
updatePlugin (val: string) {
this.pluginList.forEach(item => {
if (item.name === val) {
if (item.fullName === val) {
item.ing = true
}
})
this.loading = true
ipcRenderer.send('updatePlugin', val)
}
reloadApp () {
remote.app.relaunch()
remote.app.exit(0)
}
handleReload () {
this.$db.set('needReload', true)
async handleReload () {
this.saveConfig({
needReload: true
})
this.needReload = true
const successNotification = new Notification('更新成功', {
body: '请点击此通知重启应用以生效'
@@ -345,14 +367,14 @@ export default class extends Vue {
cleanSearch () {
this.searchText = ''
}
toggleTransformer (transformer: string) {
let currentTransformer = this.$db.get('picBed.transformer') || 'path'
async toggleTransformer (transformer: string) {
let currentTransformer = await this.getConfig<string>('picBed.transformer') || 'path'
if (currentTransformer === transformer) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.transformer': 'path'
})
} else {
this.letPicGoSaveData({
this.saveConfig({
'picBed.transformer': transformer
})
}
@@ -363,17 +385,17 @@ export default class extends Vue {
if (result !== false) {
switch (this.currentType) {
case 'plugin':
this.letPicGoSaveData({
[`picgo-plugin-${this.configName}`]: result
this.saveConfig({
[`${this.configName}`]: result
})
break
case 'uploader':
this.letPicGoSaveData({
this.saveConfig({
[`picBed.${this.configName}`]: result
})
break
case 'transformer':
this.letPicGoSaveData({
this.saveConfig({
[`transformer.${this.configName}`]: result
})
break
@@ -407,7 +429,7 @@ export default class extends Vue {
})
}
handleSearchResult (item: INPMSearchResultObject) {
const name = item.package.name.replace(/picgo-plugin-/, '')
const name = handleStreamlinePluginName(item.package.name)
let gui = false
if (item.package.keywords && item.package.keywords.length > 0) {
if (item.package.keywords.includes('picgo-gui-plugin')) {
@@ -416,31 +438,33 @@ export default class extends Vue {
}
return {
name: name,
fullName: item.package.name,
author: item.package.author.name,
description: item.package.description,
logo: `https://cdn.jsdelivr.net/npm/${item.package.name}/logo.png`,
config: {},
homepage: item.package.links ? item.package.links.homepage : '',
hasInstall: this.pluginNameList.some(plugin => plugin === item.package.name.replace(/picgo-plugin-/, '')),
hasInstall: this.pluginNameList.some(plugin => plugin === item.package.name),
version: item.package.version,
gui,
ing: false // installing or uninstalling
}
}
// restore Uploader & Transformer
handleRestoreState (item: string, name: string) {
async handleRestoreState (item: string, name: string) {
if (item === 'uploader') {
const current = this.$db.get('picBed.current')
const current = await this.getConfig('picBed.current')
if (current === name) {
this.letPicGoSaveData({
'picBed.current': 'smms'
this.saveConfig({
'picBed.current': 'smms',
'picBed.uploader': 'smms'
})
}
}
if (item === 'transformer') {
const current = this.$db.get('picBed.transformer')
const current = await this.getConfig('picBed.transformer')
if (current === name) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.transformer': 'path'
})
}
@@ -454,14 +478,16 @@ export default class extends Vue {
goAwesomeList () {
remote.shell.openExternal('https://github.com/PicGo/Awesome-PicGo')
}
letPicGoSaveData (data: IObj) {
ipcRenderer.send('picgoSaveData', data)
handleImportLocalPlugin () {
ipcRenderer.send('importLocalPlugin')
this.loading = true
}
beforeDestroy () {
ipcRenderer.removeAllListeners('pluginList')
ipcRenderer.removeAllListeners('installSuccess')
ipcRenderer.removeAllListeners('installPlugin')
ipcRenderer.removeAllListeners('uninstallSuccess')
ipcRenderer.removeAllListeners('updateSuccess')
ipcRenderer.removeAllListeners('hideLoading')
}
}
</script>
@@ -491,6 +517,7 @@ $darwinBg = #172426
font-size 20px
text-align center
margin 10px auto
position relative
i.el-icon-goods
font-size 20px
vertical-align middle
@@ -498,6 +525,16 @@ $darwinBg = #172426
transition color .2s ease-in-out
&:hover
color #49B1F5
i.el-icon-download
position absolute
right 0
top 8px
font-size 20px
vertical-align middle
cursor pointer
transition color .2s ease-in-out
&:hover
color #49B1F5
.handle-bar
margin-bottom 20px
&.cut-width
+8 -11
View File
@@ -109,8 +109,8 @@ export default class extends Vue {
command = ''
shortKey = ''
currentIndex = 0
created () {
const shortKeyConfig = this.$db.get('settings.shortKey') as IShortKeyConfigs
async created () {
const shortKeyConfig = (await this.getConfig<IShortKeyConfigs>('settings.shortKey'))!
this.list = Object.keys(shortKeyConfig).map(item => {
return {
...shortKeyConfig[item],
@@ -132,26 +132,23 @@ export default class extends Vue {
toggleEnable (item: IShortKeyConfig) {
const status = !item.enable
item.enable = status
// this.$db.set(`settings.shortKey.${item.name}.enable`, status)
ipcRenderer.send('bindOrUnbindShortKey', item, item.from)
}
keyDetect (event: KeyboardEvent) {
this.shortKey = keyDetect(event).join('+')
}
openKeyBindingDialog (config: IShortKeyConfig, index: number) {
async openKeyBindingDialog (config: IShortKeyConfig, index: number) {
this.command = `${config.from}:${config.name}`
this.shortKey = this.$db.get(`settings.shortKey.${this.command}.key`)
this.shortKey = await this.getConfig(`settings.shortKey.${this.command}.key`) || ''
this.currentIndex = index
this.keyBindingVisible = true
}
cancelKeyBinding () {
async cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = this.$db.get(`settings.shortKey.${this.command}.key`)
this.shortKey = await this.getConfig<string>(`settings.shortKey.${this.command}.key`) || ''
}
confirmKeyBinding () {
const oldKey = this.$db.get(`settings.shortKey.${this.command}.key`)
// this.$db.set(`settings.shortKey.${this.command}.key`, this.shortKey)
// const newKey = this.$db.get(`settings.shortKey.${this.command}`)
async confirmKeyBinding () {
const oldKey = await this.getConfig<string>(`settings.shortKey.${this.command}.key`)
const config = Object.assign({}, this.list[this.currentIndex])
config.key = this.shortKey
ipcRenderer.send('updateShortKey', config, oldKey, config.from)
+18 -16
View File
@@ -30,12 +30,15 @@ import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/mixin'
import pasteTemplate from '#/utils/pasteTemplate'
import { ipcRenderer, clipboard } from 'electron'
import { IPasteStyle } from '#/types/enum'
import { IResult } from '@picgo/store/dist/types'
@Component({
name: 'tray-page',
mixins: [mixin]
})
export default class extends Vue {
files = []
files: IResult<ImgInfo>[] = []
notification = {
title: '复制链接成功',
body: '',
@@ -46,16 +49,16 @@ export default class extends Vue {
get reverseList () {
return this.files.slice().reverse()
}
getData () {
// @ts-ignore
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
async getData () {
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
}
copyTheLink (item: ImgInfo) {
async copyTheLink (item: ImgInfo) {
this.notification.body = item.imgUrl!
this.notification.icon = item.imgUrl!
const myNotification = new Notification(this.notification.title, this.notification)
const pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
clipboard.writeText(pasteTemplate(pasteStyle, item))
const pasteStyle = await this.getConfig<IPasteStyle>('settings.pasteStyle') || IPasteStyle.MARKDOWN
const customLink = await this.getConfig<string>('settings.customLink')
clipboard.writeText(pasteTemplate(pasteStyle, item, customLink))
myNotification.onclick = () => {
return true
}
@@ -83,19 +86,18 @@ export default class extends Vue {
mounted () {
this.disableDragFile()
this.getData()
ipcRenderer.on('dragFiles', (event: Event, files: string[]) => {
files.forEach(item => {
this.$db.insert('uploaded', item)
})
// @ts-ignore
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
ipcRenderer.on('dragFiles', async (event: Event, files: string[]) => {
for (let i = 0; i < files.length; i++) {
const item = files[i]
await this.$$db.insert(item)
}
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
})
ipcRenderer.on('clipboardFiles', (event: Event, files: ImgInfo[]) => {
this.clipboardFiles = files
})
ipcRenderer.on('uploadFiles', (event: Event) => {
// @ts-ignore
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
ipcRenderer.on('uploadFiles', async (event: Event) => {
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
console.log(this.files)
this.uploadFlag = false
})
+16 -12
View File
@@ -169,11 +169,13 @@ export default class extends Vue {
})
ipcRenderer.send('uploadChoosedFiles', sendFiles)
}
getPasteStyle () {
this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
async getPasteStyle () {
this.pasteStyle = await this.getConfig('settings.pasteStyle') || 'markdown'
}
handlePasteStyleChange (val: string) {
this.$db.set('settings.pasteStyle', val)
this.saveConfig({
'settings.pasteStyle': val
})
}
uploadClipboardFiles () {
ipcRenderer.send('uploadClipboardFilesFromUploadPage')
@@ -196,10 +198,10 @@ export default class extends Vue {
this.$message.error('请输入合法的URL')
}
}
getDefaultPicBed () {
const current: string = this.$db.get('picBed.current')
async getDefaultPicBed () {
const currentPicBed = await this.getConfig<string>('picBed.current')
this.picBed.forEach(item => {
if (item.type === current) {
if (item.type === currentPicBed) {
this.picBedName = item.name
}
})
@@ -208,21 +210,23 @@ export default class extends Vue {
this.picBed = picBeds
this.getDefaultPicBed()
}
handleChangePicBed () {
this.buildMenu()
async handleChangePicBed () {
await this.buildMenu()
// this.menu.popup(remote.getCurrentWindow())
this.menu!.popup()
}
buildMenu () {
async buildMenu () {
const _this = this
const currentPicBed = await this.getConfig<string>('picBed.current')
const submenu = this.picBed.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: this.$db.get('picBed.current') === item.type,
checked: currentPicBed === item.type,
click () {
_this.letPicGoSaveData({
'picBed.current': item.type
_this.saveConfig({
'picBed.current': item.type,
'picBed.uploader': item.type
})
ipcRenderer.send('syncPicBed')
}
+3 -3
View File
@@ -86,8 +86,8 @@ export default class extends Vue {
customUrl: '',
options: ''
}
created () {
const config = this.$db.get('picBed.aliyun') as IAliYunConfig
async created () {
const config = await this.getConfig<IAliYunConfig>('picBed.aliyun')
if (config) {
this.form = Object.assign({}, config)
}
@@ -96,7 +96,7 @@ export default class extends Vue {
// @ts-ignore
this.$refs.aliyun.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.aliyun': this.form
})
const successNotification = new window.Notification('设置结果', {
+4 -4
View File
@@ -25,7 +25,7 @@
:rules="{
required: true, message: '分支名不能为空', trigger: 'blur'
}">
<el-input v-model="form.branch" @keyup.native.enter="confirm" placeholder="例如:master"></el-input>
<el-input v-model="form.branch" @keyup.native.enter="confirm" placeholder="例如:main"></el-input>
</el-form-item>
<el-form-item
label="设定Token"
@@ -71,8 +71,8 @@ export default class extends Vue {
customUrl: '',
branch: ''
}
created () {
const config = this.$db.get('picBed.github') as IGitHubConfig
async created () {
const config = await this.getConfig<IGitHubConfig>('picBed.github')
if (config) {
this.form = Object.assign({}, config)
}
@@ -81,7 +81,7 @@ export default class extends Vue {
// @ts-ignore
this.$refs.github.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.github': this.form
})
const successNotification = new Notification('设置结果', {
+3 -3
View File
@@ -48,8 +48,8 @@ export default class extends Vue {
clientId: '',
proxy: ''
}
created () {
const config = this.$db.get('picBed.imgur') as IImgurConfig
async created () {
const config = await this.getConfig<IImgurConfig>('picBed.imgur')
if (config) {
this.form = Object.assign({}, config)
}
@@ -58,7 +58,7 @@ export default class extends Vue {
// @ts-ignore
this.$refs.imgur.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.imgur': this.form
})
const successNotification = new Notification('设置结果', {
+9 -4
View File
@@ -1,6 +1,6 @@
<template>
<div id="others-view">
<el-row :gutter="16">
<el-row :gutter="16" class="setting-list">
<el-col :span="16" :offset="4">
<div class="view-title">
{{ picBedName }}设置
@@ -56,7 +56,7 @@ export default class extends Vue {
// @ts-ignore
const result = await this.$refs.configForm.validate()
if (result !== false) {
this.letPicGoSaveData({
this.saveConfig({
[`picBed.${this.type}`]: result
})
const successNotification = new Notification('设置结果', {
@@ -68,8 +68,9 @@ export default class extends Vue {
}
}
setDefaultPicBed (type: string) {
this.letPicGoSaveData({
'picBed.current': type
this.saveConfig({
'picBed.current': type,
'picBed.uploader': type
})
// @ts-ignore 来自mixin的数据
this.defaultPicBed = type
@@ -91,6 +92,10 @@ export default class extends Vue {
</script>
<style lang='stylus'>
#others-view
.setting-list
height 425px
overflow-y auto
overflow-x hidden
.el-form
label
line-height 22px
+4 -4
View File
@@ -17,7 +17,7 @@
:rules="{
required: true, message: 'AccessKey不能为空', trigger: 'blur'
}">
<el-input v-model="form.accessKey" placeholder="AccessKey" @keyup.native.enter="confirm('weiboForm')"></el-input>
<el-input v-model="form.accessKey" placeholder="AccessKey" @keyup.native.enter="confirm"></el-input>
</el-form-item>
<el-form-item
label="设定SecretKey"
@@ -88,8 +88,8 @@ export default class extends Vue {
options: '',
path: ''
}
created () {
const config = this.$db.get('picBed.qiniu') as IQiniuConfig
async created () {
const config = await this.getConfig<IQiniuConfig>('picBed.qiniu')
if (config) {
this.form = Object.assign({}, config)
}
@@ -98,7 +98,7 @@ export default class extends Vue {
// @ts-ignore
this.$refs.qiniu.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.qiniu': this.form
})
const successNotification = new Notification('设置结果', {
+4 -4
View File
@@ -41,17 +41,17 @@ export default class extends Vue {
form: ISMMSConfig = {
token: ''
}
created () {
const config = this.$db.get('picBed.smms.token') as (string | boolean)
async created () {
const config = await this.getConfig<string | boolean>('picBed.smms.token')
if (typeof config !== 'boolean') {
this.form.token = config
this.form.token = config || ''
}
}
confirm () {
// @ts-ignore
this.$refs.smms.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.smms': this.form
})
const successNotification = new window.Notification('设置结果', {
+4 -4
View File
@@ -30,7 +30,7 @@
:rules="{
required: true, message: 'SecretId不能为空', trigger: 'blur'
}">
<el-input v-model="form.secretId" placeholder="SecretId" @keyup.native.enter="confirm('weiboForm')"></el-input>
<el-input v-model="form.secretId" placeholder="SecretId" @keyup.native.enter="confirm"></el-input>
</el-form-item>
<el-form-item
label="设定SecretKey"
@@ -104,8 +104,8 @@ export default class extends Vue {
customUrl: '',
version: 'v4'
}
created () {
const config = this.$db.get('picBed.tcyun') as ITcYunConfig
async created () {
const config = await this.getConfig<ITcYunConfig>('picBed.tcyun')
if (config) {
this.form = Object.assign({}, config)
}
@@ -114,7 +114,7 @@ export default class extends Vue {
// @ts-ignore
this.$refs.tcyun.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.tcyun': this.form
})
const successNotification = new window.Notification('设置结果', {
+3 -3
View File
@@ -79,8 +79,8 @@ export default class extends Vue {
options: '',
path: ''
}
created () {
const config = this.$db.get('picBed.upyun') as IUpYunConfig
async created () {
const config = await this.getConfig<IUpYunConfig>('picBed.upyun')
if (config) {
this.form = Object.assign({}, config)
}
@@ -89,7 +89,7 @@ export default class extends Vue {
// @ts-ignore
this.$refs.tcyun.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.upyun': this.form
})
const successNotification = new Notification('设置结果', {
-153
View File
@@ -1,153 +0,0 @@
<template>
<div id="weibo-view">
<el-row :gutter="16">
<el-col :span="16" :offset="4">
<div class="view-title">
微博图床设置[已停止支持]
</div>
<el-form
ref="weiboForm"
label-position="right"
label-width="120px"
size="small"
:model="form">
<el-form-item
label="设定用户名"
prop="username"
:rules="{
required: !chooseCookie, message: '用户名不能为空', trigger: 'blur'
}">
<el-input v-model="form.username" placeholder="用户名" @keyup.native.enter="confirm('weiboForm')" :disabled="chooseCookie"></el-input>
</el-form-item>
<el-form-item
label="设定密码"
prop="password"
:rules="{required: !chooseCookie,messsage: '密码不能为空',trigger: 'blur'}">
<el-input v-model="form.password" type="password" @keyup.native.enter="confirm('weiboForm')" placeholder="密码" :disabled="chooseCookie"></el-input>
</el-form-item>
<el-form-item
label="使用Cookie上传"
>
<el-switch
v-model="chooseCookie"
active-text="cookie模式"
@change="handleSwitchChange"
></el-switch>
<i class="el-icon-question" @click="openWiki"></i>
</el-form-item>
<el-form-item
label="设定Cookie"
prop="cookie"
:rules="{
required: chooseCookie, message: '密码不能为空', trigger: 'blur'
}">
<el-input v-model="form.cookie" @keyup.native.enter="confirm('weiboForm')" placeholder="Cookie" :disabled="!chooseCookie"></el-input>
</el-form-item>
<el-form-item label="* 图片质量">
<el-radio-group v-model="quality">
<el-radio label="thumbnail">缩略图</el-radio>
<el-radio label="mw690">中等尺寸</el-radio>
<el-radio label="large">原图</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button-group>
<el-button type="primary" @click="confirm('weiboForm')" round>确定</el-button>
<el-button type="success" @click="setDefaultPicBed('weibo')" round :disabled="defaultPicBed === 'weibo'">设为默认图床</el-button>
</el-button-group>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script>
import mixin from '@/utils/ConfirmButtonMixin'
export default {
name: 'weibo',
mixins: [mixin],
data () {
return {
form: {
username: '',
password: '',
cookie: ''
},
chooseCookie: false,
quality: 'large'
}
},
created () {
const config = this.$db.get('picBed.weibo')
if (config) {
this.form.username = config.username
this.form.password = config.password
this.quality = config.quality || 'large'
this.form.cookie = config.cookie
this.chooseCookie = config.chooseCookie
}
},
methods: {
confirm (formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.letPicGoSaveData({
'picBed.weibo': {
username: this.form.username,
password: this.form.password,
quality: this.quality,
cookie: this.form.cookie,
chooseCookie: this.chooseCookie
}
})
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
} else {
return false
}
})
},
handleSwitchChange () {
this.$refs['weiboForm'].resetFields()
},
openWiki () {
this.$electron.remote.shell.openExternal('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#微博图床')
}
}
}
</script>
<style lang='stylus'>
.el-message
left 60%
#weibo-view
.el-form
label
line-height 22px
padding-bottom 0
color #eee
.el-button-group
width 100%
.el-button
width 50%
.el-input__inner
border-radius 19px
.el-radio-group
margin-left 25px
.el-switch__label
color #eee
&.is-active
color #409EFF
.el-icon-question
font-size 20px
float right
margin-top 9px
color #eee
cursor pointer
transition .2s color ease-in-out
&:hover
color #409EFF
</style>
-5
View File
@@ -31,11 +31,6 @@ export default new Router({
component: () => import(/* webpackChunkName: "Upload" */ '@/pages/Upload.vue'),
name: 'upload'
},
{
path: 'weibo',
component: () => import(/* webpackChunkName: "Weibo" */ '@/pages/picbeds/Weibo.vue'),
name: 'weibo'
},
{
path: 'qiniu',
component: () => import(/* webpackChunkName: "Qiniu" */ '@/pages/picbeds/Qiniu.vue'),
+11 -3
View File
@@ -1,11 +1,19 @@
import { Component, Vue } from 'vue-property-decorator'
import { ipcRenderer } from 'electron'
import { IConfig } from 'picgo/dist/src/types'
@Component
export default class extends Vue {
defaultPicBed = this.$db.get('picBed.current')
defaultPicBed = 'smms'
async created () {
const config = await this.getConfig<IConfig>()
if (config) {
this.defaultPicBed = config?.picBed?.uploader || config?.picBed?.current || 'smms'
}
}
setDefaultPicBed (type: string) {
this.letPicGoSaveData({
'picBed.current': type
this.saveConfig({
'picBed.current': type,
'picBed.uploader': type
})
this.defaultPicBed = type
const successNotification = new Notification('设置默认图床', {
+23
View File
@@ -0,0 +1,23 @@
/* eslint-disable camelcase */
import {
TALKING_DATA_APPID, TALKING_DATA_EVENT
} from '~/universal/events/constants'
import pkg from 'root/package.json'
import { ipcRenderer } from 'electron'
import { handleTalkingDataEvent } from './common'
const { version } = pkg
export const initTalkingData = () => {
setTimeout(() => {
const talkingDataScript = document.createElement('script')
talkingDataScript.src = `http://sdk.talkingdata.com/app/h5/v1?appid=${TALKING_DATA_APPID}&vn=${version}&vc=${version}`
const head = document.getElementsByTagName('head')[0]
head.appendChild(talkingDataScript)
}, 0)
}
ipcRenderer.on(TALKING_DATA_EVENT, (_, data: ITalkingDataOptions) => {
handleTalkingDataEvent(data)
})
+10
View File
@@ -0,0 +1,10 @@
const isDevelopment = process.env.NODE_ENV !== 'production'
/* eslint-disable camelcase */
export const handleTalkingDataEvent = (data: ITalkingDataOptions) => {
const { EventId, Label = '', MapKv = {} } = data
MapKv.from = window.location.href
window.TDAPP.onEvent(EventId, Label, MapKv)
if (isDevelopment) {
console.log('talkingData', data)
}
}
+53
View File
@@ -0,0 +1,53 @@
import { IObject, IResult, IGetResult, IFilter } from '@picgo/store/dist/types'
import { ipcRenderer, IpcRendererEvent } from 'electron'
import { uuid } from 'uuidv4'
import {
PICGO_GET_DB,
PICGO_INSERT_DB,
PICGO_INSERT_MANY_DB,
PICGO_UPDATE_BY_ID_DB,
PICGO_GET_BY_ID_DB,
PICGO_REMOVE_BY_ID_DB
} from '#/events/constants'
import { IGalleryDB } from '#/types/extra-vue'
export class GalleryDB implements IGalleryDB {
async get<T> (filter?: IFilter): Promise<IGetResult<T>> {
const res = await this.msgHandler<IGetResult<T>>(PICGO_GET_DB, filter)
return res
}
async insert<T> (value: T): Promise<IResult<T>> {
const res = await this.msgHandler<IResult<T>>(PICGO_INSERT_DB, value)
return res
}
async insertMany<T> (value: T[]): Promise<IResult<T>[]> {
const res = await this.msgHandler<IResult<T>[]>(PICGO_INSERT_MANY_DB, value)
return res
}
async updateById (id: string, value: IObject): Promise<boolean> {
const res = await this.msgHandler<boolean>(PICGO_UPDATE_BY_ID_DB, id, value)
return res
}
async getById<T> (id: string): Promise<IResult<T> | undefined> {
const res = await this.msgHandler<IResult<T> | undefined>(PICGO_GET_BY_ID_DB, id)
return res
}
async removeById (id: string): Promise<void> {
const res = await this.msgHandler<void>(PICGO_REMOVE_BY_ID_DB, id)
return res
}
private msgHandler<T> (method: string, ...args: any[]): Promise<T> {
return new Promise((resolve) => {
const callbackId = uuid()
const callback = (event: IpcRendererEvent, data: T, returnCallbackId: string) => {
if (returnCallbackId === callbackId) {
resolve(data)
ipcRenderer.removeListener(method, callback)
}
}
ipcRenderer.on(method, callback)
ipcRenderer.send(method, ...args, callbackId)
})
}
}
export default new GalleryDB()
+24 -3
View File
@@ -1,8 +1,29 @@
import { Component, Vue } from 'vue-property-decorator'
import { ipcRenderer } from 'electron'
import { ipcRenderer, IpcRendererEvent } from 'electron'
import { PICGO_SAVE_CONFIG, PICGO_GET_CONFIG } from '#/events/constants'
import { uuid } from 'uuidv4'
@Component
export default class extends Vue {
letPicGoSaveData (data: IObj) {
ipcRenderer.send('picgoSaveData', data)
// support string key + value or object config
saveConfig (config: IObj | string, value?: any) {
if (typeof config === 'string') {
config = {
[config]: value
}
}
ipcRenderer.send(PICGO_SAVE_CONFIG, config)
}
getConfig<T> (key?: string): Promise<T | undefined> {
return new Promise((resolve) => {
const callbackId = uuid()
const callback = (event: IpcRendererEvent, config: T | undefined, returnCallbackId: string) => {
if (returnCallbackId === callbackId) {
resolve(config)
ipcRenderer.removeListener(PICGO_GET_CONFIG, callback)
}
}
ipcRenderer.on(PICGO_GET_CONFIG, callback)
ipcRenderer.send(PICGO_GET_CONFIG, key, callbackId)
})
}
}
+12
View File
@@ -1,3 +1,15 @@
export const SHOW_INPUT_BOX = 'SHOW_INPUT_BOX'
export const SHOW_INPUT_BOX_RESPONSE = 'SHOW_INPUT_BOX_RESPONSE'
export const TOGGLE_SHORTKEY_MODIFIED_MODE = 'TOGGLE_SHORTKEY_MODIFIED_MODE'
export const TALKING_DATA_APPID = '7E6832BCE3F1438696579E541DFEBFDA'
export const TALKING_DATA_EVENT = 'TALKING_DATA_EVENT'
export const SHOW_PRIVACY_MESSAGE = 'SHOW_PRIVACY_MESSAGE'
export const PICGO_SAVE_CONFIG = 'PICGO_SAVE_CONFIG'
export const PICGO_GET_CONFIG = 'PICGO_GET_CONFIG'
export const PICGO_GET_DB = 'PICGO_GET_DB'
export const PICGO_INSERT_DB = 'PICGO_INSERT_DB'
export const PICGO_INSERT_MANY_DB = 'PICGO_INSERT_MANY_DB'
export const PICGO_UPDATE_BY_ID_DB = 'PICGO_UPDATE_BY_ID_DB'
export const PICGO_GET_BY_ID_DB = 'PICGO_GET_BY_ID_DB'
export const PICGO_REMOVE_BY_ID_DB = 'PICGO_REMOVE_BY_ID_DB'
export const PICGO_OPEN_FILE = 'PICGO_OPEN_FILE'
+11
View File
@@ -20,3 +20,14 @@ declare interface IWindowManager {
deleteById: (id: number) => void
getAvailableWindow: () => BrowserWindow
}
// https://stackoverflow.com/questions/35074713/extending-typescript-global-object-in-node-js/44387594#44387594
declare global {
namespace NodeJS {
interface Global {
PICGO_GUI_VERSION: string
PICGO_CORE_VERSION: string
notificationList: IAppNotification[]
}
}
}
+14 -3
View File
@@ -1,14 +1,25 @@
import VueRouter, { Route } from 'vue-router'
import db from '#/datastore'
import axios from 'axios'
import { IObject, IResult, IGetResult, IFilter } from '@picgo/store/dist/types'
interface IGalleryDB {
get<T>(filter?: IFilter): Promise<IGetResult<T>>
insert<T> (value: T): Promise<IResult<T>>
insertMany<T> (value: T[]): Promise<IResult<T>[]>
updateById (id: string, value: IObject): Promise<boolean>
getById<T> (id: string): Promise<IResult<T> | undefined>
removeById (id: string): Promise<void>
}
declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter,
$route: Route,
$db: typeof db
$http: typeof axios
$builtInPicBed: string[]
$bus: Vue
letPicGoSaveData(data: IObj): void
$$db: IGalleryDB
saveConfig(data: IObj | string, value?: any): void
getConfig<T>(key?: string): Promise<T | undefined>
}
}
+6
View File
@@ -10,4 +10,10 @@ declare global {
[elem: string]: any
}
}
interface Window {
TDAPP: {
onEvent: (EventId: string, Label?: string, MapKv?: IStringKeyMap) => void
}
}
}
+42
View File
@@ -45,6 +45,7 @@ interface ImgInfo {
extname?: string
imgUrl?: string
id?: string
type?: string
[propName: string]: any
}
@@ -123,6 +124,7 @@ interface IBounds {
type ICtx = import('picgo')
interface IPicGoPlugin {
name: string
fullName: string
author: string
description: string
logo: string
@@ -143,8 +145,17 @@ interface IPicGoPlugin {
hasInstall?: boolean
}
interface IPicGoPluginConfig {
name: string
type: string
required: boolean
default?: any
[propName: string]: any
}
interface IPluginMenuConfig {
name: string
fullName?: string
config: any[]
}
@@ -194,6 +205,14 @@ type IUploadOption = string[]
interface IShowNotificationOption {
title: string
body: string
icon?: string | import('electron').NativeImage
}
interface IPrivateShowNotificationOption extends IShowNotificationOption{
/**
* click notification to copy the body
*/
clickToCopy?: boolean
}
interface IShowMessageBoxOption {
@@ -279,3 +298,26 @@ interface IUpYunConfig {
}
type ILoggerType = string | Error | boolean | number | undefined
interface IAppNotification {
title: string
body: string
icon?: string
}
interface ITalkingDataOptions {
EventId: string
Label?: string
MapKv?: IStringKeyMap
}
interface IAnalyticsData {
fromClipboard: boolean
type: string
count: number
duration?: number // 耗时
}
interface IStringKeyMap {
[propName: string]: any
}
+15
View File
@@ -15,3 +15,18 @@ export const handleUrlEncode = (url: string): string => {
}
return url
}
/**
* streamline the full plugin name to a simple one
* for example:
* 1. picgo-plugin-xxx -> xxx
* 2. @xxx/picgo-plugin-yyy -> yyy
* @param name pluginFullName
*/
export const handleStreamlinePluginName = (name: string) => {
if (/^@[^/]+\/picgo-plugin-/.test(name)) {
return name.replace(/^@[^/]+\/picgo-plugin-/, '')
} else {
return name.replace(/picgo-plugin-/, '')
}
}
+3 -4
View File
@@ -1,4 +1,3 @@
import db from '#/datastore'
import { IPasteStyle } from '#/types/enum'
import { handleUrlEncode } from './common'
@@ -19,15 +18,15 @@ const formatCustomLink = (customLink: string, item: ImgInfo) => {
return customLink
}
export default (style: IPasteStyle, item: ImgInfo) => {
export default (style: IPasteStyle, item: ImgInfo, customLink: string | undefined) => {
const url = handleUrlEncode(item.url || item.imgUrl)
const customLink = db.get('settings.customLink') || '$url'
const _customLink = customLink || '$url'
const tpl = {
'markdown': `![](${url})`,
'HTML': `<img src="${url}"/>`,
'URL': url,
'UBB': `[IMG]${url}[/IMG]`,
'Custom': formatCustomLink(customLink, item)
'Custom': formatCustomLink(_customLink, item)
}
return tpl[style]
}
+32 -2
View File
@@ -2,13 +2,24 @@ const path = require('path')
function resolve (dir) {
return path.join(__dirname, dir)
}
module.exports = {
const arch = process.argv.includes('--ia32') ? 'ia32' : 'x64'
const config = {
configureWebpack: {
devtool: 'nosources-source-map'
},
chainWebpack: config => {
config.resolve.alias
.set('@', resolve('src/renderer'))
.set('~', resolve('src'))
.set('root', resolve('./'))
.set('#', resolve('src/universal'))
// define
// config.plugin('define')
// .tap(args => {
// return args
// })
},
pluginOptions: {
electronBuilder: {
@@ -57,7 +68,14 @@ module.exports = {
},
win: {
icon: 'build/icons/icon.ico',
target: 'nsis'
// eslint-disable-next-line no-template-curly-in-string
artifactName: `PicGo Setup \${version}-${arch}.exe`,
target: [{
target: 'nsis',
arch: [
arch
]
}]
},
nsis: {
shortcutName: 'PicGo',
@@ -74,3 +92,15 @@ module.exports = {
}
}
}
if (process.env.NODE_ENV === 'development') {
config.configureWebpack = {
devtool: 'source-map'
}
// for dev main process hot reload
config.pluginOptions.electronBuilder.mainProcessWatch = ['src/main/**/*']
}
module.exports = {
...config
}
+152 -4
View File
@@ -918,6 +918,18 @@
dependencies:
ora "^3.4.0"
"@picgo/store@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@picgo/store/-/store-1.0.3.tgz#cfe346ad30c80f378e0bc81311e7468df436bc1f"
integrity sha512-quyu56yuUsFFJGQ88suTwUSEzKosZPzR1M52eyqzyQ2HZWZVn0pa/eBBsh7OYefS0V13YLR5llnIkQaL8y11Ww==
dependencies:
"@types/bson" "^4.0.1"
"@types/graceful-fs" "^4.1.3"
bson "^4.0.3"
comment-json "^4.1.0"
lodash-id "^0.14.0"
lowdb "^1.0.0"
"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@@ -939,6 +951,13 @@
dependencies:
defer-to-connect "^1.0.1"
"@types/bson@^4.0.1":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.0.4.tgz#79d2d26e81070044db2a1a8b2cc2f673c840e1e5"
integrity sha512-awqorHvQS0DqxkHQ/FxcPX9E+H7Du51Qw/2F+5TBMSaE3G0hm+8D3eXJ6MAzFw75nE8V7xF0QvzUSdxIjJb/GA==
dependencies:
"@types/node" "*"
"@types/caseless@*":
version "0.12.2"
resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
@@ -980,6 +999,13 @@
"@types/minimatch" "*"
"@types/node" "*"
"@types/graceful-fs@^4.1.3":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==
dependencies:
"@types/node" "*"
"@types/inquirer@^6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-6.5.0.tgz#b83b0bf30b88b8be7246d40e51d32fe9d10e09be"
@@ -1064,6 +1090,11 @@
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.6.tgz#c880579e087d7a0db13777ff8af689f4ffc7b0d5"
integrity sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ==
"@types/uuid@8.3.1":
version "8.3.1"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f"
integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==
"@types/webpack-env@^1.13.9":
version "1.14.1"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.14.1.tgz#0d8a53f308f017c53a5ddc3d07f4d6fa76b790d7"
@@ -1812,6 +1843,11 @@ array-includes@^3.0.3:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.0"
array-timsort@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/array-timsort/-/array-timsort-1.0.3.tgz#3c9e4199e54fb2b9c3fe5976396a21614ef0d926"
integrity sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==
array-union@^1.0.1, array-union@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
@@ -2009,6 +2045,11 @@ base64-js@^1.0.2:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
base@^0.11.1:
version "0.11.2"
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
@@ -2252,6 +2293,13 @@ browserslist@^4.0.0, browserslist@^4.6.0, browserslist@^4.7.3, browserslist@^4.8
electron-to-chromium "^1.3.322"
node-releases "^1.1.42"
bson@^4.0.3:
version "4.4.1"
resolved "https://registry.yarnpkg.com/bson/-/bson-4.4.1.tgz#682c3cb8b90b222414ce14ef8398154ba2cc21bc"
integrity sha512-Uu4OCZa0jouQJCKOk1EmmyqtdWAP5HVLru4lQxTwzJzxT+sJ13lVpEZU/MATDxtHiekWMAL84oQY3Xn1LpJVSg==
dependencies:
buffer "^5.6.0"
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
@@ -2312,6 +2360,14 @@ buffer@^5.1.0, buffer@^5.2.1:
base64-js "^1.0.2"
ieee754 "^1.1.4"
buffer@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
builder-util-runtime@8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.3.0.tgz#f5fac9139af6facf42a21fbe4d3aebed88fda33e"
@@ -2879,6 +2935,17 @@ comment-json@^2.3.1:
has-own-prop "^2.0.0"
repeat-string "^1.6.1"
comment-json@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.1.0.tgz#09d08f0fbc4ad5eeccbac20f469adbb967dcbd2c"
integrity sha512-WEghmVYaNq9NlWbrkzQTSsya9ycLyxJxpTQfZEan6a5Jomnjw18zS3Podf8q1Zf9BvonvQd/+Z7Z39L7KKzzdQ==
dependencies:
array-timsort "^1.0.3"
core-util-is "^1.0.2"
esprima "^4.0.1"
has-own-prop "^2.0.0"
repeat-string "^1.6.1"
commitizen@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-4.0.3.tgz#c19a4213257d0525b85139e2f36db7cc3b4f6dae"
@@ -4189,6 +4256,16 @@ electron-chromedriver@^5.0.1:
electron-download "^4.1.1"
extract-zip "^1.6.7"
electron-devtools-installer@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/electron-devtools-installer/-/electron-devtools-installer-3.2.0.tgz#acc48d24eb7033fe5af284a19667e73b78d406d0"
integrity sha512-t3UczsYugm4OAbqvdImMCImIMVdFzJAHgbwHpkl5jmfu1izVgUcP/mnrPqJIpEeCK1uZGpt+yHgWEN+9EwoYhQ==
dependencies:
rimraf "^3.0.2"
semver "^7.2.1"
tslib "^2.1.0"
unzip-crx-3 "^0.2.0"
electron-download@^4.1.0, electron-download@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8"
@@ -5969,6 +6046,11 @@ icss-utils@^4.0.0, icss-utils@^4.1.1:
dependencies:
postcss "^7.0.14"
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ieee754@^1.1.4:
version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
@@ -6348,6 +6430,11 @@ is-directory@^0.3.1:
resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
is-docker@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -6559,6 +6646,13 @@ is-wsl@^1.1.0:
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
dependencies:
is-docker "^2.0.0"
is-yarn-global@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
@@ -7109,6 +7203,13 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
@@ -8289,10 +8390,10 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
picgo@^1.4.10:
version "1.4.10"
resolved "https://registry.npmjs.org/picgo/-/picgo-1.4.10.tgz#460b311663521e90c680c1ec1cf1edf1bcc7a294"
integrity sha512-mDFr1d4gh0BIhLAkCVKZFMzDLz2KwM0grbgqTCtLhZYXiOYihv8hOTArUlrffOiTD5vuB2P+a1LN6a7WNDVSeA==
picgo@^1.4.24:
version "1.4.24"
resolved "https://registry.yarnpkg.com/picgo/-/picgo-1.4.24.tgz#0d5a492e1f4d29823912535122fdc14928a6ddad"
integrity sha512-4QVTRIMLJes9c5vDuUl/c0TOEtUJycQhVdsXYtwx0fkKk/vhvfh0pRYUc33DEW88eGRi2CzAE2JJ93KHy5A8cg==
dependencies:
chalk "^2.4.1"
commander "^2.17.0"
@@ -8305,6 +8406,7 @@ picgo@^1.4.10:
globby "^8.0.2"
image-size "^0.8.3"
inquirer "^6.0.0"
is-wsl "^2.2.0"
lodash-id "^0.14.0"
lowdb "^1.0.0"
md5 "^2.2.1"
@@ -8908,6 +9010,11 @@ qiniu@^7.2.1:
tunnel-agent "0.6.0"
urllib "2.22.0"
qrcode.vue@^1.7.0:
version "1.7.0"
resolved "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-1.7.0.tgz#c54b2934f0650c10d92785d08aaad36c55e0fc56"
integrity sha512-R7t6Y3fDDtcU7L4rtqwGUDP9xD64gJhIwpfjhRCTKmBoYF6SS49PIJHRJ048cse6OI7iwTwgyy2C46N9Ygoc6g==
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@@ -9449,6 +9556,13 @@ rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3, rimraf@^2.7.1:
dependencies:
glob "^7.1.3"
rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
ripemd160@^2.0.0, ripemd160@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
@@ -9602,6 +9716,13 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.2.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.2.1:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@@ -10708,6 +10829,11 @@ tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
tslib@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
tslint@^5.20.1:
version "5.20.1"
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d"
@@ -10904,6 +11030,15 @@ unset-value@^1.0.0:
has-value "^0.3.1"
isobject "^3.0.0"
unzip-crx-3@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/unzip-crx-3/-/unzip-crx-3-0.2.0.tgz#d5324147b104a8aed9ae8639c95521f6f7cda292"
integrity sha512-0+JiUq/z7faJ6oifVB5nSwt589v1KCduqIJupNVDoWSXZtWDmjDGO3RAEOvwJ07w90aoXoP4enKsR7ecMrJtWQ==
dependencies:
jszip "^3.1.0"
mkdirp "^0.5.1"
yaku "^0.16.6"
unzip-crx@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/unzip-crx/-/unzip-crx-0.2.0.tgz#4c0baa8bdac756256754beca7843c13d7b858c18"
@@ -11079,11 +11214,24 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
uuid@8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^3.0.1, uuid@^3.3.2:
version "3.3.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
uuidv4@^6.2.11:
version "6.2.11"
resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.11.tgz#34d5a03324eb38296b87ae523a64233b5286cc27"
integrity sha512-OTS4waH9KplrXNADKo+Q1kT9AHWr8DaC0S5F54RQzEwcUaEzBEWQQlJyDUw/u1bkRhJyqkqhLD4M4lbFbV+89g==
dependencies:
"@types/uuid" "8.3.1"
uuid "8.3.2"
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"