Compare commits

...
84 Commits
Author SHA1 Message Date
Molunerfinn 53cdddf44a 2.0.5 2019-03-12 11:21:02 +08:00
Molunerfinn 2315aad732 Fixed: WIN10 powershell output encoding bug 2019-03-12 11:20:27 +08:00
Molunerfinn 7f8c6fd60e Fixed: #195 || windows7's bug of uploading image in clipboard 2019-03-11 13:40:39 +08:00
Molunerfinn 10959f3c54 2.0.4 2019-03-08 14:39:05 +08:00
Molunerfinn 1f4f40848e Updated: doc 2019-03-08 14:39:00 +08:00
Molunerfinn 7ef76b0a9b Fixed: #190 || copy image to upload correctly 2019-03-01 16:41:40 +08:00
Molunerfinn f87311760a Fixed: #189 || added change picbed button for upload page 2019-02-27 11:11:48 +08:00
Molunerfinn d597477a79 2.0.3 2019-01-31 16:41:06 +08:00
Molunerfinn 2bcbf749d1 Added: awesome list 2019-01-31 16:40:08 +08:00
PiEgg 59dd2c0b55 Fixed: weibo picbed docs link 2019-01-27 10:22:00 +08:00
Molunerfinn 43bace3212 Fixed: #176 || macOS tray window upload img failed 2019-01-25 19:16:52 +08:00
Molunerfinn 649e9f0695 Fixed: #175 || gallery page keep-alive 2019-01-24 11:29:48 +08:00
Molunerfinn e8f2185bbb Updated: copyright 2019-01-22 10:32:23 +08:00
Molunerfinn cc9dea5e71 2.0.2 2019-01-22 09:49:09 +08:00
Molunerfinn aabc0028af Fixed: custom url bugs & updated picgo version 2019-01-22 09:48:36 +08:00
Molunerfinn 193946c469 Updated: miniWindow style & add reload button for app 2019-01-21 16:46:33 +08:00
Molunerfinn 22c7b9a170 Fixed: #172 || miniWindowOntop option 2019-01-21 15:43:27 +08:00
PiEgg c5f6ae8993 Fixed: #171 || guiMenu is a function 2019-01-19 14:42:11 +08:00
Molunerfinn bb034677bd Fixed: logo url 2019-01-17 19:30:41 +08:00
Molunerfinn d2eb8999c5 Fixed: #168 || add new logo 2019-01-17 19:27:25 +08:00
Molunerfinn 4cb7acfec0 Updated: awesome-picgo 2019-01-16 10:27:34 +08:00
Molunerfinn 188b5e5148 2.0.1 2019-01-15 17:30:41 +08:00
Molunerfinn 0abdb3cf00 Fixed: #166 & add showNotification for guiApi 2019-01-15 17:28:50 +08:00
PiEgg 8704e212bf Updated: docs 2019-01-13 20:17:50 +08:00
PiEgg 4b92c90361 Updated: picgo-core version 2019-01-13 16:53:59 +08:00
PiEgg 2def54ec49 2.0.0 2019-01-13 16:30:39 +08:00
PiEgg 1d5142da79 Updated: README 2019-01-13 16:21:48 +08:00
PiEgg 8e3c51d0b9 Updated: homepage 2019-01-12 21:04:09 +08:00
Molunerfinn 0cc3f70cd7 Fixed: PATH in macOS 2019-01-12 19:19:26 +08:00
Molunerfinn a71ba33156 Fixed: bugs with imgFromClipboard 2019-01-12 16:09:24 +08:00
PiEgg 960e01f17f Added: ctx for guiApi 2019-01-12 13:47:44 +08:00
Molunerfinn 86265ee5c5 Updated: build version 2019-01-12 12:48:54 +08:00
PiEgg a6377696d9 Added: guiMenu for plugins 2019-01-11 21:13:16 +08:00
Molunerfinn 927d913f3b Added: guiApi for plugins 2019-01-11 17:22:33 +08:00
PiEgg 250a014fc1 Fixed: hide mini-window options in macOS 2019-01-07 19:37:20 +08:00
PiEgg 5e5a5f133a Fixed: transformer value 2019-01-02 22:56:26 +08:00
PiEgg 78d0baef7d Added: transformer setting 2019-01-01 23:44:00 +08:00
PiEgg 3490752e2f Fixed: title text-align style 2018-12-28 20:53:21 +08:00
Molunerfinn 2b94fe49a2 Finished: macOS new theme color 2018-12-28 20:30:47 +08:00
Molunerfinn d0fdbf1ea2 Updated: home page webpack config 2018-12-28 11:10:28 +08:00
Molunerfinn eddd39309e Updated: electron version->4 & webpack version -> 4 2018-12-25 17:28:12 +08:00
Molunerfinn 60ccd10e1a Finished: dynamic sync default picbed in menu 2018-12-25 16:11:10 +08:00
Molunerfinn 51cea7cdf0 Fixed: config sync between plugin page & picbed page 2018-12-25 15:24:32 +08:00
Molunerfinn f7dc41a927 Partly finished: plugin system 2018-12-24 16:05:30 +08:00
PiEgg ae9d8eee13 Finished: other picbed page 2018-12-23 23:15:00 +08:00
Molunerfinn 2d63f2ff42 Added: other-picbed page 2018-12-21 17:25:42 +08:00
PiEgg 778c910436 Fixed: #116 || added proxy option 2018-12-20 22:30:08 +08:00
Molunerfinn 405548426a Added: clipboard image handler for picgo-core 2018-12-20 17:23:03 +08:00
Molunerfinn 8ff7d15f8d Fixed: view-title style & open config file 2018-12-19 16:46:07 +08:00
Molunerfinn 5cf96d38b8 Added: open plugin hompage by click plugin name 2018-12-19 15:45:46 +08:00
PiEgg 8ec128121f Added: pasteStyle handler in gallery page 2018-12-19 11:52:27 +08:00
Molunerfinn be6cce9501 Added: picgo plugin handle states 2018-12-18 18:58:31 +08:00
PiEgg 72f2669cce Added: plugin reload state in localstorage 2018-12-17 23:56:11 +08:00
Molunerfinn cdf7e14548 Fixed: #145 || changed rename window display position for more reasonable 2018-12-13 17:05:08 +08:00
Molunerfinn 0fe28ce675 Changed: renderer files' structure 2018-12-10 17:13:48 +08:00
Molunerfinn 9d979a2b04 Fixed: progress display when upload from clipboard 2018-11-29 11:14:45 +08:00
PiEgg 9c12fb6ede Fixed: #140 || drag the image to app will be opened by Chrome & gallery close button 2018-11-25 23:53:26 +08:00
Molunerfinn 8a8b97a937 Fixed: #136 || multiple image timestamp rename bug 2018-11-22 16:55:34 +08:00
Molunerfinn 1af9af74ce Fixed: possible event listener memeory leak 2018-11-01 23:42:20 +08:00
Molunerfinn ca01d2c521 Fixed: #128 || tray window display bug in multi screen 2018-10-20 23:25:30 +08:00
Molunerfinn 094b83b30b Changed: npm search api url 2018-10-12 14:56:25 +08:00
Molunerfinn ea5b937c93 Fixed: some bugs 2018-10-10 17:25:44 +08:00
Molunerfinn c149211def Added: ipcRenderer remore listener 2018-10-09 17:11:24 +08:00
Molunerfinn 13c63ba66d Added: plugin install & uninstall handler 2018-10-09 17:01:33 +08:00
Molunerfinn dc1ba57a97 Fixed: plugin handler require bug 2018-09-29 11:32:44 +08:00
Molunerfinn 392af6c617 Added: plugin handler but with bugs 2018-09-28 17:31:28 +08:00
Molunerfinn 5909d35566 Fixed: image-size bug && change config all to settings 2018-09-27 17:22:29 +08:00
Molunerfinn 804072999e Fixed: aliyun imgUrl in list 2018-09-26 23:11:21 +08:00
Molunerfinn 17755b9809 Updated: vs-picgo 2018-09-26 20:13:48 +08:00
Molunerfinn c3b2d7b941 Fixed: bug with function in config 2018-09-20 15:14:19 +08:00
Molunerfinn 4a420dd5c5 Added: plugin config save 2018-09-20 14:49:20 +08:00
Molunerfinn 51281f345b Added: config form 2018-09-19 17:27:09 +08:00
Molunerfinn c8b4bebe1d Fixed: don't show mini-window in macOS 2018-09-18 19:08:26 +08:00
Molunerfinn aea0c3661d Added: picgo-core-ipc & fixed dynamic require bug in picgo 2018-09-18 18:48:55 +08:00
Molunerfinn cb32d43be4 Added: plugin view 2018-09-17 16:52:01 +08:00
Molunerfinn 4a0d398b4f Added: picgo-core support for uploading 2018-09-17 15:50:24 +08:00
Molunerfinn fb5a10d792 Updated: electron version 2018-09-14 18:49:12 +08:00
Molunerfinn 65dfb4ac05 Partly: added picgo package to PicGo 2018-09-14 17:31:57 +08:00
Molunerfinn f309d18aed Changed: auto start from settingWindow to miniWindow 2018-09-13 16:54:18 +08:00
Molunerfinn 6fca0e73c0 Added: miniWindowOntop option 2018-09-07 17:32:11 +08:00
Molunerfinn 0daafa3964 Updated: readme 2018-09-07 16:31:55 +08:00
Molunerfinn a2f1eec8d7 Fixed: some text error 2018-09-04 11:28:34 +08:00
Molunerfinn fc56d07ab2 Fixed: tray drag imgs 2018-08-08 14:43:49 +08:00
Molunerfinn cf504a0173 Fixed: sync upload img to gallery 2018-08-08 14:39:40 +08:00
69 changed files with 6270 additions and 4450 deletions
+14 -12
View File
@@ -12,7 +12,7 @@ const Multispinner = require('multispinner')
const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')
const webConfig = require('./webpack.web.config')
// const webConfig = require('./webpack.web.config')
const doneLog = chalk.bgGreen.white(' DONE ') + ' '
const errorLog = chalk.bgRed.white(' ERROR ') + ' '
@@ -72,6 +72,7 @@ function build () {
function pack (config) {
return new Promise((resolve, reject) => {
config.mode = 'production'
webpack(config, (err, stats) => {
if (err) reject(err.stack || err)
else if (stats.hasErrors()) {
@@ -97,19 +98,20 @@ function pack (config) {
})
}
function web () {
del.sync(['dist/web/*', '!.gitkeep'])
webpack(webConfig, (err, stats) => {
if (err || stats.hasErrors()) console.log(err)
// function web () {
// del.sync(['dist/web/*', '!.gitkeep'])
// webConfig.mode = 'production'
// webpack(webConfig, (err, stats) => {
// if (err || stats.hasErrors()) console.log(err)
console.log(stats.toString({
chunks: false,
colors: true
}))
// console.log(stats.toString({
// chunks: false,
// colors: true
// }))
process.exit()
})
}
// process.exit()
// })
// }
function greeting () {
const cols = process.stdout.columns
+22 -10
View File
@@ -41,21 +41,21 @@ function logStats (proc, data) {
function startRenderer () {
return new Promise((resolve, reject) => {
rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
rendererConfig.mode = 'development'
const compiler = webpack(rendererConfig)
hotMiddleware = webpackHotMiddleware(compiler, {
log: false,
heartbeat: 2500
hotMiddleware = webpackHotMiddleware(compiler, {
log: false,
heartbeat: 2500
})
compiler.plugin('compilation', compilation => {
compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => {
compiler.hooks.compilation.tap('compilation', compilation => {
compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
compiler.plugin('done', stats => {
compiler.hooks.done.tap('done', stats => {
logStats('Renderer', stats)
})
@@ -80,10 +80,10 @@ function startRenderer () {
function startMain () {
return new Promise((resolve, reject) => {
mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
mainConfig.mode = 'development'
const compiler = webpack(mainConfig)
compiler.plugin('watch-run', (compilation, done) => {
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
logStats('Main', chalk.white.bold('compiling...'))
hotMiddleware.publish({ action: 'compiling' })
done()
@@ -114,8 +114,20 @@ function startMain () {
}
function startElectron () {
electronProcess = spawn(electron, ['--inspect=5858', path.join(__dirname, '../dist/electron/main.js')])
var args = [
'--inspect=5858',
path.join(__dirname, '../dist/electron/main.js')
]
// detect yarn or npm and process commandline args accordingly
if (process.env.npm_execpath.endsWith('yarn.js')) {
args = args.concat(process.argv.slice(3))
} else if (process.env.npm_execpath.endsWith('npm-cli.js')) {
args = args.concat(process.argv.slice(2))
}
electronProcess = spawn(electron, args)
electronProcess.stdout.on('data', data => {
electronLog(data, 'blue')
})
+18 -12
View File
@@ -1,9 +1,11 @@
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const webpack = require('webpack')
module.exports = {
mode: 'development',
context: path.resolve(__dirname, '../docs'),
entry: './main.js',
output: {
@@ -28,12 +30,20 @@ module.exports = {
}
}
},
{
test: /\.styl(us)?$/,
use: [
'vue-style-loader',
'css-loader',
'stylus-loader'
]
},
{
test: /\.pug$/, loader: "pug-plain-loader"
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.html$/,
@@ -85,28 +95,24 @@ module.exports = {
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../docs/template.html')
}),
new ExtractTextPlugin("styles.css")
new MiniCssExtractPlugin({filename: 'styles.css'}),
]
}
if (process.env.NODE_ENV === 'production') {
// module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.mode = 'production'
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
+3 -3
View File
@@ -6,14 +6,14 @@ const path = require('path')
const { dependencies } = require('../package.json')
const webpack = require('webpack')
const babelMinifyWebpackPlugin = require('babel-minify-webpack-plugin')
const BabiliWebpackPlugin = require('babili-webpack-plugin')
let mainConfig = {
entry: {
main: path.join(__dirname, '../src/main/index.js')
},
externals: [
// ...Object.keys(dependencies || {})
...Object.keys(dependencies || {})
],
module: {
rules: [
@@ -73,7 +73,7 @@ if (process.env.NODE_ENV !== 'production') {
*/
if (process.env.NODE_ENV === 'production') {
mainConfig.plugins.push(
new babelMinifyWebpackPlugin(),
new BabiliWebpackPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
})
+23 -8
View File
@@ -8,8 +8,9 @@ const webpack = require('webpack')
const BabiliWebpackPlugin = require('babili-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
/**
* List of node_modules to include in webpack bundle
@@ -41,12 +42,21 @@ let rendererConfig = {
}
}
},
{
test: /\.styl(us)?$/,
use: [
'vue-style-loader',
'css-loader',
'stylus-loader'
]
},
{
test: /\.less$/,
use: ['vue-style-loader', 'css-loader', 'less-loader']
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.html$/,
@@ -69,7 +79,9 @@ let rendererConfig = {
extractCSS: process.env.NODE_ENV === 'production',
loaders: {
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
scss: 'vue-style-loader!css-loader!sass-loader'
scss: 'vue-style-loader!css-loader!sass-loader',
less: 'vue-style-loader!css-loader!less-loader',
stylus: 'vue-style-loader!css-loader!stylus-loader'
}
}
}
@@ -109,7 +121,8 @@ let rendererConfig = {
__filename: process.env.NODE_ENV !== 'production'
},
plugins: [
new ExtractTextPlugin('styles.css'),
new VueLoaderPlugin(),
new MiniCssExtractPlugin({filename: 'styles.css'}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
@@ -134,7 +147,9 @@ let rendererConfig = {
alias: {
'@': path.join(__dirname, '../src/renderer'),
'vue$': 'vue/dist/vue.esm.js',
'utils': path.join(__dirname, '../src/renderer/utils')
'utils': path.join(__dirname, '../src/renderer/utils'),
'~': path.join(__dirname, '../src'),
'root': path.join(__dirname, '../')
},
extensions: ['.js', '.vue', '.json', '.css', '.node']
},
+1 -1
View File
@@ -29,7 +29,7 @@ before_install:
install:
#- export DISPLAY=':99.0'
#- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
- nvm install 8.9
- nvm install 10
- curl -o- -L https://yarnpkg.com/install.sh | bash
- source ~/.bashrc
- npm install -g xvfb-maybe
+24 -9
View File
@@ -3,7 +3,7 @@
> 图片上传+管理新体验
<p align="center">
<img src="https://user-images.githubusercontent.com/12621342/33876119-85a5148e-df5f-11e7-8843-46224e595d52.png" alt="">
<img src="https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/New%20LOGO-150.png" alt="">
</p>
<p align="center">
<a href="https://github.com/feross/standard">
@@ -35,19 +35,30 @@ PicGo目前支持了
- `阿里云OSS` v1.6.0
- `Imgur` v1.6.0
未来将不增加默认的图床支持。待PicGo v2.0出来后你可以自行开发第三方图床插件。
**本体不再增加默认的图床支持。你可以自行开发第三方图床插件。详见[PicGo-Core](https://picgo.github.io/PicGo-Core-Doc/)**
第三方插件以及使用了PicGo底层的应用可以在[Awesome-PicGo](https://github.com/PicGo/Awesome-PicGo)看到。欢迎贡献!
支持macOS、windows 64位(>= v1.3.1),linux>= v1.6.0)。
支持快捷键`command+shift+p`macOS)或者`control+shift+p`windows\linux)用以支持快捷上传剪贴板里的图片(第一张)。PicGo v1.4.0版本及以上支持自定义快捷键,使用方法见[配置手册](https://github.com/Molunerfinn/PicGo/wiki/%E8%AF%A6%E7%BB%86%E7%AA%97%E5%8F%A3%E7%9A%84%E4%BD%BF%E7%94%A8#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%BF%AB%E6%8D%B7%E9%94%AE)。
**如果第一次使用,请参考应用使用[手册](https://github.com/Molunerfinn/PicGo/wiki)。遇到问题了还可以看看[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)macOS用户请下载最新版本的`dmg`文件,windows用户请下载最新版本的`exe`文件,linux用户请下载`AppImage`文件。**如果是国内用户下载github release文件速度很慢的话,推荐使用[Free Download Manager](http://www.freedownloadmanager.org/download.htm)来下载,速度会快。**
支持快捷键`command+shift+p`macOS)或者`control+shift+p`windows\linux)用以支持快捷上传剪贴板里的图片(第一张)。
PicGo支持自定义快捷键,使用方法见[配置手册](https://picgo.github.io/PicGo-Doc/zh/guide/config.html)。
开发进度可以查看[Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度。
![](https://user-images.githubusercontent.com/12621342/34242857-d177930a-e658-11e7-9688-7405851dd5e5.gif)
**如果第一次使用,请参考应用使用[快速上手](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)。**
## 下载安装
macOS用户请下载最新版本的`dmg`文件,windows用户请下载最新版本的`exe`文件,linux用户请下载`AppImage`文件。
点击此处下载[应用](https://github.com/Molunerfinn/PicGo/releases)。
**如果你是Arch类的Linux用户,可以直接通过`aurman -S picgo-appimage`来安装PicGo。感谢@houbaron的贡献!**
## 应用截图
![](https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/picgo-2.0.gif)
![picgo-menubar](https://user-images.githubusercontent.com/12621342/34242310-b5056510-e655-11e7-8568-60ffd4f71910.gif)
@@ -86,6 +97,10 @@ npm run build
只需第一次构建的时候指定一下国内源即可。后续构建不需要特地指定。二进制文件下载在`~/.electron/`目录下。如果想要更新`electron`构建版本,可以删除`~/.electron/`目录,然后重新运行上一步,让`electron-builder`去下载最新的`electron`二进制文件。
## 其他相关
- [vs-picgo](https://github.com/Spades-S/vs-picgo)picgo的VSCode版。
## 赞助
如果你喜欢PicGo并且它对你确实有帮助,欢迎给我打赏一杯咖啡哈~
@@ -102,4 +117,4 @@ npm run build
[MIT](http://opensource.org/licenses/MIT)
Copyright (c) 2017 Molunerfinn
Copyright (c) 2017 - 2019 Molunerfinn
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

+26 -4
View File
@@ -4,13 +4,16 @@
.mask
img.logo(src="~icons/256x256.png", alt="PicGo")
h1.title PicGo
small(v-if="version") {{ version }}
h2.desc 图片上传+管理新体验
button.download(@click="goLink('https://github.com/Molunerfinn/picgo/releases')") 免费下载
button.download(@click="goLink('https://github.com/Molunerfinn/picgo')") 在github上查看
button.download(@click="goLink('https://picgo.github.io/PicGo-Doc/zh/guide/')") 查看文档
h3.desc
| 基于#[a(href="https://github.com/SimulatedGREG/electron-vue" target="_blank") electron-vue]开发
h3.desc
| 支持macOS,Windows,Linux
h3.desc
| 支持插件系统让PicGo更强大
#container.container-fluid
.row.ex-width
img.gallery.col-xs-10.col-xs-offset-1.col-md-offset-2.col-md-8(src="https://ws1.sinaimg.cn/large/8700af19gy1fmayjwttnbj218g0p0q4e")
@@ -23,13 +26,15 @@
.display-list__desc {{ item.desc }}
.row.ex-width.info
.col-xs-10.col-xs-offset-1
| &copy;2017 #[a(href="https://github.com/Molunerfinn" target="_blank") Molunerfinn]
| &copy;2017 - {{ year }} #[a(href="https://github.com/Molunerfinn" target="_blank") Molunerfinn]
</template>
<script>
export default {
name: '',
data () {
return {
version: '',
year: new Date().getFullYear(),
itemList: [
{
url: 'https://ws1.sinaimg.cn/large/8700af19ly1fma907llb5j20m30ed46a',
@@ -49,19 +54,32 @@ export default {
{
url: 'https://ws1.sinaimg.cn/large/8700af19ly1fmd5ck9m0wj20lr0cxmzs',
title: '可选图床',
desc: '默认支持微博图床、七牛图床、腾讯云COS、又拍云、GitHub、SM.MS、阿里云OSS、Imgur。方便不同图床的上传需求。'
desc: '默认支持微博图床、七牛图床、腾讯云COS、又拍云、GitHub、SM.MS、阿里云OSS、Imgur。方便不同图床的上传需求。2.0版本开始更可以自己开发插件实现其他图床的上传需求。'
},
{
url: 'https://ws1.sinaimg.cn/large/8700af19gy1fmayjwttnbj218g0p0q4e',
title: '多样链接',
desc: '支持4种默认剪贴板链接格式,以及一种自定义格式,让你的文本编辑游刃有余。'
desc: '支持5种默认剪贴板链接格式,包括一种自定义格式,让你的文本编辑游刃有余。'
},
{
url: 'https://i.loli.net/2019/01/12/5c39a2f60a32a.png',
title: '插件系统',
desc: '2.0版本开始支持插件系统,让PicGo发挥无限潜能,成为一个极致的效率工具。'
}
]
}
},
created () {
this.getVersion()
},
methods: {
goLink (link) {
window.open(link, '_blank')
},
async getVersion () {
const release = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
const res = await this.$http.get(release)
this.version = res.data.name
}
}
}
@@ -105,6 +123,10 @@ h1
font-size 36px
font-weight 300
margin 10px auto
text-align center
small
margin-left 10px
font-size 14px
.desc
font-weight 400
margin 20px auto 10px
+3
View File
@@ -1,6 +1,9 @@
import Vue from 'vue'
import App from './APP.vue'
import 'melody.css'
import axios from 'axios'
Vue.prototype.$http = axios
new Vue({
render: h => h(App)
+20 -20
View File
@@ -1,6 +1,6 @@
{
"name": "picgo",
"version": "1.6.2",
"version": "2.0.5",
"author": "Molunerfinn <marksz@teamsz.xyz>",
"description": "Easy to upload your pic & copy to write",
"license": "MIT",
@@ -73,8 +73,8 @@
"dependencies": {
"axios": "^0.16.1",
"dateformat": "^3.0.3",
"element-ui": "^2.0.5",
"fecha": "^2.3.3",
"element-ui": "^2.4.11",
"fix-path": "^2.1.0",
"fs-extra": "^4.0.2",
"image-size": "^0.6.1",
"keycode": "^2.1.9",
@@ -82,9 +82,8 @@
"lowdb": "^1.0.0",
"md5": "^2.2.1",
"melody.css": "^1.0.2",
"picgo": "^1.2.8",
"qiniu": "^7.1.1",
"request": "^2.83.0",
"request-promise": "^4.2.2",
"vue": "^2.3.3",
"vue-electron": "^1.0.6",
"vue-gallery": "^1.2.4",
@@ -96,7 +95,6 @@
"babel-core": "^6.25.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.1.1",
"babel-minify-webpack-plugin": "^0.2.0",
"babel-plugin-istanbul": "^4.1.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.0",
@@ -111,22 +109,21 @@
"css-loader": "^0.28.4",
"del": "^3.0.0",
"devtron": "^1.4.0",
"electron": "1.8.7",
"electron-builder": "^19.19.1",
"electron": "4.0.2",
"electron-builder": "^20.38.4",
"electron-debug": "^1.4.0",
"electron-devtools-installer": "^2.2.0",
"eslint": "^4.4.1",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.9.0",
"eslint-loader": "^2.1.1",
"eslint-plugin-html": "^3.1.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.1.1",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^0.11.2",
"html-webpack-plugin": "^2.30.1",
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"inject-loader": "^3.0.0",
"karma": "^1.3.0",
"karma-chai": "^0.1.0",
@@ -136,24 +133,27 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "^0.0.31",
"karma-webpack": "^2.0.1",
"mini-css-extract-plugin": "0.4.0",
"mocha": "^3.0.2",
"multispinner": "^0.2.1",
"node-loader": "^0.6.0",
"pug": "^2.0.0-rc.4",
"pug-loader": "^2.3.0",
"pug-plain-loader": "^1.0.0",
"require-dir": "^0.3.0",
"spectron": "^3.7.1",
"style-loader": "^0.18.2",
"style-loader": "^0.23.1",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.1",
"url-loader": "^0.5.9",
"url-loader": "^1.1.2",
"vue-html-loader": "^1.2.4",
"vue-loader": "^13.0.5",
"vue-style-loader": "^3.0.1",
"vue-loader": "^15.4.2",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.4.2",
"webpack": "^3.5.2",
"webpack-dev-server": "^2.7.1",
"webpack-hot-middleware": "^2.18.2",
"webpack-merge": "^4.1.0"
"webpack": "^4.15.1",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.4",
"webpack-hot-middleware": "^2.22.2",
"webpack-merge": "^4.1.3"
}
}
+45 -2
View File
@@ -5,6 +5,13 @@ import path from 'path'
import fs from 'fs-extra'
import { remote, app } from 'electron'
if (process.env.NODE_ENV !== 'development') {
global.__static = path.join(__dirname, '/static').replace(/\\/g, '\\\\')
}
if (process.env.DEBUG_ENV === 'debug') {
global.__static = path.join(__dirname, '../../static').replace(/\\/g, '\\\\')
}
const APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData')
@@ -29,10 +36,46 @@ if (!db.has('picBed').value()) {
}).write()
}
if (!db.has('shortKey').value()) {
db.set('shortKey', {
if (!db.has('settings.shortKey').value()) {
db.set('settings.shortKey', {
upload: 'CommandOrControl+Shift+P'
}).write()
}
// init generate clipboard image files
let clipboardFiles = getClipboardFiles()
if (!fs.pathExistsSync(path.join(STORE_PATH, 'windows.ps1'))) {
clipboardFiles.forEach(item => {
fs.copyFileSync(item.origin, item.dest)
})
} else {
clipboardFiles.forEach(item => {
diffFilesAndUpdate(item.origin, item.dest)
})
}
function diffFilesAndUpdate (filePath1, filePath2) {
let file1 = fs.readFileSync(filePath1)
let file2 = fs.readFileSync(filePath2)
if (!file1.equals(file2)) {
fs.copyFileSync(filePath1, filePath2)
}
}
function getClipboardFiles () {
let files = [
'/linux.sh',
'/mac.applescript',
'/windows.ps1'
]
return files.map(item => {
return {
origin: path.join(__static, item),
dest: path.join(STORE_PATH, item)
}
})
}
export default db
-21
View File
@@ -1,21 +0,0 @@
import weiboUpload from '../main/utils/weiboUpload'
import qiniuUpload from '../main/utils/qiniuUpload'
import tcYunUpload from '../main/utils/tcYunUpload'
import upYunUpload from '../main/utils/upYunUpload'
import githubUpload from '../main/utils/githubUpload'
import smmsUpload from '../main/utils/smmsUpload'
import aliYunUpload from '../main/utils/aliYunUpload'
import imgurUpload from '../main/utils/imgurUpload'
const picBedHandler = {
weibo: weiboUpload,
qiniu: qiniuUpload,
tcyun: tcYunUpload,
upyun: upYunUpload,
github: githubUpload,
smms: smmsUpload,
aliyun: aliYunUpload,
imgur: imgurUpload
}
export default picBedHandler
+1 -15
View File
@@ -7,9 +7,6 @@
/* eslint-disable */
// Set environment for development
process.env.NODE_ENV = 'development'
// Install `electron-debug` with `devtron`
require('electron-debug')({ showDevTools: false })
@@ -24,15 +21,4 @@ require('electron').app.on('ready', () => {
})
// Require `main` process to boot app
if (process.env.DEBUG_ENV === 'debug') {
require('babel-core/register')({
'presets': [
['env', {
'targets': {
'node': true
}
}]
]
})
}
require('./index')
require('./index')
+87 -100
View File
@@ -1,12 +1,14 @@
'use strict'
import uploader from './utils/uploader.js'
import Uploader from './utils/uploader.js'
import { app, BrowserWindow, Tray, Menu, Notification, clipboard, ipcMain, globalShortcut, dialog } from 'electron'
import db from '../datastore'
import pasteTemplate from './utils/pasteTemplate'
import updateChecker from './utils/updateChecker'
import { getPicBeds } from './utils/getPicBeds'
import pkg from '../../package.json'
import picBed from '../datastore/pic-bed'
import picgoCoreIPC from './utils/picgoCoreIPC'
import fixPath from 'fix-path'
/**
* Set `__static` path to static files in production
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
@@ -34,18 +36,12 @@ const miniWinURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/#mini-page`
: `file://${__dirname}/index.html#mini-page`
const uploadFailed = () => {
const notification = new Notification({
title: '上传失败',
body: '请检查你的图床配置!'
})
notification.show()
}
// fix the $PATH in macOS
fixPath()
function createTray () {
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
tray = new Tray(menubarPic)
const submenu = picBed.map(item => {
function createContextMenu () {
const picBeds = getPicBeds(app)
const submenu = picBeds.map(item => {
return {
label: item.name,
type: 'radio',
@@ -92,10 +88,17 @@ function createTray () {
{
label: '打开更新助手',
type: 'checkbox',
checked: db.get('picBed.showUpdateTip').value(),
checked: db.get('settings.showUpdateTip').value(),
click () {
const value = db.read().get('picBed.showUpdateTip').value()
db.read().set('picBed.showUpdateTip', !value).write()
const value = db.read().get('settings.showUpdateTip').value()
db.read().set('settings.showUpdateTip', !value).write()
}
},
{
label: '重启应用',
click () {
app.relaunch()
app.exit(0)
}
},
{
@@ -103,13 +106,19 @@ function createTray () {
label: '退出'
}
])
}
function createTray () {
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
tray = new Tray(menubarPic)
tray.on('right-click', () => {
if (window) {
window.hide()
}
createContextMenu()
tray.popUpContextMenu(contextMenu)
})
tray.on('click', () => {
tray.on('click', (event, bounds) => {
if (process.platform === 'darwin') {
let img = clipboard.readImage()
let obj = []
@@ -122,7 +131,7 @@ function createTray () {
imgUrl
})
}
toggleWindow()
toggleWindow(bounds)
setTimeout(() => {
window.webContents.send('clipboardFiles', obj)
}, 0)
@@ -137,6 +146,9 @@ function createTray () {
settingWindow.show()
settingWindow.focus()
}
if (miniWindow) {
miniWindow.hide()
}
}
})
@@ -149,11 +161,12 @@ function createTray () {
})
tray.on('drop-files', async (event, files) => {
const pasteStyle = db.read().get('picBed.pasteStyle').value() || 'markdown'
const imgs = await uploader(files, 'imgFromPath', window.webContents)
const pasteStyle = db.read().get('settings.pasteStyle').value() || 'markdown'
const imgs = await new Uploader(files, window.webContents).upload()
if (imgs !== false) {
for (let i in imgs) {
clipboard.writeText(pasteTemplate(pasteStyle, imgs[i].imgUrl))
const url = imgs[i].url || imgs[i].imgUrl
clipboard.writeText(pasteTemplate(pasteStyle, url))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl,
@@ -162,10 +175,9 @@ function createTray () {
setTimeout(() => {
notification.show()
}, i * 100)
db.read().get('uploaded').insert(imgs[i]).write()
}
window.webContents.send('dragFiles', imgs)
} else {
uploadFailed()
}
})
// toggleWindow()
@@ -207,9 +219,10 @@ const createMiniWidow = () => {
let obj = {
height: 64,
width: 64,
show: false,
show: true,
frame: false,
fullscreenable: false,
skipTaskbar: true,
resizable: false,
transparent: true,
icon: `${__static}/logo.png`,
@@ -222,6 +235,14 @@ const createMiniWidow = () => {
obj.transparent = false
}
if (process.platform === 'darwin') {
obj.show = false
}
if (db.read().get('settings.miniWindowOntop').value()) {
obj.alwaysOnTop = true
}
miniWindow = new BrowserWindow(obj)
miniWindow.loadURL(miniWinURL)
@@ -245,11 +266,12 @@ const createSettingWindow = () => {
transparent: true,
titleBarStyle: 'hidden',
webPreferences: {
backgroundThrottling: false
backgroundThrottling: false,
webSecurity: false
}
}
if (process.platform !== 'darwin') {
options.show = true
options.show = false
options.frame = false
options.backgroundColor = '#3f3c37'
options.transparent = false
@@ -295,63 +317,41 @@ const createMenu = () => {
}
}
const getWindowPosition = () => {
const windowBounds = window.getBounds()
const trayBounds = tray.getBounds()
const x = Math.round(trayBounds.x + (trayBounds.width / 2) - (windowBounds.width / 2))
const y = Math.round(trayBounds.y + trayBounds.height - 10)
return {
x,
y
}
}
const toggleWindow = () => {
const toggleWindow = (bounds) => {
if (window.isVisible()) {
window.hide()
} else {
showWindow()
showWindow(bounds)
}
}
const showWindow = () => {
const position = getWindowPosition()
window.setPosition(position.x, position.y, false)
const showWindow = (bounds) => {
window.setPosition(bounds.x - 98 + 11, bounds.y, false)
window.webContents.send('updateFiles')
window.show()
window.focus()
}
const uploadClipboardFiles = async () => {
let img = clipboard.readImage()
let uploadImg = null
if (!img.isEmpty()) {
// 从剪贴板来的图片默认转为png
const imgUrl = 'data:image/png;base64,' + Buffer.from(img.toPNG(), 'binary').toString('base64')
uploadImg = {
width: img.getSize().width,
height: img.getSize().height,
imgUrl
}
}
let win
if (miniWindow.isVisible) {
if (miniWindow.isVisible()) {
win = miniWindow
} else {
win = settingWindow || window
}
img = await uploader(uploadImg, 'imgFromClipboard', win.webContents)
let img = await new Uploader(undefined, win.webContents).upload()
if (img !== false) {
if (img.length > 0) {
const pasteStyle = db.read().get('picBed.pasteStyle').value() || 'markdown'
clipboard.writeText(pasteTemplate(pasteStyle, img[0].imgUrl))
const pasteStyle = db.read().get('settings.pasteStyle').value() || 'markdown'
const url = img[0].url || img[0].imgUrl
clipboard.writeText(pasteTemplate(pasteStyle, url))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl,
icon: img[0].imgUrl
})
notification.show()
db.read().get('uploaded').insert(img[0]).write()
window.webContents.send('clipboardFiles', [])
window.webContents.send('uploadFiles', img)
if (settingWindow) {
@@ -364,32 +364,18 @@ const uploadClipboardFiles = async () => {
})
notification.show()
}
} else {
uploadFailed()
}
}
const updateDefaultPicBed = () => {
if (process.platform === 'darwin' || process.platform === 'win32') {
const types = picBed.map(item => item.type)
let submenuItem = contextMenu.items[2].submenu.items
submenuItem.forEach((item, index) => {
const result = db.read().get('picBed.current').value() === types[index]
if (result) {
item.click() // It's a bug which can not set checked status
return true
}
})
} else {
return false
}
}
picgoCoreIPC(app, ipcMain)
// from macOS tray
ipcMain.on('uploadClipboardFiles', async (evt, file) => {
const img = await uploader(file, 'imgFromClipboard', window.webContents)
const img = await new Uploader(undefined, window.webContents).upload()
if (img !== false) {
const pasteStyle = db.read().get('picBed.pasteStyle').value() || 'markdown'
clipboard.writeText(pasteTemplate(pasteStyle, img[0].imgUrl))
const pasteStyle = db.read().get('settings.pasteStyle').value() || 'markdown'
const url = img[0].url || img[0].imgUrl
clipboard.writeText(pasteTemplate(pasteStyle, url))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl,
@@ -397,14 +383,13 @@ ipcMain.on('uploadClipboardFiles', async (evt, file) => {
icon: img[0].imgUrl
})
notification.show()
db.read().get('uploaded').insert(img[0]).write()
window.webContents.send('clipboardFiles', [])
window.webContents.send('uploadFiles', img)
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
} else {
uploadFailed()
}
window.webContents.send('uploadFiles')
})
ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
@@ -412,12 +397,14 @@ ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
})
ipcMain.on('uploadChoosedFiles', async (evt, files) => {
const imgs = await uploader(files, 'imgFromUploader', evt.sender)
const input = files.map(item => item.path)
const imgs = await new Uploader(input, evt.sender).upload()
if (imgs !== false) {
const pasteStyle = db.read().get('picBed.pasteStyle').value() || 'markdown'
const pasteStyle = db.read().get('settings.pasteStyle').value() || 'markdown'
let pasteText = ''
for (let i in imgs) {
pasteText += pasteTemplate(pasteStyle, imgs[i].imgUrl) + '\r\n'
const url = imgs[i].url || imgs[i].imgUrl
pasteText += pasteTemplate(pasteStyle, url) + '\r\n'
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl,
@@ -426,21 +413,20 @@ ipcMain.on('uploadChoosedFiles', async (evt, files) => {
setTimeout(() => {
notification.show()
}, i * 100)
db.read().get('uploaded').insert(imgs[i]).write()
}
clipboard.writeText(pasteText)
window.webContents.send('uploadFiles', imgs)
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
} else {
uploadFailed()
}
})
ipcMain.on('updateShortKey', (evt, oldKey) => {
globalShortcut.unregisterAll()
for (let key in oldKey) {
globalShortcut.register(db.read().get('shortKey').value()[key], () => {
globalShortcut.register(db.read().get('settings.shortKey').value()[key], () => {
return shortKeyHash[key]()
})
}
@@ -483,34 +469,34 @@ ipcMain.on('openMiniWindow', (evt) => {
settingWindow.hide()
})
// update
ipcMain.on('updateDefaultPicBed', (evt) => {
updateDefaultPicBed()
})
// from mini window
ipcMain.on('syncPicBed', (evt) => {
if (settingWindow) {
settingWindow.webContents.send('syncPicBed')
}
updateDefaultPicBed()
})
ipcMain.on('getPicBeds', (evt) => {
const picBeds = getPicBeds(app)
evt.sender.send('getPicBeds', picBeds)
evt.returnValue = picBeds
})
const shortKeyHash = {
upload: uploadClipboardFiles
}
const isSecondInstance = app.makeSingleInstance(() => {
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
if (settingWindow) {
if (settingWindow.isMinimized()) {
settingWindow.restore()
}
settingWindow.focus()
}
})
if (isSecondInstance) {
app.quit()
}
if (process.platform === 'win32') {
@@ -527,9 +513,10 @@ app.on('ready', () => {
if (process.platform === 'darwin' || process.platform === 'win32') {
createTray()
}
db.read().set('needReload', false).write()
updateChecker()
globalShortcut.register(db.read().get('shortKey.upload').value(), () => {
globalShortcut.register(db.read().get('settings.shortKey.upload').value(), () => {
uploadClipboardFiles()
})
})
@@ -554,7 +541,7 @@ app.on('will-quit', () => {
})
app.setLoginItemSettings({
openAtLogin: db.read().get('picBed.autoStart').value() || false
openAtLogin: db.read().get('settings.autoStart').value() || false
})
/**
-83
View File
@@ -1,83 +0,0 @@
import request from 'request-promise'
import * as img2Base64 from './img2base64'
import db from '../../datastore/index'
import { Notification, clipboard } from 'electron'
import crypto from 'crypto'
import mime from 'mime-types'
// generate OSS signature
const generateSignature = (fileName) => {
const options = db.read().get('picBed.aliyun').value()
const date = new Date().toGMTString()
const signString = `PUT\n\n${mime.lookup(fileName)}\n${date}\n/${options.bucket}/${options.path}${fileName}`
const signature = crypto.createHmac('sha1', options.accessKeySecret).update(signString).digest('base64')
return `OSS ${options.accessKeyId}:${signature}`
}
const postOptions = (fileName, signature, imgBase64) => {
const options = db.read().get('picBed.aliyun').value()
return {
method: 'PUT',
url: `https://${options.bucket}.${options.area}.aliyuncs.com/${encodeURI(options.path)}${encodeURI(fileName)}`,
headers: {
Host: `${options.bucket}.${options.area}.aliyuncs.com`,
Authorization: signature,
Date: new Date().toGMTString(),
'content-type': mime.lookup(fileName)
},
body: Buffer.from(imgBase64, 'base64'),
resolveWithFullResponse: true
}
}
const aliYunUpload = async (img, type, webContents) => {
try {
webContents.send('uploadProgress', 0)
const imgList = await img2Base64[type](img)
webContents.send('uploadProgress', 30)
const aliYunOptions = db.read().get('picBed.aliyun').value()
const customUrl = aliYunOptions.customUrl
const path = aliYunOptions.path
const length = imgList.length
generateSignature()
for (let i in imgList) {
const signature = generateSignature(imgList[i].fileName)
const options = postOptions(imgList[i].fileName, signature, imgList[i].base64Image)
let body = await request(options)
if (body.statusCode === 200) {
delete imgList[i].base64Image
if (customUrl) {
imgList[i]['imgUrl'] = `${customUrl}/${path}${imgList[i].fileName}`
} else {
imgList[i]['imgUrl'] = `https://${aliYunOptions.bucket}.${aliYunOptions.area}.aliyuncs.com/${path}${imgList[0].fileName}`
}
imgList[i]['type'] = 'aliyun'
if (i - length === -1) {
webContents.send('uploadProgress', 60)
}
} else {
webContents.send('uploadProgress', -1)
const notification = new Notification({
title: '上传失败!',
body: '上传失败!'
})
notification.show()
return false
}
}
webContents.send('uploadProgress', 100)
return imgList
} catch (err) {
webContents.send('uploadProgress', -1)
const notification = new Notification({
title: '上传失败!',
body: `请检查你的配置项是否正确`
})
notification.show()
clipboard.writeText('https://cloud.tencent.com/document/product/436/8432')
throw new Error(err)
}
}
export default aliYunUpload
+27
View File
@@ -0,0 +1,27 @@
import path from 'path'
import db from '../../datastore'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const getPicBeds = (app) => {
const PicGo = requireFunc('picgo')
const STORE_PATH = app.getPath('userData')
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
const picgo = new PicGo(CONFIG_PATH)
const picBedTypes = picgo.helper.uploader.getIdList()
const picBedFromDB = db.read().get('picBed.list').value() || []
const picBeds = picBedTypes.map(item => {
const visible = picBedFromDB.find(i => i.type === item) // object or undefined
return {
type: item,
name: picgo.helper.uploader.get(item).name || item,
visible: visible ? visible.visible : true
}
})
picgo.cmd.program.removeAllListeners()
return picBeds
}
export {
getPicBeds
}
-66
View File
@@ -1,66 +0,0 @@
import request from 'request-promise'
import * as img2Base64 from './img2base64'
import db from '../../datastore/index'
import { Notification } from 'electron'
const postOptions = (fileName, options, data) => {
const path = options.path || ''
const {token, repo} = options
return {
method: 'PUT',
url: `https://api.github.com/repos/${repo}/contents/${encodeURI(path)}${encodeURI(fileName)}`,
headers: {
Authorization: `token ${token}`,
'User-Agent': 'PicGo'
},
body: data,
json: true
}
}
const githubUpload = async function (img, type, webContents) {
try {
webContents.send('uploadProgress', 0)
const imgList = await img2Base64[type](img)
const length = imgList.length
const githubOptions = db.read().get('picBed.github').value()
webContents.send('uploadProgress', 30)
for (let i in imgList) {
const data = {
message: 'Upload by PicGo',
branch: githubOptions.branch,
content: imgList[i].base64Image,
path: githubOptions.path + encodeURI(imgList[i].fileName)
}
const postConfig = postOptions(imgList[i].fileName, githubOptions, data)
const body = await request(postConfig)
if (body) {
delete imgList[i].base64Image
if (githubOptions.customUrl) {
imgList[i]['imgUrl'] = `${githubOptions.customUrl}/${githubOptions.path}${imgList[i].fileName}`
} else {
imgList[i]['imgUrl'] = body.content.download_url
}
imgList[i]['type'] = 'github'
if (i - length === -1) {
webContents.send('uploadProgress', 60)
}
} else {
webContents.send('uploadProgress', -1)
return new Error()
}
}
webContents.send('uploadProgress', 100)
return imgList
} catch (err) {
webContents.send('uploadProgress', -1)
const notification = new Notification({
title: '上传失败!',
body: '服务端出错,请重试'
})
notification.show()
throw new Error(err)
}
}
export default githubUpload
+105
View File
@@ -0,0 +1,105 @@
import {
dialog,
BrowserWindow,
clipboard,
Notification
} from 'electron'
import db from '../../datastore'
import Uploader from './uploader'
import pasteTemplate from './pasteTemplate'
const WEBCONTENTS = Symbol('WEBCONTENTS')
const IPCMAIN = Symbol('IPCMAIN')
const PICGO = Symbol('PICGO')
class GuiApi {
constructor (ipcMain, webcontents, picgo) {
this[WEBCONTENTS] = webcontents
this[IPCMAIN] = ipcMain
this[PICGO] = picgo
}
/**
* for plugin showInputBox
* @param {object} options
* return type is string or ''
*/
showInputBox (options) {
if (options === undefined) {
options = {
title: '',
placeholder: ''
}
}
this[WEBCONTENTS].send('showInputBox', options)
return new Promise((resolve, reject) => {
this[IPCMAIN].once('showInputBox', (event, value) => {
resolve(value)
})
})
}
/**
* for plugin show file explorer
* @param {object} options
*/
showFileExplorer (options) {
if (options === undefined) {
options = {}
}
return new Promise((resolve, reject) => {
dialog.showOpenDialog(BrowserWindow.fromWebContents(this[WEBCONTENTS]), options, filename => {
resolve(filename)
})
})
}
/**
* for plugin to upload file
* @param {array} input
*/
async upload (input) {
const imgs = await new Uploader(input, this[WEBCONTENTS], this[PICGO]).upload()
if (imgs !== false) {
const pasteStyle = db.read().get('settings.pasteStyle').value() || 'markdown'
let pasteText = ''
for (let i in imgs) {
const url = imgs[i].url || imgs[i].imgUrl
pasteText += pasteTemplate(pasteStyle, url) + '\r\n'
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl,
icon: imgs[i].imgUrl
})
setTimeout(() => {
notification.show()
}, i * 100)
db.read().get('uploaded').insert(imgs[i]).write()
}
clipboard.writeText(pasteText)
this[WEBCONTENTS].send('uploadFiles', imgs)
this[WEBCONTENTS].send('updateGallery')
return imgs
}
return []
}
/**
* For notification
* @param {Object} options
*/
showNotification (options) {
if (options === undefined) {
options = {
title: '',
body: ''
}
}
const notification = new Notification({
title: options.title,
body: options.body
})
notification.show()
}
}
export default GuiApi
-146
View File
@@ -1,146 +0,0 @@
import fs from 'fs-extra'
import path from 'path'
import sizeOf from 'image-size'
import fecha from 'fecha'
import { BrowserWindow, ipcMain } from 'electron'
import db from '../../datastore/index.js'
const renameURL = process.env.NODE_ENV === 'development' ? `http://localhost:9080/#rename-page` : `file://${__dirname}/index.html#rename-page`
const createRenameWindow = () => {
let options = {
height: 175,
width: 300,
show: true,
fullscreenable: false,
resizable: false,
vibrancy: 'ultra-dark',
webPreferences: {
backgroundThrottling: false
}
}
if (process.platform !== 'darwin') {
options.show = true
options.backgroundColor = '#3f3c37'
options.autoHideMenuBar = true
options.transparent = false
}
const window = new BrowserWindow(options)
window.loadURL(renameURL)
return window
}
const imgFromPath = async (imgPath) => {
let results = []
const rename = db.read().get('picBed.rename').value()
const autoRename = db.read().get('picBed.autoRename').value()
await Promise.all(imgPath.map(async item => {
let name
let fileName
if (autoRename) {
fileName = fecha.format(new Date(), 'YYYYMMDDHHmmss') + path.extname(item)
} else {
fileName = path.basename(item)
}
if (rename) {
const window = createRenameWindow()
await waitForShow(window.webContents)
window.webContents.send('rename', fileName, window.webContents.id)
name = await waitForRename(window, window.webContents.id)
}
let buffer = await fs.readFile(item)
let base64Image = Buffer.from(buffer).toString('base64')
let imgSize = sizeOf(item)
results.push({
base64Image,
fileName: name || fileName,
width: imgSize.width,
height: imgSize.height,
extname: path.extname(item)
})
}))
return results
}
const imgFromClipboard = async (file) => {
let result = []
let rename = db.read().get('picBed.rename').value()
if (file !== null) {
let name
const today = fecha.format(new Date(), 'YYYYMMDDHHmmss') + '.png'
if (rename) {
const window = createRenameWindow()
await waitForShow(window.webContents)
window.webContents.send('rename', today, window.webContents.id)
name = await waitForRename(window, window.webContents.id)
}
result.push({
base64Image: file.imgUrl.replace(/^data\S+,/, ''),
fileName: name || today,
width: file.width,
height: file.height,
extname: '.png'
})
}
return result
}
const imgFromUploader = async (files) => {
let results = []
const rename = db.read().get('picBed.rename').value()
const autoRename = db.read().get('picBed.autoRename').value()
await Promise.all(files.map(async item => {
let name
let fileName
if (autoRename) {
fileName = fecha.format(new Date(), 'YYYYMMDDHHmmss') + path.extname(item.name)
} else {
fileName = path.basename(item.path)
}
if (rename) {
const window = createRenameWindow()
await waitForShow(window.webContents)
window.webContents.send('rename', fileName, window.webContents.id)
name = await waitForRename(window, window.webContents.id)
}
let buffer = await fs.readFile(item.path)
let base64Image = Buffer.from(buffer).toString('base64')
let imgSize = sizeOf(item.path)
results.push({
base64Image,
fileName: name || fileName,
width: imgSize.width,
height: imgSize.height,
extname: path.extname(item.name)
})
}))
return results
}
const waitForShow = (webcontent) => {
return new Promise((resolve, reject) => {
webcontent.on('dom-ready', () => {
resolve()
})
})
}
const waitForRename = (window, id) => {
return new Promise((resolve, reject) => {
ipcMain.once(`rename${id}`, (evt, newName) => {
resolve(newName)
window.hide()
})
window.on('close', () => {
resolve(null)
ipcMain.removeAllListeners(`rename${id}`)
})
})
}
export {
imgFromPath,
imgFromClipboard,
imgFromUploader
}
-65
View File
@@ -1,65 +0,0 @@
import request from 'request-promise'
import * as img2Base64 from './img2base64'
import db from '../../datastore/index'
import { Notification, clipboard } from 'electron'
const postOptions = (fileName, imgBase64) => {
const options = db.read().get('picBed.imgur').value()
const clientId = options.clientId
let obj = {
method: 'POST',
url: `https://api.imgur.com/3/image`,
headers: {
Authorization: 'Client-ID ' + clientId,
'content-type': 'multipart/form-data',
'User-Agent': 'PicGo'
},
formData: {
image: imgBase64,
type: 'base64',
name: fileName
}
}
if (options.proxy) {
obj.proxy = options.proxy
}
return obj
}
const imgurUpload = async (img, type, webContents) => {
try {
webContents.send('uploadProgress', 0)
const imgList = await img2Base64[type](img)
webContents.send('uploadProgress', 30)
const length = imgList.length
for (let i in imgList) {
const options = postOptions(imgList[i].fileName, imgList[i].base64Image)
let body = await request(options)
body = JSON.parse(body)
if (body.success) {
delete imgList[i].base64Image
imgList[i]['imgUrl'] = `${body.data.link}`
imgList[i]['type'] = 'imgur'
if (i - length === -1) {
webContents.send('uploadProgress', 60)
}
} else {
webContents.send('uploadProgress', -1)
return new Error()
}
}
webContents.send('uploadProgress', 100)
return imgList
} catch (err) {
webContents.send('uploadProgress', -1)
const notification = new Notification({
title: '上传失败!',
body: `请检查你的配置以及网络!`
})
notification.show()
clipboard.writeText('http://docs.imgur.com/api/errno/')
throw new Error(err)
}
}
export default imgurUpload
+1 -1
View File
@@ -1,7 +1,7 @@
import db from '../../datastore'
export default (style, url) => {
const customLink = db.read().get('customLink').value() || '$url'
const customLink = db.read().get('settings.customLink').value() || '$url'
const tpl = {
'markdown': `![](${url})`,
'HTML': `<img src="${url}"/>`,
+174
View File
@@ -0,0 +1,174 @@
import path from 'path'
import GuiApi from './guiApi'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo')
const PluginHandler = requireFunc('picgo/dist/lib/PluginHandler').default
// get uploader or transformer config
const getConfig = (name, type, ctx) => {
let config = []
if (name === '') {
return config
} else {
const handler = ctx.helper[type].get(name)
if (handler) {
if (handler.config) {
config = handler.config(ctx)
}
}
return config
}
}
const handleConfigWithFunction = config => {
for (let i in config) {
if (typeof config[i].default === 'function') {
config[i].default = config[i].default()
}
if (typeof config[i].choices === 'function') {
config[i].choices = config[i].choices()
}
}
return config
}
const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => {
ipcMain.on('getPluginList', event => {
const picgo = new PicGo(CONFIG_PATH)
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 = {
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, 'uploader', picgo))
},
transformer: {
name: transformerName,
config: handleConfigWithFunction(getConfig(uploaderName, 'transformer', picgo))
}
},
enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`),
homepage: pluginPKG.homepage ? pluginPKG.homepage : '',
guiMenu: menu,
ing: false
}
list.push(obj)
}
event.sender.send('pluginList', list)
picgo.cmd.program.removeAllListeners()
})
}
const handlePluginInstall = (ipcMain, CONFIG_PATH) => {
ipcMain.on('installPlugin', (event, msg) => {
const picgo = new PicGo(CONFIG_PATH)
const pluginHandler = new PluginHandler(picgo)
picgo.on('installSuccess', notice => {
event.sender.send('installSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
})
pluginHandler.install([msg])
picgo.cmd.program.removeAllListeners()
})
}
const handlePluginUninstall = (ipcMain, CONFIG_PATH) => {
ipcMain.on('uninstallPlugin', (event, msg) => {
const picgo = new PicGo(CONFIG_PATH)
const pluginHandler = new PluginHandler(picgo)
picgo.on('uninstallSuccess', notice => {
event.sender.send('uninstallSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
})
pluginHandler.uninstall([msg])
picgo.cmd.program.removeAllListeners()
})
}
const handlePluginUpdate = (ipcMain, CONFIG_PATH) => {
ipcMain.on('updatePlugin', (event, msg) => {
const picgo = new PicGo(CONFIG_PATH)
const pluginHandler = new PluginHandler(picgo)
picgo.on('updateSuccess', notice => {
event.sender.send('updateSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
})
pluginHandler.update([msg])
picgo.cmd.program.removeAllListeners()
})
}
const handleGetPicBedConfig = (ipcMain, CONFIG_PATH) => {
ipcMain.on('getPicBedConfig', (event, type) => {
const picgo = new PicGo(CONFIG_PATH)
const name = picgo.helper.uploader.get(type).name || type
if (picgo.helper.uploader.get(type).config) {
const config = handleConfigWithFunction(picgo.helper.uploader.get(type).config(picgo))
event.sender.send('getPicBedConfig', config, name)
} else {
event.sender.send('getPicBedConfig', [], name)
}
picgo.cmd.program.removeAllListeners()
})
}
const handlePluginActions = (ipcMain, CONFIG_PATH) => {
ipcMain.on('pluginActions', (event, name, label) => {
const picgo = new PicGo(CONFIG_PATH)
const plugin = picgo.pluginLoader.getPlugin(`picgo-plugin-${name}`)
const guiApi = new GuiApi(ipcMain, event.sender, picgo)
if (plugin.guiMenu && plugin.guiMenu(picgo).length > 0) {
const menu = plugin.guiMenu(picgo)
menu.forEach(item => {
if (item.label === label) {
item.handle(picgo, guiApi)
}
})
}
})
}
const handleRemoveFiles = (ipcMain, CONFIG_PATH) => {
ipcMain.on('removeFiles', (event, files) => {
const picgo = new PicGo(CONFIG_PATH)
picgo.emit('remove', files)
})
}
export default (app, ipcMain) => {
const STORE_PATH = app.getPath('userData')
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
handleGetPluginList(ipcMain, STORE_PATH, CONFIG_PATH)
handlePluginInstall(ipcMain, CONFIG_PATH)
handlePluginUninstall(ipcMain, CONFIG_PATH)
handlePluginUpdate(ipcMain, CONFIG_PATH)
handleGetPicBedConfig(ipcMain, CONFIG_PATH)
handlePluginActions(ipcMain, CONFIG_PATH)
handleRemoveFiles(ipcMain, CONFIG_PATH)
}
-79
View File
@@ -1,79 +0,0 @@
import request from 'request-promise'
import * as img2Base64 from './img2base64'
import db from '../../datastore/index'
import * as qiniu from 'qiniu'
import { Notification } from 'electron'
function postOptions (fileName, token, imgBase64) {
const area = selectArea(db.read().get('picBed.qiniu.area').value() || 'z0')
const path = db.read().get('picBed.qiniu.path').value() || ''
const base64FileName = Buffer.from(path + fileName, 'utf-8').toString('base64').replace(/\+/g, '-').replace(/\//g, '_')
return {
method: 'POST',
url: `http://upload${area}.qiniu.com/putb64/-1/key/${base64FileName}`,
headers: {
Authorization: `UpToken ${token}`,
contentType: 'application/octet-stream'
},
body: imgBase64
}
}
function selectArea (area) {
return area === 'z0' ? '' : '-' + area
}
function getToken () {
const accessKey = db.read().get('picBed.qiniu.accessKey').value()
const secretKey = db.read().get('picBed.qiniu.secretKey').value()
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
const options = {
scope: db.read().get('picBed.qiniu.bucket').value()
}
const putPolicy = new qiniu.rs.PutPolicy(options)
return putPolicy.uploadToken(mac)
}
const qiniuUpload = async function (img, type, webContents) {
try {
webContents.send('uploadProgress', 0)
const imgList = await img2Base64[type](img)
webContents.send('uploadProgress', 30)
const length = imgList.length
for (let i in imgList) {
const options = postOptions(imgList[i].fileName, getToken(), imgList[i].base64Image)
const res = await request(options)
const body = JSON.parse(res)
if (body.key) {
delete imgList[i].base64Image
const baseUrl = db.get('picBed.qiniu.url').value()
const options = db.get('picBed.qiniu.options').value()
imgList[i]['imgUrl'] = `${baseUrl}/${body.key}${options}`
imgList[i]['type'] = 'qiniu'
if (i - length === -1) {
webContents.send('uploadProgress', 60)
}
} else {
webContents.send('uploadProgress', -1)
const notification = new Notification({
title: '上传失败!',
body: res.body.msg
})
notification.show()
}
}
webContents.send('uploadProgress', 100)
return imgList
} catch (err) {
webContents.send('uploadProgress', -1)
const error = JSON.parse(err.response.body)
const notification = new Notification({
title: '上传失败!',
body: error.error
})
notification.show()
throw new Error(err)
}
}
export default qiniuUpload
-56
View File
@@ -1,56 +0,0 @@
import request from 'request-promise'
import * as img2Base64 from './img2base64'
import { Notification } from 'electron'
const postOptions = (fileName, imgBase64) => {
return {
method: 'POST',
url: `https://sm.ms/api/upload`,
headers: {
contentType: 'multipart/form-data',
'User-Agent': 'PicGo'
},
formData: {
smfile: {
value: Buffer.from(imgBase64, 'base64'),
options: {
filename: fileName
}
},
ssl: 'true'
}
}
}
const smmsUpload = async function (img, type, webContents) {
try {
webContents.send('uploadProgress', 0)
const imgList = await img2Base64[type](img)
webContents.send('uploadProgress', 30)
for (let i in imgList) {
const postConfig = postOptions(imgList[i].fileName, imgList[i].base64Image)
let body = await request(postConfig)
body = JSON.parse(body)
if (body.code === 'success') {
delete imgList[i].base64Image
imgList[i]['imgUrl'] = body.data.url
imgList[i]['type'] = 'smms'
} else {
webContents.send('uploadProgress', -1)
return new Error()
}
}
webContents.send('uploadProgress', 100)
return imgList
} catch (err) {
webContents.send('uploadProgress', -1)
const notification = new Notification({
title: '上传失败!',
body: '服务端出错,请重试'
})
notification.show()
throw new Error(err)
}
}
export default smmsUpload
-166
View File
@@ -1,166 +0,0 @@
import request from 'request-promise'
import * as img2Base64 from './img2base64'
import db from '../../datastore/index'
import { Notification, clipboard } from 'electron'
import crypto from 'crypto'
import mime from 'mime-types'
// generate COS signature string
const generateSignature = (fileName) => {
const options = db.read().get('picBed.tcyun').value()
const secretId = options.secretId
const secretKey = options.secretKey
const appId = options.appId
const bucket = options.bucket
let signature
let signTime
if (!options.version || options.version === 'v4') {
const random = Math.floor(Math.random() * 10000000000)
const current = parseInt(new Date().getTime() / 1000) - 1
const expired = current + 3600
const multiSignature = `a=${appId}&b=${bucket}&k=${secretId}&e=${expired}&t=${current}&r=${random}&f=`
const signHexKey = crypto.createHmac('sha1', secretKey).update(multiSignature).digest()
const tempString = Buffer.concat([signHexKey, Buffer.from(multiSignature)])
signature = Buffer.from(tempString).toString('base64')
} else {
// https://cloud.tencent.com/document/product/436/7778#signature
const today = Math.floor(new Date().getTime() / 1000)
const tomorrow = today + 86400
signTime = `${today};${tomorrow}`
const signKey = crypto.createHmac('sha1', secretKey).update(signTime).digest('hex')
const httpString = `put\n/${options.path}${fileName}\n\nhost=${options.bucket}.cos.${options.area}.myqcloud.com\n`
const sha1edHttpString = crypto.createHash('sha1').update(httpString).digest('hex')
const stringToSign = `sha1\n${signTime}\n${sha1edHttpString}\n`
signature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex')
}
return {
signature,
appId,
bucket,
signTime
}
}
const postOptions = (fileName, signature, imgBase64) => {
const options = db.read().get('picBed.tcyun').value()
const area = options.area
const path = options.path
if (!options.version || options.version === 'v4') {
return {
method: 'POST',
url: `http://${area}.file.myqcloud.com/files/v2/${signature.appId}/${signature.bucket}/${encodeURI(path)}${fileName}`,
headers: {
Host: `${area}.file.myqcloud.com`,
Authorization: signature.signature,
contentType: 'multipart/form-data'
},
formData: {
op: 'upload',
filecontent: Buffer.from(imgBase64, 'base64')
}
}
} else {
return {
method: 'PUT',
url: `http://${options.bucket}.cos.${options.area}.myqcloud.com/${encodeURI(path)}${encodeURI(fileName)}`,
headers: {
Host: `${options.bucket}.cos.${options.area}.myqcloud.com`,
Authorization: `q-sign-algorithm=sha1&q-ak=${options.secretId}&q-sign-time=${signature.signTime}&q-key-time=${signature.signTime}&q-header-list=host&q-url-param-list=&q-signature=${signature.signature}`,
contentType: mime.lookup(fileName)
},
body: Buffer.from(imgBase64, 'base64'),
resolveWithFullResponse: true
}
}
}
const tcYunUpload = async (img, type, webContents) => {
try {
webContents.send('uploadProgress', 0)
const imgList = await img2Base64[type](img)
webContents.send('uploadProgress', 30)
const length = imgList.length
const tcYunOptions = db.read().get('picBed.tcyun').value()
const customUrl = tcYunOptions.customUrl
const path = tcYunOptions.path
const useV4 = !tcYunOptions.version || tcYunOptions.version === 'v4'
for (let i in imgList) {
const signature = generateSignature(imgList[i].fileName)
const options = postOptions(imgList[i].fileName, signature, imgList[i].base64Image)
const res = await request(options)
.then(res => res)
.catch(err => {
console.log(err.response.body)
return {
statusCode: 400,
body: {
msg: '认证失败!'
}
}
})
let body
if (useV4) {
body = JSON.parse(res)
} else {
body = res
}
if (useV4 && body.message === 'SUCCESS') {
delete imgList[i].base64Image
if (customUrl) {
imgList[i]['imgUrl'] = `${customUrl}/${path}${imgList[i].fileName}`
} else {
imgList[i]['imgUrl'] = body.data.source_url
}
imgList[i]['type'] = 'tcyun'
if (i - length === -1) {
webContents.send('uploadProgress', 60)
}
} else if (!useV4 && body && body.statusCode === 200) {
delete imgList[i].base64Image
if (customUrl) {
imgList[i]['imgUrl'] = `${customUrl}/${path}${imgList[i].fileName}`
} else {
imgList[i]['imgUrl'] = `https://${tcYunOptions.bucket}.cos.${tcYunOptions.area}.myqcloud.com/${path}${imgList[i].fileName}`
}
imgList[i]['type'] = 'tcyun'
if (i - length === -1) {
webContents.send('uploadProgress', 60)
}
} else {
webContents.send('uploadProgress', -1)
const notification = new Notification({
title: '上传失败!',
body: res.body.msg
})
notification.show()
return false
}
}
webContents.send('uploadProgress', 100)
return imgList
} catch (err) {
const options = db.read().get('picBed.tcyun').value()
let body
if (!options.version || options.version === 'v4') {
body = JSON.parse(err.error)
const notification = new Notification({
title: '上传失败!',
body: `错误码:${body.code},请打开浏览器粘贴地址查看相关原因。`
})
notification.show()
clipboard.writeText('https://cloud.tencent.com/document/product/436/8432')
} else {
const notification = new Notification({
title: '上传失败!',
body: `请检查你的配置项是否正确`
})
notification.show()
}
webContents.send('uploadProgress', -1)
throw new Error(err)
}
}
export default tcYunUpload
-78
View File
@@ -1,78 +0,0 @@
import request from 'request-promise'
import * as img2Base64 from './img2base64'
import db from '../../datastore/index'
import { Notification, clipboard } from 'electron'
import crypto from 'crypto'
import MD5 from 'md5'
// generate COS signature string
const generateSignature = (fileName) => {
const options = db.read().get('picBed.upyun').value()
const path = options.path || ''
const operator = options.operator
const password = options.password
const md5Password = MD5(password)
const date = new Date().toGMTString()
const uri = `/${options.bucket}/${encodeURI(path)}${encodeURI(fileName)}`
const value = `PUT&${uri}&${date}`
const sign = crypto.createHmac('sha1', md5Password).update(value).digest('base64')
return `UPYUN ${operator}:${sign}`
}
const postOptions = (fileName, signature, imgBase64) => {
const options = db.read().get('picBed.upyun').value()
const bucket = options.bucket
const path = options.path || ''
return {
method: 'PUT',
url: `https://v0.api.upyun.com/${bucket}/${encodeURI(path)}${encodeURI(fileName)}`,
headers: {
Authorization: signature,
Date: new Date().toGMTString()
},
body: Buffer.from(imgBase64, 'base64'),
resolveWithFullResponse: true
}
}
const upYunUpload = async (img, type, webContents) => {
try {
webContents.send('uploadProgress', 0)
const imgList = await img2Base64[type](img)
webContents.send('uploadProgress', 30)
const length = imgList.length
const upyunOptions = db.read().get('picBed.upyun').value()
const path = upyunOptions.path || ''
for (let i in imgList) {
const singature = generateSignature(imgList[i].fileName)
const options = postOptions(imgList[i].fileName, singature, imgList[i].base64Image)
const body = await request(options)
if (body.statusCode === 200) {
delete imgList[i].base64Image
imgList[i]['imgUrl'] = `${upyunOptions.url}/${path}${imgList[i].fileName}${upyunOptions.options}`
imgList[i]['type'] = 'upyun'
if (i - length === -1) {
webContents.send('uploadProgress', 60)
}
} else {
webContents.send('uploadProgress', -1)
return new Error()
}
}
webContents.send('uploadProgress', 100)
return imgList
} catch (err) {
console.log(err)
const body = JSON.parse(err.error)
webContents.send('uploadProgress', -1)
const notification = new Notification({
title: '上传失败!',
body: `错误码:${body.code},请打开浏览器粘贴地址查看相关原因。`
})
notification.show()
clipboard.writeText('http://docs.upyun.com/api/errno/')
// throw new Error(err)
}
}
export default upYunUpload
+4 -4
View File
@@ -7,9 +7,9 @@ const release = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
const checkVersion = async () => {
let showTip = db.read().get('picBed.showUpdateTip').value()
let showTip = db.read().get('settings.showUpdateTip').value()
if (showTip === undefined) {
db.read().set('picBed.showUpdateTip', true).write()
db.read().set('settings.showUpdateTip', true).write()
showTip = true
}
if (showTip) {
@@ -22,14 +22,14 @@ const checkVersion = async () => {
type: 'info',
title: '发现新版本',
buttons: ['Yes', 'No'],
message: '发现新版本,更新了很多功能,是否去下载最新的版本?',
message: `发现新版本${latest},更新了很多功能,是否去下载最新的版本?`,
checkboxLabel: '以后不再提醒',
checkboxChecked: false
}, (res, checkboxChecked) => {
if (res === 0) { // if selected yes
shell.openExternal(downloadUrl)
}
db.read().set('picBed.showUpdateTip', !checkboxChecked).write()
db.read().set('settings.showUpdateTip', !checkboxChecked).write()
})
}
} else {
+150 -29
View File
@@ -1,35 +1,156 @@
import db from '../../datastore/index'
import { Notification } from 'electron'
import picBeds from '../../datastore/pic-bed-handler'
import {
app,
Notification,
BrowserWindow,
ipcMain
} from 'electron'
import path from 'path'
import dayjs from 'dayjs'
// const checkUploader = (type) => {
// const currentUploader = db.read().get(`picBed.${type}`).value()
// if (currentUploader) {
// return true
// } else {
// return false
// }
// }
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo')
const STORE_PATH = app.getPath('userData')
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
const renameURL = process.env.NODE_ENV === 'development' ? `http://localhost:9080/#rename-page` : `file://${__dirname}/index.html#rename-page`
const uploader = (img, type, webContents) => {
if (db.read().get('picBed.uploadNotification').value()) {
const notification = new Notification({
title: '上传进度',
body: '正在上传'
})
notification.show()
const createRenameWindow = (win) => {
let options = {
height: 175,
width: 300,
show: true,
fullscreenable: false,
resizable: false,
vibrancy: 'ultra-dark',
webPreferences: {
backgroundThrottling: false
}
}
const uploadType = db.read().get('picBed.current').value()
// if (checkUploader(uploadType)) {
try {
return picBeds[uploadType](img, type, webContents)
} catch (e) {
console.log(e)
return false
if (process.platform !== 'darwin') {
options.show = true
options.backgroundColor = '#3f3c37'
options.autoHideMenuBar = true
options.transparent = false
}
// } else {
// return false
// }
const window = new BrowserWindow(options)
window.loadURL(renameURL)
// check if this window is visible
if (win.isVisible()) {
// bounds: { x: 821, y: 75, width: 800, height: 450 }
const bounds = win.getBounds()
const positionX = bounds.x + bounds.width / 2 - 150
let positionY
// if is the settingWindow
if (bounds.height > 400) {
positionY = bounds.y + bounds.height / 2 - 88
} else { // if is the miniWindow
positionY = bounds.y + bounds.height / 2
}
window.setPosition(positionX, positionY, false)
}
return window
}
export default uploader
const waitForShow = (webcontent) => {
return new Promise((resolve, reject) => {
webcontent.on('dom-ready', () => {
resolve()
})
})
}
const waitForRename = (window, id) => {
return new Promise((resolve, reject) => {
ipcMain.once(`rename${id}`, (evt, newName) => {
resolve(newName)
window.hide()
})
window.on('close', () => {
resolve(null)
ipcMain.removeAllListeners(`rename${id}`)
})
})
}
class Uploader {
constructor (img, webContents, picgo = undefined) {
this.img = img
this.webContents = webContents
this.picgo = picgo
}
upload () {
const win = BrowserWindow.fromWebContents(this.webContents)
const picgo = this.picgo || new PicGo(CONFIG_PATH)
picgo.config.debug = true
// for picgo-core
picgo.config.PICGO_ENV = 'GUI'
let input = this.img
picgo.helper.beforeUploadPlugins.register('renameFn', {
handle: async ctx => {
const rename = picgo.getConfig('settings.rename')
const autoRename = picgo.getConfig('settings.autoRename')
await Promise.all(ctx.output.map(async (item, index) => {
let name
let fileName
if (autoRename) {
fileName = dayjs().add(index, 'second').format('YYYYMMDDHHmmss') + item.extname
} else {
fileName = item.fileName
}
if (rename) {
const window = createRenameWindow(win)
await waitForShow(window.webContents)
window.webContents.send('rename', fileName, window.webContents.id)
name = await waitForRename(window, window.webContents.id)
}
item.fileName = name || fileName
}))
}
})
picgo.on('beforeTransform', ctx => {
if (ctx.getConfig('settings.uploadNotification')) {
const notification = new Notification({
title: '上传进度',
body: '正在上传'
})
notification.show()
}
})
picgo.upload(input)
picgo.on('notification', message => {
const notification = new Notification(message)
notification.show()
})
picgo.on('uploadProgress', progress => {
this.webContents.send('uploadProgress', progress)
})
return new Promise((resolve) => {
picgo.on('finished', ctx => {
if (ctx.output.every(item => item.imgUrl)) {
resolve(ctx.output)
} else {
resolve(false)
}
})
picgo.on('failed', ctx => {
const notification = new Notification({
title: '上传失败',
body: '请检查配置和上传的文件是否符合要求'
})
notification.show()
resolve(false)
})
})
}
}
export default Uploader
-99
View File
@@ -1,99 +0,0 @@
import request from 'request-promise'
import * as img2Base64 from './img2base64'
import db from '../../datastore/index'
import { Notification } from 'electron'
const j = request.jar()
const rp = request.defaults({jar: j})
const UPLOAD_URL = 'http://picupload.service.weibo.com/interface/pic_upload.php?ori=1&mime=image%2Fjpeg&data=base64&url=0&markpos=1&logo=&nick=0&marks=1&app=miniblog'
const postOptions = (formData) => {
return {
method: 'POST',
url: 'https://passport.weibo.cn/sso/login',
headers: {
Referer: 'https://passport.weibo.cn/signin/login',
contentType: 'application/x-www-form-urlencoded'
},
formData,
json: true,
resolveWithFullResponse: true
}
}
const weiboUpload = async function (img, type, webContents) {
try {
webContents.send('uploadProgress', 0)
const formData = {
username: db.read().get('picBed.weibo.username').value(),
password: db.read().get('picBed.weibo.password').value()
}
const quality = db.read().get('picBed.weibo.quality').value()
const cookie = db.read().get('picBed.weibo.cookie').value()
const chooseCookie = db.read().get('picBed.weibo.chooseCookie').value()
const options = postOptions(formData)
let res
if (!chooseCookie) {
res = await rp(options)
}
webContents.send('uploadProgress', 30)
if (chooseCookie || res.body.retcode === 20000000) {
if (res) {
for (let i in res.body.data.crossdomainlist) {
await rp.get(res.body.data.crossdomainlist[i])
}
}
webContents.send('uploadProgress', 60)
const imgList = await img2Base64[type](img)
for (let i in imgList) {
let opt = {
formData: {
b64_data: imgList[i].base64Image
}
}
if (chooseCookie) {
opt.headers = {
Cookie: cookie
}
}
let result = await rp.post(UPLOAD_URL, opt)
result = result.replace(/<.*?\/>/, '').replace(/<(\w+).*?>.*?<\/\1>/, '')
delete imgList[i].base64Image
const resTextJson = JSON.parse(result)
if (resTextJson.data.pics.pic_1.pid === undefined) {
webContents.send('uploadProgress', -1)
const notification = new Notification({
title: '上传失败!',
body: '微博Cookie失效,请在网页版重新登录一遍'
})
notification.show()
return new Error()
} else {
const extname = imgList[i].extname === '.gif' ? '.gif' : '.jpg'
imgList[i]['imgUrl'] = `https://ws1.sinaimg.cn/${quality}/${resTextJson.data.pics.pic_1.pid}${extname}`
imgList[i]['type'] = 'weibo'
}
delete imgList[i].extname
}
webContents.send('uploadProgress', 100)
return imgList
} else {
webContents.send('uploadProgress', -1)
const notification = new Notification({
title: '上传失败!',
body: res.body.msg
})
notification.show()
return new Error()
}
} catch (err) {
webContents.send('uploadProgress', -1)
const notification = new Notification({
title: '上传失败!',
body: '服务端出错,请重试'
})
notification.show()
throw new Error(err)
}
}
export default weiboUpload
+142
View File
@@ -0,0 +1,142 @@
<template>
<div id="config-form">
<el-form
label-position="right"
label-width="120px"
:model="ruleForm"
ref="form"
size="mini"
>
<el-form-item
v-for="(item, index) in configList"
:label="item.name"
:required="item.required"
:prop="item.name"
:key="item.name + index"
>
<el-input
v-if="item.type === 'input' || item.type === 'password'"
:type="item.type === 'password' ? 'password' : 'input'"
v-model="ruleForm[item.name]"
:placeholder="item.message || item.name"
></el-input>
<el-select
v-else-if="item.type === 'list'"
v-model="ruleForm[item.name]"
:placeholder="item.message || item.name"
>
<el-option
v-for="(choice, idx) in item.choices"
:label="choice.name || choice.value || choice"
:key="choice.name || choice.value || choice"
:value="choice.value || choice"
></el-option>
</el-select>
<el-select
v-else-if="item.type === 'checkbox'"
v-model="ruleForm[item.name]"
:placeholder="item.message || item.name"
multiple
collapse-tags
>
<el-option
v-for="(choice, idx) in item.choices"
:label="choice.name || choice.value || choice"
:key="choice.value || choice"
:value="choice.value || choice"
></el-option>
</el-select>
<el-switch
v-else-if="item.type === 'confirm'"
v-model="ruleForm[item.name]"
active-text="yes"
inactive-text="no"
>
</el-switch>
</el-form-item>
<slot></slot>
</el-form>
</div>
</template>
<script>
import { cloneDeep, union } from 'lodash'
export default {
name: 'config-form',
props: {
config: Array,
type: String,
id: {
type: String,
default: ''
}
},
data () {
return {
configList: [],
ruleForm: {}
}
},
created () {
},
watch: {
config: {
deep: true,
handler (val) {
this.ruleForm = Object.assign({}, {})
const config = this.$db.read().get(`picBed.${this.id}`).value()
if (val.length > 0) {
this.configList = cloneDeep(val).map(item => {
let defaultValue = item.default !== undefined
? item.default : item.type === 'checkbox'
? [] : null
if (item.type === 'checkbox') {
const defaults = item.choices.filter(i => {
return i.checked
}).map(i => i.value)
defaultValue = union(defaultValue, defaults)
}
if (config && config[item.name] !== undefined) {
defaultValue = config[item.name]
}
this.$set(this.ruleForm, item.name, defaultValue)
return item
})
}
},
immediate: true
}
},
methods: {
async validate () {
return new Promise((resolve, reject) => {
this.$refs.form.validate(valid => {
if (valid) {
resolve(this.ruleForm)
} else {
resolve(false)
return false
}
})
})
}
}
}
</script>
<style lang='stylus'>
#config-form
.el-form
label
line-height 22px
padding-bottom 0
.el-button-group
width 100%
.el-button
width 50%
.el-input__inner
border-radius 19px
.el-radio-group
margin-left 25px
.el-switch__label
&.is-active
color #409EFF
</style>
@@ -1,7 +1,9 @@
<template>
<div id="setting-page">
<div class="fake-title-bar">
PicGo - {{ version }}
<div class="fake-title-bar" :class="{ 'darwin': os === 'darwin' }">
<div class="fake-title-bar__title">
PicGo - {{ version }}
</div>
<div class="handle-bar" v-if="os !== 'darwin'">
<i class="el-icon-minus" @click="minimizeWindow"></i>
<i class="el-icon-circle-plus-outline" @click="openMiniWindow"></i>
@@ -36,7 +38,7 @@
>
<el-menu-item
v-if="item.visible"
:index="item.type"
:index="`picbeds-${item.type}`"
:key="item.type"
>
<!-- <i :class="`el-icon-ui-${item.type}`"></i> -->
@@ -48,11 +50,27 @@
<i class="el-icon-setting"></i>
<span slot="title">PicGo设置</span>
</el-menu-item>
<el-menu-item index="plugin">
<i class="el-icon-share"></i>
<span slot="title">插件设置</span>
</el-menu-item>
</el-menu>
<i class="el-icon-info setting-window" @click="openDialog"></i>
</el-col>
<el-col :span="19" :offset="5" style="height: 428px">
<router-view></router-view>
<el-col
:span="19"
:offset="5"
style="height: 428px"
class="main-wrapper"
:class="{ 'darwin': os === 'darwin' }">
<transition name="picgo-fade" mode="out-in">
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
</transition>
<transition name="picgo-fade" mode="out-in">
<router-view :key="$route.path" v-if="!$route.meta.keepAlive"></router-view>
</transition>
</el-col>
</el-row>
<el-dialog
@@ -128,13 +146,15 @@
</div>
</template>
<script>
import pkg from '../../../package.json'
import pkg from 'root/package.json'
import keyDetect from 'utils/key-binding'
import { remote } from 'electron'
import db from '../../datastore'
import db from '~/datastore'
import mixin from '@/utils/mixin'
const { Menu, dialog, BrowserWindow } = remote
export default {
name: 'setting-page',
mixins: [mixin],
data () {
const customLinkRule = (rule, value, callback) => {
if (!/\$url/.test(value)) {
@@ -162,18 +182,37 @@ export default {
shortKey: {
upload: db.read().get('shortKey.upload').value()
},
picBed: this.$picBed
picBed: []
}
},
created () {
this.os = process.platform
this.buildMenu()
this.$electron.ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds)
},
methods: {
handleSelect (index) {
this.$router.push({
name: index
})
const type = index.match(/picbeds-/)
if (type === null) {
this.$router.push({
name: index
})
} else {
const picBed = index.replace(/picbeds-/, '')
if (this.$builtInPicBed.includes(picBed)) {
this.$router.push({
name: picBed
})
} else {
this.$router.push({
name: 'others',
params: {
type: picBed
}
})
}
}
},
minimizeWindow () {
const window = BrowserWindow.getFocusedWindow()
@@ -238,16 +277,36 @@ export default {
},
openMiniWindow () {
this.$electron.ipcRenderer.send('openMiniWindow')
},
getPicBeds (event, picBeds) {
this.picBed = picBeds
}
},
beforeRouteEnter: (to, from, next) => {
next(vm => {
vm.defaultActive = to.name
})
},
beforeDestroy () {
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}
}
</script>
<style lang='stylus'>
$darwinBg = transparentify(#172426, #000, 0.7)
.picgo-fade
&-enter,
&-leave,
&-leave-active
opacity 0
&-enter-active,
&-leave-active
transition all 100ms linear
.view-title
color #eee
font-size 20px
text-align center
margin 10px auto
#setting-page
.fake-title-bar
-webkit-app-region drag
@@ -259,6 +318,17 @@ export default {
line-height h
position fixed
z-index 100
&.darwin
background transparent
background-image linear-gradient(
to right,
transparent 0%,
transparent 167px,
$darwinBg 167px,
$darwinBg 100%
)
.fake-title-bar__title
padding-left 167px
.handle-bar
position absolute
top 2px
@@ -278,6 +348,9 @@ export default {
.el-icon-circle-plus-outline
&:hover
color #69C282
.main-wrapper
&.darwin
background $darwinBg
.side-bar-menu
position fixed
height calc(100vh - 22px)
@@ -288,7 +361,7 @@ export default {
position fixed
bottom 4px
left 4px
cursor pointer
cursor poiter
color #878d99
transition .2s all ease-in-out
&:hover
@@ -341,12 +414,12 @@ export default {
.align-center
input
text-align center
*::-webkit-scrollbar
width 8px
height 8px
*::-webkit-scrollbar-thumb
border-radius 4px
background #6f6f6f
*::-webkit-scrollbar-track
background-color transparent
*::-webkit-scrollbar
width 8px
height 8px
*::-webkit-scrollbar-thumb
border-radius 4px
background #6f6f6f
*::-webkit-scrollbar-track
background-color transparent
</style>
+10 -2
View File
@@ -8,7 +8,6 @@ import store from './store'
import db from '../datastore/index'
import { webFrame } from 'electron'
import './assets/fonts/iconfont.css'
import picBed from '../datastore/pic-bed'
import VueLazyLoad from 'vue-lazyload'
Vue.use(ElementUI)
@@ -20,7 +19,16 @@ webFrame.setLayoutZoomLevelLimits(0, 0)
if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.http = Vue.prototype.$http = axios
Vue.prototype.$db = db
Vue.prototype.$picBed = picBed
Vue.prototype.$builtInPicBed = [
'smms',
'weibo',
'imgur',
'qiniu',
'tcyun',
'upyun',
'aliyun',
'github'
]
Vue.config.productionTip = false
/* eslint-disable no-new */
@@ -3,29 +3,58 @@
<div class="view-title">
相册 - {{ filterList.length }} <i class="el-icon-caret-bottom" @click="toggleHandleBar" :class="{'active': handleBarActive}"></i>
</div>
<transition name="el-zoom-in-center">
<transition name="el-zoom-in-top">
<el-row v-show="handleBarActive">
<el-col :span="20" :offset="2" :class="{ 'long-list': filterList.length > 4 }">
<el-col :span="20" :offset="2">
<el-row class="handle-bar" :gutter="16">
<el-col :span="6" v-for="item in $picBed" :key="item.type">
<div class="pic-bed-item" @click="choosePicBed(item.type)" :class="{active: choosedPicBed.indexOf(item.type) !== -1}">
{{ item.name }}
</div>
<el-col :span="12">
<el-select
v-model="choosedPicBed"
multiple
collapse-tags
size="mini"
style="width: 100%"
placeholder="请选择显示的图床">
<el-option
v-for="item in picBed"
:key="item.type"
:label="item.name"
:value="item.type">
</el-option>
</el-select>
</el-col>
<el-col :span="12">
<el-select
v-model="pasteStyle"
size="mini"
style="width: 100%"
@change="handlePasteStyleChange"
placeholder="请选择粘贴的格式">
<el-option
v-for="(value, key) in pasteStyleMap"
:key="key"
:label="key"
:value="value">
</el-option>
</el-select>
</el-col>
</el-row>
<el-row class="handle-bar" :gutter="16">
<el-col :span="12">
<el-input placeholder="搜索" size="mini" v-model="searchText">
<el-input
placeholder="搜索"
size="mini"
v-model="searchText">
<i slot="suffix" class="el-input__icon el-icon-close" v-if="searchText" @click="cleanSearch" style="cursor: pointer"></i>
</el-input>
</el-col>
<el-col :span="6">
<div class="item-base copy" :class="{ active: isMultiple(choosedList)}" @click="multiCopy">
<div class="item-base copy round" :class="{ active: isMultiple(choosedList)}" @click="multiCopy">
<i class="el-icon-document"></i> 批量复制
</div>
</el-col>
<el-col :span="6">
<div class="item-base delete" :class="{ active: isMultiple(choosedList)}" @click="multiDelete">
<div class="item-base delete round" :class="{ active: isMultiple(choosedList)}" @click="multiRemove">
<i class="el-icon-delete"></i> 批量删除
</div>
</el-col>
@@ -50,7 +79,7 @@
>
</div>
<div class="gallery-list__tool-panel">
<i class="el-icon-document" @click="copy(item.imgUrl)"></i>
<i class="el-icon-document" @click="copy(item)"></i>
<i class="el-icon-edit-outline" @click="openDialog(item)"></i>
<i class="el-icon-delete" @click="remove(item.id)"></i>
<el-checkbox v-model="choosedList[item.id]" class="pull-right" @change=" handleBarActive = true"></el-checkbox>
@@ -75,7 +104,7 @@
</template>
<script>
import gallerys from 'vue-gallery'
import pasteStyle from '../../../main/utils/pasteTemplate'
import pasteStyle from '~/main/utils/pasteTemplate'
export default {
name: 'gallery',
components: {
@@ -98,15 +127,34 @@ export default {
choosedList: {},
choosedPicBed: [],
searchText: '',
handleBarActive: false
handleBarActive: false,
pasteStyle: '',
pasteStyleMap: {
Markdown: 'markdown',
HTML: 'HTML',
URL: 'URL',
UBB: 'UBB',
Custom: 'Custom'
},
picBed: []
}
},
created () {
this.getGallery()
this.$electron.ipcRenderer.on('updateGallery', (event) => {
this.filterList = this.getGallery()
beforeRouteEnter (to, from, next) {
next(vm => {
vm.getGallery()
vm.getPasteStyle()
vm.getPicBeds()
})
},
created () {
this.$electron.ipcRenderer.on('updateGallery', (event) => {
this.$nextTick(() => {
this.filterList = this.getGallery()
})
})
this.$electron.ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds)
},
computed: {
filterList: {
get () {
@@ -118,6 +166,9 @@ export default {
}
},
methods: {
getPicBeds (event, picBeds) {
this.picBed = picBeds
},
getGallery () {
if (this.choosedPicBed.length > 0) {
let arr = []
@@ -148,12 +199,22 @@ export default {
},
zoomImage (index) {
this.idx = index
this.changeZIndexForGallery(true)
},
changeZIndexForGallery (isOpen) {
if (isOpen) {
document.querySelector('.main-content.el-row').style.zIndex = 101
} else {
document.querySelector('.main-content.el-row').style.zIndex = 10
}
},
handleClose () {
this.idx = null
this.changeZIndexForGallery(false)
},
copy (url) {
const style = this.$db.read().get('picBed.pasteStyle').value() || 'markdown'
copy (item) {
const url = item.url || item.imgUrl
const style = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
const copyLink = pasteStyle(style, url)
const obj = {
title: '复制链接成功',
@@ -172,7 +233,9 @@ export default {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const file = this.$db.read().get('uploaded').getById(id).value()
this.$db.read().get('uploaded').removeById(id).write()
this.$electron.ipcRenderer.send('removeFiles', [file])
const obj = {
title: '操作结果',
body: '删除成功'
@@ -222,15 +285,19 @@ export default {
isMultiple (obj) {
return Object.values(obj).some(item => item)
},
multiDelete () {
multiRemove () {
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
if (Object.values(this.choosedList).some(item => item)) {
this.$confirm('将删除刚才选中的图片,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let files = []
Object.keys(this.choosedList).forEach(key => {
if (this.choosedList[key]) {
const file = this.$db.read().get('uploaded').getById(key).value()
files.push(file)
this.$db.read().get('uploaded').removeById(key).write()
}
})
@@ -240,6 +307,7 @@ export default {
title: '操作结果',
body: '删除成功'
}
this.$electron.ipcRenderer.send('removeFiles', files)
const myNotification = new window.Notification(obj.title, obj)
myNotification.onclick = () => {
return true
@@ -252,10 +320,14 @@ export default {
multiCopy () {
if (Object.values(this.choosedList).some(item => item)) {
let copyString = ''
const style = this.$db.read().get('picBed.pasteStyle').value() || 'markdown'
const style = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
Object.keys(this.choosedList).forEach(key => {
if (this.choosedList[key]) {
copyString += pasteStyle(style, this.$db.read().get('uploaded').getById(key).value().imgUrl) + '\n'
const item = this.$db.read().get('uploaded').getById(key).value()
const url = item.url || item.imgUrl
copyString += pasteStyle(style, url) + '\n'
this.choosedList[key] = false
}
})
const obj = {
@@ -271,10 +343,19 @@ export default {
},
toggleHandleBar () {
this.handleBarActive = !this.handleBarActive
},
getPasteStyle () {
this.pasteStyle = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
},
handlePasteStyleChange (val) {
this.$db.read().set('settings.pasteStyle', val)
.write()
this.pasteStyle = val
}
},
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('updateGallery')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}
}
</script>
@@ -291,14 +372,17 @@ export default {
transition all .2s ease-in-out
&.active
transform: rotate(180deg)
#gallery-view
height 100%
.item-base
background #2E2E2E
text-align center
margin-bottom 10px
padding 5px 0
cursor pointer
font-size 13px
transition all .2s ease-in-out
height: 28px
box-sizing: border-box
&.copy
cursor not-allowed
background #49B1F5
@@ -313,10 +397,10 @@ export default {
cursor pointer
background #F15140
color #fff
.long-list
width: calc(83.3333333% - 6px)
#gallery-view
position relative
.round
border-radius 14px
.pull-right
float right
.gallery-list
@@ -330,8 +414,8 @@ export default {
transition all .2s ease-in-out .1s
width 100%
&.small
height: 245px
top: 152px
height: 287px
top: 113px
&__img
height 150px
position relative
@@ -369,19 +453,9 @@ export default {
&.el-icon-delete
&:hover
color #F15140
.blueimp-gallery
.title
top 30px
.handle-bar
color #ddd
.pic-bed-item
@extend .item-base
&:hover
background #A4D8FA
color #fff
&.active
background #49B1F5
color #fff
margin-bottom 10px
.el-input__inner
border-radius 0
border-radius 14px
</style>
@@ -9,21 +9,20 @@
:class="{ 'is-dragover': dragover, uploading: showProgress, linux: os === 'linux' }" @drop.prevent="onDrop" @dragover.prevent="dragover = true" @dragleave.prevent="dragover = false"
:style="{ backgroundPosition: '0 ' + progress + '%'}"
>
<div id="upload-dragger" @dblclick="openUplodWindow">
<div id="upload-dragger" @dblclick="openUploadWindow">
<input type="file" id="file-uploader" @change="onChange" multiple>
</div>
</div>
</div>
</template>
<script>
import mixin from './mixin'
import picBed from '../../datastore/pic-bed.js'
import mixin from '@/utils/mixin'
export default {
name: 'mini-page',
mixins: [mixin],
data () {
return {
logo: 'static/logo.png',
logo: 'static/squareLogo.png',
dragover: false,
progress: 0,
showProgress: false,
@@ -34,13 +33,12 @@ export default {
screenX: '',
screenY: '',
menu: null,
os: ''
os: '',
picBed: []
}
},
created () {
this.os = process.platform
},
mounted () {
this.$electron.ipcRenderer.on('uploadProgress', (event, progress) => {
if (progress !== -1) {
this.showProgress = true
@@ -50,7 +48,9 @@ export default {
this.showError = true
}
})
this.buildMenu()
this.getPicBeds()
},
mounted () {
window.addEventListener('mousedown', this.handleMouseDown, false)
window.addEventListener('mousemove', this.handleMouseMove, false)
window.addEventListener('mouseup', this.handleMouseUp, false)
@@ -69,11 +69,15 @@ export default {
}
},
methods: {
getPicBeds () {
this.picBed = this.$electron.ipcRenderer.sendSync('getPicBeds')
this.buildMenu()
},
onDrop (e) {
this.dragover = false
this.ipcSendFiles(e.dataTransfer.files)
},
openUplodWindow () {
openUploadWindow () {
document.getElementById('file-uploader').click()
},
onChange (e) {
@@ -116,18 +120,9 @@ export default {
this.dragging = false
if (this.screenX === e.screenX && this.screenY === e.screenY) {
if (e.button === 0) { // left mouse
this.openUplodWindow()
this.openUploadWindow()
} else {
let _this = this
const types = picBed.map(item => item.type)
let submenuItem = this.menu.items[1].submenu.items
submenuItem.forEach((item, index) => {
const result = _this.$db.read().get('picBed.current').value() === types[index]
if (result) {
item.click()
return true
}
})
this.getPicBeds()
this.openContextMenu()
}
}
@@ -137,7 +132,7 @@ export default {
},
buildMenu () {
const _this = this
const submenu = picBed.map(item => {
const submenu = this.picBed.map(item => {
return {
label: item.name,
type: 'radio',
@@ -166,6 +161,17 @@ export default {
_this.$electron.ipcRenderer.send('uploadClipboardFilesFromUploadPage')
}
},
{
label: '最小化窗口',
role: 'minimize'
},
{
label: '重启应用',
click () {
_this.$electron.remote.app.relaunch()
_this.$electron.remote.app.exit(0)
}
},
{
role: 'quit',
label: '退出'
@@ -176,6 +182,7 @@ export default {
},
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('uploadProgress')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
window.removeEventListener('mousedown', this.handleMouseDown, false)
window.removeEventListener('mousemove', this.handleMouseMove, false)
window.removeEventListener('mouseup', this.handleMouseUp, false)
@@ -12,7 +12,12 @@
size="small"
>
<el-form-item
label="修改快捷键"
label="打开配置文件"
>
<el-button type="primary" round size="mini" @click="openConfigFile">点击打开</el-button>
</el-form-item>
<el-form-item
label="修改上传快捷键"
>
<el-button type="primary" round size="mini" @click="keyBindingVisible = true">点击设置</el-button>
</el-form-item>
@@ -21,10 +26,15 @@
>
<el-button type="primary" round size="mini" @click="customLinkVisible = true">点击设置</el-button>
</el-form-item>
<el-form-item
label="设置代理"
>
<el-button type="primary" round size="mini" @click="proxyVisible = true">点击设置</el-button>
</el-form-item>
<el-form-item
label="检查更新"
>
<el-button type="primary" round size="mini" @click="checkUpdate">点击设置</el-button>
<el-button type="primary" round size="mini" @click="checkUpdate">点击检查</el-button>
</el-form-item>
<el-form-item
label="打开更新助手"
@@ -76,6 +86,17 @@
@change="handleUploadNotification"
></el-switch>
</el-form-item>
<el-form-item
v-if="os !== 'darwin'"
label="Mini窗口置顶"
>
<el-switch
v-model="form.miniWindowOntop"
active-text="开"
inactive-text="关"
@change="handleMiniWindowOntop"
></el-switch>
</el-form-item>
<el-form-item
label="选择显示的图床"
>
@@ -95,7 +116,7 @@
</el-col>
</el-row>
<el-dialog
title="修改快捷键"
title="修改上传快捷键"
:visible.sync="keyBindingVisible"
:modal-append-to-body="false"
>
@@ -114,8 +135,8 @@
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="cancelKeyBinding">取消</el-button>
<el-button type="primary" @click="confirmKeyBinding">确定</el-button>
<el-button @click="cancelKeyBinding" round>取消</el-button>
<el-button type="primary" @click="confirmKeyBinding" round>确定</el-button>
</span>
</el-dialog>
<el-dialog
@@ -144,8 +165,35 @@
[]($url)
</div>
<span slot="footer">
<el-button @click="cancelCustomLink">取消</el-button>
<el-button type="primary" @click="confirmCustomLink">确定</el-button>
<el-button @click="cancelCustomLink" round>取消</el-button>
<el-button type="primary" @click="confirmCustomLink" round>确定</el-button>
</span>
</el-dialog>
<el-dialog
title="设置代理"
:visible.sync="proxyVisible"
:modal-append-to-body="false"
>
<el-form
label-position="right"
:model="customLink"
ref="customLink"
:rules="rules"
label-width="80px"
>
<el-form-item
label="代理地址"
>
<el-input
v-model="proxy"
:autofocus="true"
placeholder="例如:http://127.0.0.1:1080"
></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="cancelProxy" round>取消</el-button>
<el-button type="primary" @click="confirmProxy" round>确定</el-button>
</span>
</el-dialog>
<el-dialog
@@ -163,16 +211,16 @@
PicGo更新啦请点击确定打开下载页面
</div>
<span slot="footer">
<el-button @click="cancelCheckVersion">取消</el-button>
<el-button type="primary" @click="confirmCheckVersion">确定</el-button>
<el-button @click="cancelCheckVersion" round>取消</el-button>
<el-button type="primary" @click="confirmCheckVersion" round>确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import db from '../../../datastore'
import keyDetect from 'utils/key-binding'
import pkg from '../../../../package.json'
import pkg from 'root/package.json'
import path from 'path'
const release = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
export default {
@@ -196,61 +244,77 @@ export default {
}
return {
form: {
updateHelper: this.$db.get('picBed.showUpdateTip').value(),
updateHelper: this.$db.read().get('settings.showUpdateTip').value(),
showPicBedList: [],
autoStart: this.$db.get('picBed.autoStart').value() || false,
rename: this.$db.get('picBed.rename').value() || false,
autoRename: this.$db.get('picBed.autoRename').value() || false,
uploadNotification: this.$db.get('picBed.uploadNotification').value() || false
autoStart: this.$db.read().get('settings.autoStart').value() || false,
rename: this.$db.read().get('settings.rename').value() || false,
autoRename: this.$db.read().get('settings.autoRename').value() || false,
uploadNotification: this.$db.read().get('settings.uploadNotification').value() || false,
miniWindowOntop: this.$db.read().get('settings.miniWindowOntop').value() || false
},
picBed: this.$picBed,
picBed: [],
keyBindingVisible: false,
customLinkVisible: false,
checkUpdateVisible: false,
proxyVisible: false,
customLink: {
value: db.read().get('customLink').value() || '$url'
value: this.$db.read().get('settings.customLink').value() || '$url'
},
shortKey: {
upload: db.read().get('shortKey.upload').value()
upload: this.$db.read().get('settings.shortKey.upload').value()
},
proxy: this.$db.read().get('picBed.proxy').value() || undefined,
rules: {
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
},
version: pkg.version,
latestVersion: ''
latestVersion: '',
os: ''
}
},
created () {
this.form.showPicBedList = this.picBed.map(item => {
if (item.visible) {
return item.name
}
})
this.os = process.platform
this.$electron.ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds)
},
methods: {
getPicBeds (event, picBeds) {
this.picBed = picBeds
this.form.showPicBedList = this.picBed.map(item => {
if (item.visible) {
return item.name
}
})
},
openConfigFile () {
const { app, shell } = this.$electron.remote
const STORE_PATH = app.getPath('userData')
const CONFIG_FILE = path.join(STORE_PATH, '/data.json')
shell.openItem(CONFIG_FILE)
},
keyDetect (type, event) {
this.shortKey[type] = keyDetect(event).join('+')
},
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = db.read().get('shortKey').value()
this.shortKey = this.$db.read().get('settings.shortKey').value()
},
confirmKeyBinding () {
const oldKey = db.read().get('shortKey').value()
db.read().set('shortKey', this.shortKey).write()
const oldKey = this.$db.read().get('settings.shortKey').value()
this.$db.read().set('settings.shortKey', this.shortKey).write()
this.keyBindingVisible = false
this.$electron.ipcRenderer.send('updateShortKey', oldKey)
},
cancelCustomLink () {
this.customLinkVisible = false
this.customLink.value = db.read().get('customLink').value() || '$url'
this.customLink.value = this.$db.read().get('settings.customLink').value() || '$url'
},
confirmCustomLink () {
this.$refs.customLink.validate((valid) => {
if (valid) {
db.read().set('customLink', this.customLink.value).write()
this.$db.read().set('settings.customLink', this.customLink.value).write()
this.customLinkVisible = false
this.$electron.ipcRenderer.send('updateCustomLink')
} else {
@@ -258,8 +322,22 @@ export default {
}
})
},
cancelProxy () {
this.proxyVisible = false
this.proxy = this.$db.read().get('picBed.proxy').value() || undefined
},
confirmProxy () {
this.proxyVisible = false
this.$db.read().set('picBed.proxy', this.proxy).write()
const successNotification = new window.Notification('设置代理', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
},
updateHelperChange (val) {
this.$db.read().set('picBed.showUpdateTip', val).write()
this.$db.read().set('settings.showUpdateTip', val).write()
},
handleShowPicBedListChange (val) {
const list = this.picBed.map(item => {
@@ -271,16 +349,17 @@ export default {
return item
})
this.$db.read().set('picBed.list', list).write()
this.$electron.ipcRenderer.send('getPicBeds')
},
handleAutoStartChange (val) {
this.$db.read().set('picBed.autoStart', val).write()
this.$db.read().set('settings.autoStart', val).write()
this.$electron.ipcRenderer.send('autoStart', val)
},
handleRename (val) {
this.$db.read().set('picBed.rename', val).write()
this.$db.read().set('settings.rename', val).write()
},
handleAutoRename (val) {
this.$db.read().set('picBed.autoRename', val).write()
this.$db.read().set('settings.autoRename', val).write()
},
compareVersion2Update (current, latest) {
const currentVersion = current.split('.').map(item => parseInt(item))
@@ -315,22 +394,21 @@ export default {
this.checkUpdateVisible = false
},
handleUploadNotification (val) {
this.$db.read().set('picBed.uploadNotification', val).write()
this.$db.read().set('settings.uploadNotification', val).write()
},
handleMiniWindowOntop (val) {
this.$db.read().set('settings.miniWindowOntop', val).write()
this.$message('需要重启生效')
}
},
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('autoStart')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}
}
</script>
<style lang='stylus'>
.el-message
left 60%
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#picgo-setting
.sub-title
font-size 14px
+593
View File
@@ -0,0 +1,593 @@
<template>
<div id="plugin-view">
<div class="view-title">
插件设置 - <i class="el-icon-goods" @click="goAwesomeList"></i>
</div>
<el-row class="handle-bar" :class="{ 'cut-width': pluginList.length > 6 }">
<el-input
v-model="searchText"
placeholder="搜索npm上的PicGo插件,或者点击上方按钮查看优秀插件列表"
size="small"
>
<i slot="suffix" class="el-input__icon el-icon-close" v-if="searchText" @click="cleanSearch" style="cursor: pointer"></i>
</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">
<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"
onerror="this.src='static/roundLogo.png'"
>
<div
class="plugin-item__content"
:class="{ disabled: !item.enabled }"
>
<div class="plugin-item__name" @click="openHomepage(item.homepage)">
{{ item.name }} <small>{{ ' ' + item.version }}</small>
</div>
<div class="plugin-item__desc" :title="item.description">
{{ item.description }}
</div>
<div class="plugin-item__info-bar">
<span class="plugin-item__author">
{{ item.author }}
</span>
<span class="plugin-item__config" >
<template v-if="searchText">
<template v-if="!item.hasInstall">
<span class="config-button install" v-if="!item.ing" @click="installPlugin(item)">
安装
</span>
<span v-else-if="item.ing" class="config-button ing">
安装中
</span>
</template>
<span v-else class="config-button ing">
已安装
</span>
</template>
<template v-else>
<span v-if="item.ing" class="config-button ing">
进行中
</span>
<template v-else>
<i
v-if="item.enabled"
class="el-icon-setting"
@click="buildContextMenu(item)"
></i>
<i
v-else
class="el-icon-remove-outline"
@click="buildContextMenu(item)"
></i>
</template>
</template>
</span>
</div>
</div>
</div>
</el-col>
</el-row>
<el-row v-show="needReload" class="reload-mask" :class="{ 'cut-width': pluginList.length > 6 }">
<el-button type="primary" @click="reloadApp" size="mini" round>重启以生效</el-button>
</el-row>
<el-dialog
:visible.sync="dialogVisible"
:modal-append-to-body="false"
:title="`配置${configName}`"
width="70%"
>
<config-form
:config="config"
:type="currentType"
:id="configName"
ref="configForm"
>
</config-form>
<span slot="footer">
<el-button @click="dialogVisible = false" round>取消</el-button>
<el-button type="primary" @click="handleConfirmConfig" round>确定</el-button>
</span>
</el-dialog>
<el-dialog
:title="inputBoxOptions.title || '输入框'"
:visible.sync="showInputBoxVisible"
:modal-append-to-body="false"
@close="handleInputBoxClose"
>
<el-input
v-model="inputBoxValue"
:placeholder="inputBoxOptions.placeholder"></el-input>
<span slot="footer">
<el-button @click="showInputBoxVisible = false" round>取消</el-button>
<el-button type="primary" @click="showInputBoxVisible = false" round>确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import ConfigForm from '@/components/ConfigForm'
import { debounce } from 'lodash'
export default {
name: 'plugin',
components: {
ConfigForm
},
data () {
return {
searchText: '',
pluginList: [],
menu: null,
config: [],
currentType: '',
configName: '',
dialogVisible: false,
pluginNameList: [],
loading: true,
needReload: false,
id: '',
os: '',
// for showInputBox
showInputBoxVisible: false,
inputBoxValue: '',
inputBoxOptions: {
title: '',
placeholder: ''
}
}
},
computed: {
npmSearchText () {
return this.searchText.match('picgo-plugin-')
? this.searchText
: this.searchText !== ''
? `picgo-plugin-${this.searchText}`
: this.searchText
}
},
watch: {
npmSearchText (val) {
if (val) {
this.loading = true
this.pluginList = []
this.getSearchResult(val)
} else {
this.getPluginList()
}
}
},
created () {
this.os = process.platform
this.$electron.ipcRenderer.on('pluginList', (evt, list) => {
this.pluginList = list
this.pluginNameList = list.map(item => item.name)
this.loading = false
})
this.$electron.ipcRenderer.on('installSuccess', (evt, plugin) => {
this.loading = false
this.pluginList.forEach(item => {
if (item.name === plugin) {
item.ing = false
item.hasInstall = true
}
})
})
this.$electron.ipcRenderer.on('updateSuccess', (evt, plugin) => {
this.loading = false
this.pluginList.forEach(item => {
if (item.name === plugin) {
item.ing = false
item.hasInstall = true
}
this.getPicBeds()
})
this.handleReload()
this.getPluginList()
})
this.$electron.ipcRenderer.on('uninstallSuccess', (evt, plugin) => {
this.loading = false
this.pluginList = this.pluginList.filter(item => {
if (item.name === plugin) { // restore Uploader & Transformer after uninstalling
if (item.config.transformer.name) {
this.handleRestoreState('transformer', item.config.transformer.name)
}
if (item.config.uploader.name) {
this.handleRestoreState('uploader', item.config.uploader.name)
}
this.getPicBeds()
}
return item.name !== plugin
})
this.pluginNameList = this.pluginNameList.filter(item => item !== plugin)
})
this.getPluginList()
this.getSearchResult = debounce(this.getSearchResult, 50)
this.needReload = this.$db.read().get('needReload').value()
this.$electron.ipcRenderer.on('showInputBox', (evt, options) => {
this.inputBoxValue = ''
this.inputBoxOptions.title = options.title || ''
this.inputBoxOptions.placeholder = options.placeholder || ''
this.showInputBoxVisible = true
})
},
methods: {
buildContextMenu (plugin) {
const _this = this
let menu = [{
label: '启用插件',
enabled: !plugin.enabled,
click () {
_this.$db.read().set(`picgoPlugins.picgo-plugin-${plugin.name}`, true).write()
plugin.enabled = true
_this.getPicBeds()
}
}, {
label: '禁用插件',
enabled: plugin.enabled,
click () {
_this.$db.read().set(`picgoPlugins.picgo-plugin-${plugin.name}`, false).write()
plugin.enabled = false
_this.getPicBeds()
if (plugin.config.transformer.name) {
_this.handleRestoreState('transformer', plugin.config.transformer.name)
}
if (plugin.config.uploader.name) {
_this.handleRestoreState('uploader', plugin.config.uploader.name)
}
}
}, {
label: '卸载插件',
click () {
_this.uninstallPlugin(plugin.name)
}
}, {
label: '更新插件',
click () {
_this.updatePlugin(plugin.name)
}
}]
for (let i in plugin.config) {
if (plugin.config[i].config.length > 0) {
const obj = {
label: `配置${i} - ${plugin.config[i].name}`,
click () {
_this.currentType = i
_this.configName = plugin.config[i].name
_this.dialogVisible = true
_this.config = plugin.config[i].config
}
}
menu.push(obj)
}
}
// handle transformer
if (plugin.config.transformer.name) {
let currentTransformer = this.$db.read().get('picBed.transformer').value() || 'path'
let pluginTransformer = plugin.config.transformer.name
const obj = {
label: `${currentTransformer === pluginTransformer ? '禁用' : '启用'}transformer - ${plugin.config.transformer.name}`,
click () {
_this.toggleTransformer(plugin.config.transformer.name)
}
}
menu.push(obj)
}
// plugin custom menus
if (plugin.guiMenu) {
menu.push({
type: 'separator'
})
for (let i of plugin.guiMenu) {
menu.push({
label: i.label,
click () {
_this.$electron.ipcRenderer.send('pluginActions', plugin.name, i.label)
}
})
}
}
this.menu = this.$electron.remote.Menu.buildFromTemplate(menu)
this.menu.popup(this.$electron.remote.getCurrentWindow())
},
getPluginList () {
this.$electron.ipcRenderer.send('getPluginList')
},
getPicBeds () {
this.$electron.ipcRenderer.send('getPicBeds')
},
installPlugin (item) {
if (!item.gui) {
this.$confirm('该插件未对可视化界面进行优化, 是否继续安装?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
item.ing = true
this.$electron.ipcRenderer.send('installPlugin', item.name)
}).catch(() => {
console.log('Install canceled')
})
} else {
item.ing = true
this.$electron.ipcRenderer.send('installPlugin', item.name)
}
},
uninstallPlugin (val) {
this.pluginList.forEach(item => {
if (item.name === val) {
item.ing = true
}
})
this.$electron.ipcRenderer.send('uninstallPlugin', val)
},
updatePlugin (val) {
this.pluginList.forEach(item => {
if (item.name === val) {
item.ing = true
}
})
this.$electron.ipcRenderer.send('updatePlugin', val)
},
reloadApp () {
this.$electron.remote.app.relaunch()
this.$electron.remote.app.exit(0)
},
handleReload () {
this.$db.read().set('needReload', true).write()
this.needReload = true
const successNotification = new window.Notification('更新成功', {
body: '请点击此通知重启应用以生效'
})
successNotification.onclick = () => {
this.reloadApp()
}
},
cleanSearch () {
this.searchText = ''
},
toggleTransformer (transformer) {
let currentTransformer = this.$db.read().get('picBed.transformer').value() || 'path'
if (currentTransformer === transformer) {
this.$db.read().set('picBed.transformer', 'path').write()
} else {
this.$db.read().set('picBed.transformer', transformer).write()
}
},
async handleConfirmConfig () {
const result = await this.$refs.configForm.validate()
if (result !== false) {
switch (this.currentType) {
case 'plugin':
this.$db.read().set(`picgo-plugin-${this.configName}`, result).write()
break
case 'uploader':
this.$db.read().set(`picBed.${this.configName}`, result).write()
break
case 'transformer':
this.$db.read().set(`transformer.${this.configName}`, result).write()
break
}
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
this.dialogVisible = false
this.getPluginList()
}
},
getSearchResult: function (val) {
// this.$http.get(`https://api.npms.io/v2/search?q=${val}`)
this.$http.get(`https://registry.npmjs.com/-/v1/search?text=${val}`)
.then(res => {
this.pluginList = res.data.objects.map(item => {
return this.handleSearchResult(item)
})
this.loading = false
})
.catch(err => {
console.log(err)
this.loading = false
})
},
handleSearchResult (item) {
const name = item.package.name.replace(/picgo-plugin-/, '')
let gui = false
if (item.package.keywords && item.package.keywords.length > 0) {
if (item.package.keywords.includes('picgo-gui-plugin')) {
gui = true
}
}
return {
name: 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-/, '')),
version: item.package.version,
gui,
ing: false // installing or uninstalling
}
},
// restore Uploader & Transformer
handleRestoreState (item, name) {
if (item === 'uploader') {
const current = this.$db.read().get('picBed.current').value()
if (current === name) {
this.$db.read().set('picBed.current', 'smms').write()
}
}
if (item === 'transformer') {
const current = this.$db.read().get('picBed.transformer').value()
if (current === name) {
this.$db.read().set('picBed.transformer', 'path').write()
}
}
},
openHomepage (url) {
if (url) {
this.$electron.remote.shell.openExternal(url)
}
},
handleInputBoxClose () {
this.$electron.ipcRenderer.send('showInputBox', this.inputBoxValue)
},
goAwesomeList () {
this.$electron.remote.shell.openExternal('https://github.com/PicGo/Awesome-PicGo')
}
},
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('pluginList')
this.$electron.ipcRenderer.removeAllListeners('installSuccess')
this.$electron.ipcRenderer.removeAllListeners('uninstallSuccess')
this.$electron.ipcRenderer.removeAllListeners('updateSuccess')
this.$electron.ipcRenderer.removeAllListeners('showInputBox')
}
}
</script>
<style lang='stylus'>
$darwinBg = #172426
#plugin-view
position relative
padding 0 20px 0
.el-loading-mask
background-color rgba(0, 0, 0, 0.8)
.plugin-list
height: 339px;
box-sizing: border-box;
padding: 8px 15px;
overflow-y: auto;
overflow-x: hidden;
position: absolute;
top: 70px;
left: 5px;
transition: all 0.2s ease-in-out 0.1s;
width: 100%
.el-loading-mask
left: 20px
width: calc(100% - 40px)
.view-title
color #eee
font-size 20px
text-align center
margin 10px auto
i.el-icon-goods
font-size 20px
vertical-align middle
cursor pointer
.handle-bar
margin-bottom 20px
&.cut-width
padding-right: 8px
.el-input__inner
border-radius 0
.plugin-item
box-sizing border-box
height 80px
background #444
padding 8px
user-select text
transition all .2s ease-in-out
margin-bottom 10px
position relative
.cli-only-badge
position absolute
right 0px
top 0
font-size 12px
padding 3px 8px
background #49B1F5
color #eee
&.darwin
background transparentify($darwinBg, #000, 0.75)
&:hover
background transparentify($darwinBg, #000, 0.85)
&:hover
background #333
&__logo
width 64px
height 64px
float left
&__content
float left
width calc(100% - 72px)
height 64px
color #ddd
margin-left 8px
display flex
flex-direction column
justify-content space-between
&.disabled
color #aaa
&__name
font-size 16px
height 22px
line-height 22px
// font-weight 600
font-weight 600
cursor pointer
transition all .2s ease-in-out
&:hover
color: #1B9EF3
&__desc
font-size 14px
height 21px
line-height 21px
overflow hidden
text-overflow ellipsis
white-space nowrap
&__info-bar
font-size 14px
height 21px
line-height 28px
position relative
&__author
overflow hidden
text-overflow ellipsis
white-space nowrap
&__config
float right
font-size 16px
cursor pointer
transition all .2s ease-in-out
&:hover
color: #1B9EF3
.config-button
font-size 12px
color #ddd
background #222
padding 1px 8px
height 18px
line-height 18px
text-align center
position absolute
top 4px
right 20px
transition all .2s ease-in-out
&.reload
right 0px
&.ing
right 0px
&.install
right 0px
&:hover
background: #1B9EF3
color #fff
.reload-mask
position absolute
width calc(100% - 40px)
bottom -320px
text-align center
background rgba(0,0,0,0.4)
padding 10px 0
&.cut-width
width calc(100% - 48px)
</style>
@@ -22,8 +22,10 @@
</div>
</template>
<script>
import mixin from '@/utils/mixin'
export default {
name: 'rename-page',
mixins: [mixin],
data () {
return {
fileName: '',
@@ -5,14 +5,17 @@
<div class="wait-upload-img" v-if="clipboardFiles.length > 0">
<div class="list-title">等待上传</div>
<div v-for="(item, index) in clipboardFiles" :key="index" class="img-list" :style="{height: calcHeight(item.width, item.height) + 'px'}">
<div class="upload-img__container" @click="uploadClipboardFiles">
<div
class="upload-img__container"
:class="{ upload: uploadFlag }"
@click="uploadClipboardFiles">
<img :src="item.imgUrl" class="upload-img">
</div>
</div>
</div>
<div class="uploaded-img">
<div class="list-title">已上传</div>
<div v-for="(item, index) in files" :key="index" class="img-list" :style="{height: calcHeight(item.width, item.height) + 'px'}">
<div v-for="item in files" :key="item.imgUrl" class="img-list" :style="{height: calcHeight(item.width, item.height) + 'px'}">
<div class="upload-img__container" @click="copyTheLink(item)">
<img :src="item.imgUrl" class="upload-img">
</div>
@@ -23,9 +26,11 @@
</template>
<script>
import pasteTemplate from '../../main/utils/pasteTemplate'
import mixin from '@/utils/mixin'
import pasteTemplate from '~/main/utils/pasteTemplate'
export default {
name: 'tray-page',
mixins: [mixin],
data () {
return {
files: [],
@@ -34,7 +39,8 @@
body: '',
icon: ''
},
clipboardFiles: []
clipboardFiles: [],
uploadFlag: false
}
},
computed: {
@@ -54,11 +60,9 @@
this.$electron.ipcRenderer.on('clipboardFiles', (event, files) => {
this.clipboardFiles = files
})
this.$electron.ipcRenderer.on('uploadFiles', (event, files) => {
files.forEach(item => {
this.$db.read().get('uploaded').insert(item).write()
})
this.$electron.ipcRenderer.on('uploadFiles', (event) => {
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
this.uploadFlag = false
})
this.$electron.ipcRenderer.on('updateFiles', (event) => {
this.getData()
@@ -78,7 +82,7 @@
this.notification.body = item.imgUrl
this.notification.icon = item.imgUrl
const myNotification = new window.Notification(this.notification.title, this.notification)
const pasteStyle = this.$db.read().get('picBed.pasteStyle').value() || 'markdown'
const pasteStyle = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
this.$electron.clipboard.writeText(pasteTemplate(pasteStyle, item.imgUrl))
myNotification.onclick = () => {
return true
@@ -98,14 +102,21 @@
}, false)
},
uploadClipboardFiles () {
this.$electron.ipcRenderer.send('uploadClipboardFiles', this.clipboardFiles[0])
if (this.uploadFlag) {
return
}
this.uploadFlag = true
this.$electron.ipcRenderer.send('uploadClipboardFiles')
}
}
}
</script>
<style lang="stylus">
body::-webkit-scrollbar
width 0px
#tray-page
background-color transparent
.list-title
text-align center
color #858585
@@ -154,4 +165,6 @@
width 100%
padding 8px 10px
height 100%
&.upload
cursor not-allowed
</style>
@@ -3,11 +3,14 @@
<el-row :gutter="16">
<el-col :span="20" :offset="2">
<div class="view-title">
图片上传 - {{ picBed }}
图片上传 - {{ picBedName }} <i class="el-icon-caret-bottom" @click="handleChangePicBed"></i>
</div>
<div
id="upload-area"
:class="{ 'is-dragover': dragover }" @drop.prevent="onDrop" @dragover.prevent="dragover = true" @dragleave.prevent="dragover = false"
:class="{ 'is-dragover': dragover }"
@drop.prevent="onDrop"
@dragover.prevent="dragover = true"
@dragleave.prevent="dragover = false"
>
<div id="upload-dragger" @click="openUplodWindow">
<i class="el-icon-upload"></i>
@@ -22,7 +25,7 @@
:show-text="false"
class="upload-progress"
:class="{ 'show': showProgress }"
:status="showError ? 'exception' : ''"
:status="showError ? 'exception' : 'text'"
></el-progress>
<div class="paste-style">
<div class="el-col-16">
@@ -53,10 +56,8 @@
</div>
</template>
<script>
import mixin from '../mixin'
export default {
name: 'upload',
mixins: [mixin],
data () {
return {
dragover: false,
@@ -64,7 +65,9 @@ export default {
showProgress: false,
showError: false,
pasteStyle: '',
picBed: ''
picBed: [],
picBedName: '',
menu: null
}
},
mounted () {
@@ -82,6 +85,8 @@ export default {
this.$electron.ipcRenderer.on('syncPicBed', () => {
this.getDefaultPicBed()
})
this.$electron.ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds)
},
watch: {
progress (val) {
@@ -99,6 +104,7 @@ export default {
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('uploadProgress')
this.$electron.ipcRenderer.removeAllListeners('syncPicBed')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
},
methods: {
onDrop (e) {
@@ -124,10 +130,10 @@ export default {
this.$electron.ipcRenderer.send('uploadChoosedFiles', sendFiles)
},
getPasteStyle () {
this.pasteStyle = this.$db.read().get('picBed.pasteStyle').value() || 'markdown'
this.pasteStyle = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
},
handlePasteStyleChange (val) {
this.$db.read().set('picBed.pasteStyle', val)
this.$db.read().set('settings.pasteStyle', val)
.write()
},
uploadClipboardFiles () {
@@ -135,11 +141,34 @@ export default {
},
getDefaultPicBed () {
const current = this.$db.read().get('picBed.current').value()
this.$picBed.forEach(item => {
this.picBed.forEach(item => {
if (item.type === current) {
this.picBed = item.name
this.picBedName = item.name
}
})
},
getPicBeds (event, picBeds) {
this.picBed = picBeds
this.getDefaultPicBed()
},
handleChangePicBed () {
this.buildMenu()
this.menu.popup(this.$electron.remote.getCurrentWindow())
},
buildMenu () {
const _this = this
const submenu = this.picBed.map(item => {
return {
label: item.name,
type: 'radio',
checked: this.$db.read().get('picBed.current').value() === item.type,
click () {
_this.$db.read().set('picBed.current', item.type).write()
_this.$electron.ipcRenderer.send('syncPicBed')
}
}
})
this.menu = this.$electron.remote.Menu.buildFromTemplate(submenu)
}
}
}
@@ -149,8 +178,10 @@ export default {
color #eee
font-size 20px
text-align center
margin 20px auto
margin 10px auto
#upload-view
.view-title
margin 20px auto
#upload-area
height 220px
border 2px dashed #dddddd
@@ -65,10 +65,10 @@
</div>
</template>
<script>
import mixin from '../mixin'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
name: 'aliyun',
mixins: [mixin],
name: 'aliyun',
data () {
return {
form: {
@@ -109,11 +109,6 @@ export default {
}
</script>
<style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#aliyun-view
.el-form
label
@@ -57,9 +57,9 @@
</div>
</template>
<script>
import mixin from '../mixin'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
name: 'upyun',
name: 'github',
mixins: [mixin],
data () {
return {
@@ -100,11 +100,6 @@ export default {
}
</script>
<style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#github-view
.el-form
label
@@ -37,7 +37,7 @@
</div>
</template>
<script>
import mixin from '../mixin'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
name: 'imgur',
mixins: [mixin],
@@ -77,11 +77,6 @@ export default {
}
</script>
<style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#imgur-view
.el-form
label
+109
View File
@@ -0,0 +1,109 @@
<template>
<div id="others-view">
<el-row :gutter="16">
<el-col :span="16" :offset="4">
<div class="view-title">
{{ picBedName }}设置
</div>
<config-form
v-if="config.length > 0"
:config="config"
type="uploader"
ref="configForm"
:id="type"
>
<el-form-item>
<el-button-group>
<el-button type="primary" @click="handleConfirm" round>确定</el-button>
<el-button type="success" @click="setDefaultPicBed(type)" round :disabled="defaultPicBed === type">设为默认图床</el-button>
</el-button-group>
</el-form-item>
</config-form>
<div v-else class="single">
<div class="notice">暂无配置项</div>
<el-button type="success" @click="setDefaultPicBed(type)" round :disabled="defaultPicBed === type" size="mini">设为默认图床</el-button>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import ConfigForm from '@/components/ConfigForm'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
name: 'OtherPicBed',
mixins: [mixin],
components: {
ConfigForm
},
data () {
return {
type: '',
config: [],
picBedName: ''
}
},
created () {
this.type = this.$route.params.type
this.$electron.ipcRenderer.send('getPicBedConfig', this.$route.params.type)
this.$electron.ipcRenderer.on('getPicBedConfig', this.getPicBeds)
},
methods: {
async handleConfirm () {
const result = await this.$refs.configForm.validate()
if (result !== false) {
this.$db.read().set(`picBed.${this.type}`, result).write()
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
}
},
setDefaultPicBed (type) {
this.$db.read().set('picBed.current', type).write()
this.defaultPicBed = type
const successNotification = new window.Notification('设置默认图床', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
},
getPicBeds (event, config, name) {
this.config = config
this.picBedName = name
}
},
beforeDestroy () {
this.$electron.ipcRenderer.removeListener('getPicBedConfig', this.getPicBeds)
}
}
</script>
<style lang='stylus'>
#others-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
.notice
color #eee
text-align center
margin-bottom 10px
.single
text-align center
</style>
@@ -75,10 +75,10 @@
</div>
</template>
<script>
import mixin from '../mixin'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
name: 'qiniu',
mixins: [mixin],
name: 'qiniu',
data () {
return {
form: {
@@ -120,11 +120,6 @@ export default {
}
</script>
<style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#qiniu-view
.el-form
label
@@ -16,10 +16,10 @@
</div>
</template>
<script>
import mixin from '../mixin'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
name: 'upyun',
mixins: [mixin],
name: 'upyun',
data () {
return {}
},
@@ -38,11 +38,6 @@ export default {
}
</script>
<style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#smms-view
.content
text-align center
@@ -86,10 +86,10 @@
</div>
</template>
<script>
import mixin from '../mixin'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
name: 'tcyun',
mixins: [mixin],
name: 'tcyun',
data () {
return {
form: {
@@ -129,17 +129,12 @@ export default {
})
},
openWiki () {
this.$electron.remote.shell.openExternal('https://github.com/Molunerfinn/PicGo/wiki/%E8%AF%A6%E7%BB%86%E7%AA%97%E5%8F%A3%E7%9A%84%E4%BD%BF%E7%94%A8#%E5%BE%AE%E5%8D%9A%E5%9B%BE%E5%BA%8A')
this.$electron.remote.shell.openExternal('https://github.com/Molunerfinn/PicGo/wiki/%E8%AF%A6%E7%BB%86%E7%AA%97%E5%8F%A3%E7%9A%84%E4%BD%BF%E7%94%A8#腾讯云cos')
}
}
}
</script>
<style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#tcyun-view
.el-form
label
@@ -65,7 +65,7 @@
</div>
</template>
<script>
import mixin from '../mixin'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
name: 'upyun',
mixins: [mixin],
@@ -108,11 +108,6 @@ export default {
}
</script>
<style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#tcyun-view
.el-form
label
@@ -62,7 +62,7 @@
</div>
</template>
<script>
import mixin from '../mixin'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
name: 'weibo',
mixins: [mixin],
@@ -113,7 +113,7 @@ export default {
this.$refs['weiboForm'].resetFields()
},
openWiki () {
this.$electron.remote.shell.openExternal('https://github.com/Molunerfinn/PicGo/wiki/%E8%AF%A6%E7%BB%86%E7%AA%97%E5%8F%A3%E7%9A%84%E4%BD%BF%E7%94%A8#%E5%BE%AE%E5%8D%9A%E5%9B%BE%E5%BA%8A')
this.$electron.remote.shell.openExternal('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#微博图床')
}
}
}
@@ -121,11 +121,6 @@ export default {
<style lang='stylus'>
.el-message
left 60%
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#weibo-view
.el-form
label
+29 -16
View File
@@ -8,77 +8,90 @@ export default new Router({
{
path: '/',
name: 'tray-page',
component: require('@/components/TrayPage').default
component: require('@/pages/TrayPage').default
},
{
path: '/rename-page',
name: 'rename-page',
component: require('@/components/RenamePage').default
component: require('@/pages/RenamePage').default
},
{
path: '/mini-page',
name: 'mini-page',
component: require('@/components/MiniPage').default
component: require('@/pages/MiniPage').default
},
{
path: '/setting',
name: 'setting-page',
component: require('@/components/SettingPage').default,
component: require('@/layouts/SettingPage').default,
children: [
{
path: 'upload',
component: require('@/components/SettingView/Upload').default,
component: require('@/pages/Upload').default,
name: 'upload'
},
{
path: 'weibo',
component: require('@/components/SettingView/Weibo').default,
component: require('@/pages/picbeds/Weibo').default,
name: 'weibo'
},
{
path: 'qiniu',
component: require('@/components/SettingView/Qiniu').default,
component: require('@/pages/picbeds/Qiniu').default,
name: 'qiniu'
},
{
path: 'tcyun',
component: require('@/components/SettingView/TcYun').default,
component: require('@/pages/picbeds/TcYun').default,
name: 'tcyun'
},
{
path: 'upyun',
component: require('@/components/SettingView/UpYun').default,
component: require('@/pages/picbeds/UpYun').default,
name: 'upyun'
},
{
path: 'github',
component: require('@/components/SettingView/GitHub').default,
component: require('@/pages/picbeds/GitHub').default,
name: 'github'
},
{
path: 'smms',
component: require('@/components/SettingView/SMMS').default,
component: require('@/pages/picbeds/SMMS').default,
name: 'smms'
},
{
path: 'aliyun',
component: require('@/components/SettingView/AliYun').default,
component: require('@/pages/picbeds/AliYun').default,
name: 'aliyun'
},
{
path: 'imgur',
component: require('@/components/SettingView/Imgur').default,
component: require('@/pages/picbeds/Imgur').default,
name: 'imgur'
},
{
path: 'others/:type',
component: require('@/pages/picbeds/Others').default,
name: 'others'
},
{
path: 'gallery',
component: require('@/components/SettingView/Gallery').default,
name: 'gallery'
component: require('@/pages/Gallery').default,
name: 'gallery',
meta: {
keepAlive: true
}
},
{
path: 'setting',
component: require('@/components/SettingView/PicGoSetting').default,
component: require('@/pages/PicGoSetting').default,
name: 'setting'
},
{
path: 'plugin',
component: require('@/pages/Plugin').default,
name: 'plugin'
}
]
},
+20
View File
@@ -0,0 +1,20 @@
export default {
name: '',
data () {
return {
defaultPicBed: this.$db.read().get('picBed.current').value()
}
},
methods: {
setDefaultPicBed (type) {
this.$db.read().set('picBed.current', type).write()
this.defaultPicBed = type
const successNotification = new window.Notification('设置默认图床', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
}
}
}
+15
View File
@@ -0,0 +1,15 @@
class LS {
get (name) {
if (localStorage.getItem(name)) {
return JSON.parse(localStorage.getItem(name))
} else {
return {}
}
}
set (name, value) {
return localStorage.setItem(name, JSON.stringify(value))
}
}
export default new LS()
@@ -1,13 +1,7 @@
import db from '../../datastore'
export default {
mounted () {
this.disableDragEvent()
},
data () {
return {
defaultPicBed: db.read().get('picBed.current').value()
}
},
methods: {
disableDragEvent () {
window.addEventListener('dragenter', this.disableDrag, false)
@@ -21,17 +15,6 @@ export default {
e.dataTransfer.effectAllowed = 'none'
e.dataTransfer.dropEffect = 'none'
}
},
setDefaultPicBed (type) {
db.read().set('picBed.current', type).write()
this.defaultPicBed = type
this.$electron.ipcRenderer.send('updateDefaultPicBed', type)
const successNotification = new window.Notification('设置默认图床', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
}
},
beforeDestroy () {
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# require xclip(see http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script/677212#677212)
command -v xclip >/dev/null 2>&1 || { echo >&1 "no xclip"; exit 1; }
# write image in clipboard to file (see http://unix.stackexchange.com/questions/145131/copy-image-from-clipboard-to-file)
filePath=`xclip -selection clipboard -o 2>/dev/null | grep ^file:// | cut -c8-`
if [ ! -n "$filePath" ] ;then
if
xclip -selection clipboard -target image/png -o >/dev/null 2>&1
then
xclip -selection clipboard -target image/png -o >$1 2>/dev/null
echo $1
else
echo "no image"
fi
else
echo $filePath
fi
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 744 B

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 B

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+44
View File
@@ -0,0 +1,44 @@
# Adapted from https://github.com/octan3/img-clipboard-dump/blob/master/dump-clipboard-png.ps1
param($imagePath)
# https://github.com/PowerShell/PowerShell/issues/7233
# fix the output encoding bug
[console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding
Add-Type -Assembly PresentationCore
function main {
$img = [Windows.Clipboard]::GetImage()
if ($img -eq $null) {
"no image"
Exit 1
}
if (-not $imagePath) {
"no image"
Exit 1
}
$fcb = new-object Windows.Media.Imaging.FormatConvertedBitmap($img, [Windows.Media.PixelFormats]::Rgb24, $null, 0)
$stream = [IO.File]::Open($imagePath, "OpenOrCreate")
$encoder = New-Object Windows.Media.Imaging.PngBitmapEncoder
$encoder.Frames.Add([Windows.Media.Imaging.BitmapFrame]::Create($fcb)) | out-null
$encoder.Save($stream) | out-null
$stream.Dispose() | out-null
$imagePath
}
try {
# For WIN10
$file = Get-Clipboard -Format FileDropList
if ($file -ne $null) {
Convert-Path $file
Exit 1
}
} catch {
# For WIN7 WIN8 WIN10
main
}
main
+4108 -3130
View File
File diff suppressed because it is too large Load Diff