Compare commits

...
112 Commits
Author SHA1 Message Date
Molunerfinn 50f31bd438 🎉 Release: v2.1.2 2019-04-19 13:26:04 +08:00
Molunerfinn 5b0d596c6d 🔨 Refactor: improve picgo-setting page's thing
Add $fileName info
Add settings documentation url icon button
2019-04-19 11:00:51 +08:00
Molunerfinn c59e2bcc97 Feature: add file-name for customurl
add $fileName arg for customUrl

ISSUES CLOSED: #173
2019-04-18 17:25:10 +08:00
Molunerfinn 3c6b329927 🐛 Fix: log-level's reset value from 'all' -> ['all']
ISSUES CLOSED: #240, #237
2019-04-18 16:23:21 +08:00
Molunerfinn 466dbec4b7 🐛 Fix: mini window hidden bug in linux
ISSUES CLOSED: #239
2019-04-17 14:45:08 +08:00
Molunerfinn 4e02244ebe 🐛 Fix: log-level filter bug
if logLevel is not an array, it will throw an error to setting page

ISSUES CLOSED: #237
2019-04-17 09:28:36 +08:00
Molunerfinn 748a7fd07e 🎉 Release: v2.1.1 2019-04-16 10:45:21 +08:00
Molunerfinn 4000cea7c8 🐛 Fix: upload-area can't upload images 2019-04-16 10:45:09 +08:00
Molunerfinn 8978166328 🎉 Release: v2.1.0 2019-04-15 15:26:42 +08:00
Molunerfinn 7f1769733b Feature: add workflow for mac
for uploading images from context menu in macOS
2019-04-15 15:25:33 +08:00
Molunerfinn e5fbe750ae Feature: add windows context menu
for uploading pictures with picgo.exe
2019-04-15 14:10:01 +08:00
Molunerfinn ed539cf206 ⬆️ Upgrade: picgo-core -> 1.3.5 2019-04-15 10:28:40 +08:00
Molunerfinn 74c7016d2c Feature: support commandline -> upload images in clipboard 2019-04-15 10:28:11 +08:00
Molunerfinn 407b821aca Feature: add gui-api for remove event
ISSUES CLOSED: #201
2019-04-14 22:07:33 +08:00
PiEgg 6db86ec359 Feature: add commandline argvs support for picgo app
for example: picgo.exe ./xxx.jpg
2019-04-14 15:41:48 +08:00
PiEgg c64959a139 🐛 Fix: qiniu area option from select group -> input
ISSUES CLOSED: #230
2019-04-13 11:44:21 +08:00
Molunerfinn 6122e17d75 📝 Docs: add brew cask source
Thanks to @womeimingzi11!
2019-04-12 10:58:10 +08:00
Molunerfinn 37f1d34abc 📦 Chore: add picgo bump-version 2019-04-10 10:48:22 +08:00
Molunerfinn 3dd20857aa Fixed: #223 || change github token input type -> password 2019-04-04 17:09:42 +08:00
Molunerfinn 4a11d9830b Rm: showInputBox handler in plugin page 2019-04-04 16:58:36 +08:00
Molunerfinn 0165879f2d Changed: showInputBox from plugin page -> mainPage layout 2019-04-04 16:56:38 +08:00
Molunerfinn 17302fe3e4 Finished: log level setting 2019-04-04 15:08:47 +08:00
Molunerfinn f65d2d2df4 Fixed: npm ENOENT bug && mini window display -> false by default 2019-04-02 17:11:01 +08:00
PiEgg af19de7168 Changed: MIT LICENSE -> 996ICU LICENSE 2019-03-30 12:20:00 +08:00
Molunerfinn 9ab9e1973c Finished: guiApi.showMessageBox 2019-03-27 16:38:15 +08:00
Molunerfinn a4056cbe58 Fixed: #175 || improve gallery page scroll performance 2019-03-26 17:18:52 +08:00
zWingzandPiEgg 69d660fe7e Updated: trayPage add img-list object-fit 2019-03-16 14:47:48 +08:00
Molunerfinn 058e903789 Fixed: mime-types bug 2019-03-12 15:11:33 +08:00
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
79 changed files with 7773 additions and 4169 deletions
+14 -12
View File
@@ -12,7 +12,7 @@ const Multispinner = require('multispinner')
const mainConfig = require('./webpack.main.config') const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.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 doneLog = chalk.bgGreen.white(' DONE ') + ' '
const errorLog = chalk.bgRed.white(' ERROR ') + ' ' const errorLog = chalk.bgRed.white(' ERROR ') + ' '
@@ -72,6 +72,7 @@ function build () {
function pack (config) { function pack (config) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
config.mode = 'production'
webpack(config, (err, stats) => { webpack(config, (err, stats) => {
if (err) reject(err.stack || err) if (err) reject(err.stack || err)
else if (stats.hasErrors()) { else if (stats.hasErrors()) {
@@ -97,19 +98,20 @@ function pack (config) {
}) })
} }
function web () { // function web () {
del.sync(['dist/web/*', '!.gitkeep']) // del.sync(['dist/web/*', '!.gitkeep'])
webpack(webConfig, (err, stats) => { // webConfig.mode = 'production'
if (err || stats.hasErrors()) console.log(err) // webpack(webConfig, (err, stats) => {
// if (err || stats.hasErrors()) console.log(err)
console.log(stats.toString({ // console.log(stats.toString({
chunks: false, // chunks: false,
colors: true // colors: true
})) // }))
process.exit() // process.exit()
}) // })
} // }
function greeting () { function greeting () {
const cols = process.stdout.columns const cols = process.stdout.columns
+22 -10
View File
@@ -41,21 +41,21 @@ function logStats (proc, data) {
function startRenderer () { function startRenderer () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer) rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
rendererConfig.mode = 'development'
const compiler = webpack(rendererConfig) const compiler = webpack(rendererConfig)
hotMiddleware = webpackHotMiddleware(compiler, { hotMiddleware = webpackHotMiddleware(compiler, {
log: false, log: false,
heartbeat: 2500 heartbeat: 2500
}) })
compiler.plugin('compilation', compilation => { compiler.hooks.compilation.tap('compilation', compilation => {
compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => { compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({ action: 'reload' }) hotMiddleware.publish({ action: 'reload' })
cb() cb()
}) })
}) })
compiler.plugin('done', stats => { compiler.hooks.done.tap('done', stats => {
logStats('Renderer', stats) logStats('Renderer', stats)
}) })
@@ -80,10 +80,10 @@ function startRenderer () {
function startMain () { function startMain () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main) mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
mainConfig.mode = 'development'
const compiler = webpack(mainConfig) const compiler = webpack(mainConfig)
compiler.plugin('watch-run', (compilation, done) => { compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
logStats('Main', chalk.white.bold('compiling...')) logStats('Main', chalk.white.bold('compiling...'))
hotMiddleware.publish({ action: 'compiling' }) hotMiddleware.publish({ action: 'compiling' })
done() done()
@@ -114,8 +114,20 @@ function startMain () {
} }
function startElectron () { 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 => { electronProcess.stdout.on('data', data => {
electronLog(data, 'blue') electronLog(data, 'blue')
}) })
+18 -12
View File
@@ -1,9 +1,11 @@
const HtmlWebpackPlugin = require('html-webpack-plugin') 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 path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const webpack = require('webpack') const webpack = require('webpack')
module.exports = { module.exports = {
mode: 'development',
context: path.resolve(__dirname, '../docs'), context: path.resolve(__dirname, '../docs'),
entry: './main.js', entry: './main.js',
output: { 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$/, test: /\.css$/,
use: ExtractTextPlugin.extract({ use: ['vue-style-loader', 'css-loader']
fallback: 'style-loader',
use: 'css-loader'
})
}, },
{ {
test: /\.html$/, test: /\.html$/,
@@ -85,28 +95,24 @@ module.exports = {
] ]
}, },
plugins: [ plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../docs/template.html') template: path.resolve(__dirname, '../docs/template.html')
}), }),
new ExtractTextPlugin("styles.css") new MiniCssExtractPlugin({filename: 'styles.css'}),
] ]
} }
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
// module.exports.devtool = '#source-map' // module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html // http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.mode = 'production'
module.exports.plugins = (module.exports.plugins || []).concat([ module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
NODE_ENV: '"production"' NODE_ENV: '"production"'
} }
}), }),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({ new webpack.LoaderOptionsPlugin({
minimize: true minimize: true
}) })
+3 -3
View File
@@ -6,14 +6,14 @@ const path = require('path')
const { dependencies } = require('../package.json') const { dependencies } = require('../package.json')
const webpack = require('webpack') const webpack = require('webpack')
const babelMinifyWebpackPlugin = require('babel-minify-webpack-plugin') const BabiliWebpackPlugin = require('babili-webpack-plugin')
let mainConfig = { let mainConfig = {
entry: { entry: {
main: path.join(__dirname, '../src/main/index.js') main: path.join(__dirname, '../src/main/index.js')
}, },
externals: [ externals: [
// ...Object.keys(dependencies || {}) ...Object.keys(dependencies || {})
], ],
module: { module: {
rules: [ rules: [
@@ -73,7 +73,7 @@ if (process.env.NODE_ENV !== 'production') {
*/ */
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
mainConfig.plugins.push( mainConfig.plugins.push(
new babelMinifyWebpackPlugin(), new BabiliWebpackPlugin(),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"' 'process.env.NODE_ENV': '"production"'
}) })
+23 -8
View File
@@ -8,8 +8,9 @@ const webpack = require('webpack')
const BabiliWebpackPlugin = require('babili-webpack-plugin') const BabiliWebpackPlugin = require('babili-webpack-plugin')
const CopyWebpackPlugin = require('copy-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 HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
/** /**
* List of node_modules to include in webpack bundle * 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$/, test: /\.css$/,
use: ExtractTextPlugin.extract({ use: ['vue-style-loader', 'css-loader']
fallback: 'style-loader',
use: 'css-loader'
})
}, },
{ {
test: /\.html$/, test: /\.html$/,
@@ -69,7 +79,9 @@ let rendererConfig = {
extractCSS: process.env.NODE_ENV === 'production', extractCSS: process.env.NODE_ENV === 'production',
loaders: { loaders: {
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 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' __filename: process.env.NODE_ENV !== 'production'
}, },
plugins: [ plugins: [
new ExtractTextPlugin('styles.css'), new VueLoaderPlugin(),
new MiniCssExtractPlugin({filename: 'styles.css'}),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: 'index.html', filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'), template: path.resolve(__dirname, '../src/index.ejs'),
@@ -134,7 +147,9 @@ let rendererConfig = {
alias: { alias: {
'@': path.join(__dirname, '../src/renderer'), '@': path.join(__dirname, '../src/renderer'),
'vue$': 'vue/dist/vue.esm.js', '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'] extensions: ['.js', '.vue', '.json', '.css', '.node']
}, },
+1 -1
View File
@@ -29,7 +29,7 @@ before_install:
install: install:
#- export DISPLAY=':99.0' #- export DISPLAY=':99.0'
#- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & #- 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 - curl -o- -L https://yarnpkg.com/install.sh | bash
- source ~/.bashrc - source ~/.bashrc
- npm install -g xvfb-maybe - npm install -g xvfb-maybe
+53
View File
@@ -0,0 +1,53 @@
## :tada: 2.1.2 (2019-04-19)
### :sparkles: Features
* add file-name for customurl ([c59e2bc](https://github.com/Molunerfinn/PicGo/commit/c59e2bc)), closes [#173](https://github.com/Molunerfinn/PicGo/issues/173)
### :bug: Bug Fixes
* log-level filter bug ([4e02244](https://github.com/Molunerfinn/PicGo/commit/4e02244)), closes [#237](https://github.com/Molunerfinn/PicGo/issues/237)
* log-level's reset value from 'all' -> ['all'] ([3c6b329](https://github.com/Molunerfinn/PicGo/commit/3c6b329)), closes [#240](https://github.com/Molunerfinn/PicGo/issues/240) [#237](https://github.com/Molunerfinn/PicGo/issues/237)
* mini window hidden bug in linux ([466dbec](https://github.com/Molunerfinn/PicGo/commit/466dbec)), closes [#239](https://github.com/Molunerfinn/PicGo/issues/239)
## :tada: 2.1.1 (2019-04-16)
### :bug: Bug Fixes
* upload-area can't upload images ([4000cea](https://github.com/Molunerfinn/PicGo/commit/4000cea))
# :tada: 2.1.0 (2019-04-15)
### :sparkles: Features
* add commandline argvs support for picgo app ([6db86ec](https://github.com/Molunerfinn/PicGo/commit/6db86ec))
* add gui-api for remove event ([407b821](https://github.com/Molunerfinn/PicGo/commit/407b821)), closes [#201](https://github.com/Molunerfinn/PicGo/issues/201)
* add windows context menu ([e5fbe75](https://github.com/Molunerfinn/PicGo/commit/e5fbe75))
* add workflow for mac ([7f17697](https://github.com/Molunerfinn/PicGo/commit/7f17697))
* support commandline -> upload images in clipboard ([74c7016](https://github.com/Molunerfinn/PicGo/commit/74c7016))
### :bug: Bug Fixes
* qiniu area option from select group -> input ([c64959a](https://github.com/Molunerfinn/PicGo/commit/c64959a)), closes [#230](https://github.com/Molunerfinn/PicGo/issues/230)
### :pencil: Documentation
* add brew cask source ([6122e17](https://github.com/Molunerfinn/PicGo/commit/6122e17))
### :package: Chore
* add picgo bump-version ([37f1d34](https://github.com/Molunerfinn/PicGo/commit/37f1d34))
+10 -5
View File
@@ -1,8 +1,13 @@
MIT License The 996ICU License (996ICU)
Copyright (c) <year> <copyright holders> Version 0.1, March 2019
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: PACKAGE is distributed under LICENSE with the following restriction:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above license is only granted to entities that act in concordance
with local labor laws. In addition, the following requirements must be
observed:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * The licencee must not, explicitly or implicitly, request or schedule
their employees to work more than 45 hours in any single week.
* The licencee must not, explicitly or implicitly, request or schedule
their employees to be at work consecutively for 10 hours.
+29 -9
View File
@@ -3,7 +3,7 @@
> 图片上传+管理新体验 > 图片上传+管理新体验
<p align="center"> <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>
<p align="center"> <p align="center">
<a href="https://github.com/feross/standard"> <a href="https://github.com/feross/standard">
@@ -18,6 +18,9 @@
<a href="https://github.com/Molunerfinn/PicGo/releases/latest"> <a href="https://github.com/Molunerfinn/PicGo/releases/latest">
<img src="https://img.shields.io/github/release/Molunerfinn/PicGo.svg?style=flat-square" alt=""> <img src="https://img.shields.io/github/release/Molunerfinn/PicGo.svg?style=flat-square" alt="">
</a> </a>
<a href="https://github.com/PicGo/bump-version">
<img src="https://img.shields.io/badge/picgo-convention-blue.svg?style=flat-square" alt="">
</a>
</p> </p>
## 应用说明 ## 应用说明
@@ -35,19 +38,32 @@ PicGo目前支持了
- `阿里云OSS` v1.6.0 - `阿里云OSS` v1.6.0
- `Imgur` 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)。 支持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)。 支持快捷键`command+shift+p`macOS)或者`control+shift+p`windows\linux)用以支持快捷上传剪贴板里的图片(第一张)。
PicGo支持自定义快捷键,使用方法见[配置手册](https://picgo.github.io/PicGo-Doc/zh/guide/config.html)。
**如果第一次使用,请参考应用使用[手册](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)来下载,速度会快。**
开发进度可以查看[Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度。 开发进度可以查看[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)。**
## 下载安装
点击此处下载[应用](https://github.com/Molunerfinn/PicGo/releases)。
macOS用户请下载最新版本的`dmg`文件,windows用户请下载最新版本的`exe`文件,linux用户请下载`AppImage`文件。
**如果你是Arch类的Linux用户,可以直接通过`aurman -S picgo-appimage`来安装PicGo。感谢 @houbaron 的贡献!**
**如果你是macOS用户,可以使用brew cask来安装PicGo: `brew cask install picgo`。感谢 @womeimingzi11 的贡献!**
## 应用截图
![](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) ![picgo-menubar](https://user-images.githubusercontent.com/12621342/34242310-b5056510-e655-11e7-8568-60ffd4f71910.gif)
@@ -86,6 +102,10 @@ npm run build
只需第一次构建的时候指定一下国内源即可。后续构建不需要特地指定。二进制文件下载在`~/.electron/`目录下。如果想要更新`electron`构建版本,可以删除`~/.electron/`目录,然后重新运行上一步,让`electron-builder`去下载最新的`electron`二进制文件。 只需第一次构建的时候指定一下国内源即可。后续构建不需要特地指定。二进制文件下载在`~/.electron/`目录下。如果想要更新`electron`构建版本,可以删除`~/.electron/`目录,然后重新运行上一步,让`electron-builder`去下载最新的`electron`二进制文件。
## 其他相关
- [vs-picgo](https://github.com/Spades-S/vs-picgo)picgo的VSCode版。
## 赞助 ## 赞助
如果你喜欢PicGo并且它对你确实有帮助,欢迎给我打赏一杯咖啡哈~ 如果你喜欢PicGo并且它对你确实有帮助,欢迎给我打赏一杯咖啡哈~
@@ -102,4 +122,4 @@ npm run build
[MIT](http://opensource.org/licenses/MIT) [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

+13 -8
View File
@@ -1,8 +1,13 @@
!macro preInit !macro customInstall
SetRegView 64 SetRegView 64
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\PicGo" WriteRegStr HKCR "*\shell\PicGo" "" "Upload pictures w&ith PicGo"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\PicGo" WriteRegStr HKCR "*\shell\PicGo" "Icon" "$INSTDIR\PicGo.exe"
SetRegView 32 WriteRegStr HKCR "*\shell\PicGo\command" "" '"$INSTDIR\PicGo.exe" "upload" "%1"'
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\PicGo" SetRegView 32
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\PicGo" WriteRegStr HKCR "*\shell\PicGo" "" "Upload pictures w&ith PicGo"
!macroend WriteRegStr HKCR "*\shell\PicGo" "Icon" "$INSTDIR\PicGo.exe"
WriteRegStr HKCR "*\shell\PicGo\command" "" '"$INSTDIR\PicGo.exe" "upload" "%1"'
!macroend
!macro customUninstall
DeleteRegKey HKCR "*\shell\PicGo"
!macroend
+26 -4
View File
@@ -4,13 +4,16 @@
.mask .mask
img.logo(src="~icons/256x256.png", alt="PicGo") img.logo(src="~icons/256x256.png", alt="PicGo")
h1.title PicGo h1.title PicGo
small(v-if="version") {{ version }}
h2.desc 图片上传+管理新体验 h2.desc 图片上传+管理新体验
button.download(@click="goLink('https://github.com/Molunerfinn/picgo/releases')") 免费下载 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 h3.desc
| 基于#[a(href="https://github.com/SimulatedGREG/electron-vue" target="_blank") electron-vue]开发 | 基于#[a(href="https://github.com/SimulatedGREG/electron-vue" target="_blank") electron-vue]开发
h3.desc h3.desc
| 支持macOS,Windows,Linux | 支持macOS,Windows,Linux
h3.desc
| 支持插件系统让PicGo更强大
#container.container-fluid #container.container-fluid
.row.ex-width .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") 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 }} .display-list__desc {{ item.desc }}
.row.ex-width.info .row.ex-width.info
.col-xs-10.col-xs-offset-1 .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> </template>
<script> <script>
export default { export default {
name: '', name: '',
data () { data () {
return { return {
version: '',
year: new Date().getFullYear(),
itemList: [ itemList: [
{ {
url: 'https://ws1.sinaimg.cn/large/8700af19ly1fma907llb5j20m30ed46a', url: 'https://ws1.sinaimg.cn/large/8700af19ly1fma907llb5j20m30ed46a',
@@ -49,19 +54,32 @@ export default {
{ {
url: 'https://ws1.sinaimg.cn/large/8700af19ly1fmd5ck9m0wj20lr0cxmzs', url: 'https://ws1.sinaimg.cn/large/8700af19ly1fmd5ck9m0wj20lr0cxmzs',
title: '可选图床', title: '可选图床',
desc: '默认支持微博图床、七牛图床、腾讯云COS、又拍云、GitHub、SM.MS、阿里云OSS、Imgur。方便不同图床的上传需求。' desc: '默认支持微博图床、七牛图床、腾讯云COS、又拍云、GitHub、SM.MS、阿里云OSS、Imgur。方便不同图床的上传需求。2.0版本开始更可以自己开发插件实现其他图床的上传需求。'
}, },
{ {
url: 'https://ws1.sinaimg.cn/large/8700af19gy1fmayjwttnbj218g0p0q4e', url: 'https://ws1.sinaimg.cn/large/8700af19gy1fmayjwttnbj218g0p0q4e',
title: '多样链接', title: '多样链接',
desc: '支持4种默认剪贴板链接格式,以及一种自定义格式,让你的文本编辑游刃有余。' desc: '支持5种默认剪贴板链接格式,包括一种自定义格式,让你的文本编辑游刃有余。'
},
{
url: 'https://i.loli.net/2019/01/12/5c39a2f60a32a.png',
title: '插件系统',
desc: '2.0版本开始支持插件系统,让PicGo发挥无限潜能,成为一个极致的效率工具。'
} }
] ]
} }
}, },
created () {
this.getVersion()
},
methods: { methods: {
goLink (link) { goLink (link) {
window.open(link, '_blank') 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-size 36px
font-weight 300 font-weight 300
margin 10px auto margin 10px auto
text-align center
small
margin-left 10px
font-size 14px
.desc .desc
font-weight 400 font-weight 400
margin 20px auto 10px margin 20px auto 10px
+3
View File
@@ -1,6 +1,9 @@
import Vue from 'vue' import Vue from 'vue'
import App from './APP.vue' import App from './APP.vue'
import 'melody.css' import 'melody.css'
import axios from 'axios'
Vue.prototype.$http = axios
new Vue({ new Vue({
render: h => h(App) render: h => h(App)
+47 -21
View File
@@ -1,6 +1,6 @@
{ {
"name": "picgo", "name": "picgo",
"version": "1.6.2", "version": "2.1.2",
"author": "Molunerfinn <marksz@teamsz.xyz>", "author": "Molunerfinn <marksz@teamsz.xyz>",
"description": "Easy to upload your pic & copy to write", "description": "Easy to upload your pic & copy to write",
"license": "MIT", "license": "MIT",
@@ -26,7 +26,27 @@
"docs": "webpack-dev-server --content-base docs/dist --config .electron-vue/webpack.docs.config.js --hot --inline", "docs": "webpack-dev-server --content-base docs/dist --config .electron-vue/webpack.docs.config.js --hot --inline",
"patch": "npm version patch && git push origin master && git push origin --tags", "patch": "npm version patch && git push origin master && git push origin --tags",
"minor": "npm version minor && git push origin master && git push origin --tags", "minor": "npm version minor && git push origin master && git push origin --tags",
"major": "npm version major && git push origin master && git push origin --tags" "major": "npm version major && git push origin master && git push origin --tags",
"cz": "git-cz",
"bump": "bump-version"
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"config": {
"commitizen": {
"path": "./node_modules/cz-customizable"
},
"cz-customizable": {
"config": "./node_modules/@picgo/bump-version/.cz-config.js"
}
},
"commitlint": {
"extends": [
"./node_modules/@picgo/bump-version/commitlint-picgo"
]
}, },
"build": { "build": {
"productName": "PicGo", "productName": "PicGo",
@@ -73,8 +93,8 @@
"dependencies": { "dependencies": {
"axios": "^0.16.1", "axios": "^0.16.1",
"dateformat": "^3.0.3", "dateformat": "^3.0.3",
"element-ui": "^2.0.5", "element-ui": "^2.4.11",
"fecha": "^2.3.3", "fix-path": "^2.1.0",
"fs-extra": "^4.0.2", "fs-extra": "^4.0.2",
"image-size": "^0.6.1", "image-size": "^0.6.1",
"keycode": "^2.1.9", "keycode": "^2.1.9",
@@ -82,9 +102,8 @@
"lowdb": "^1.0.0", "lowdb": "^1.0.0",
"md5": "^2.2.1", "md5": "^2.2.1",
"melody.css": "^1.0.2", "melody.css": "^1.0.2",
"picgo": "^1.3.5",
"qiniu": "^7.1.1", "qiniu": "^7.1.1",
"request": "^2.83.0",
"request-promise": "^4.2.2",
"vue": "^2.3.3", "vue": "^2.3.3",
"vue-electron": "^1.0.6", "vue-electron": "^1.0.6",
"vue-gallery": "^1.2.4", "vue-gallery": "^1.2.4",
@@ -93,10 +112,11 @@
"vuex": "^2.3.1" "vuex": "^2.3.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^7.5.2",
"@picgo/bump-version": "^1.0.2",
"babel-core": "^6.25.0", "babel-core": "^6.25.0",
"babel-eslint": "^7.2.3", "babel-eslint": "^7.2.3",
"babel-loader": "^7.1.1", "babel-loader": "^7.1.1",
"babel-minify-webpack-plugin": "^0.2.0",
"babel-plugin-istanbul": "^4.1.1", "babel-plugin-istanbul": "^4.1.1",
"babel-plugin-transform-runtime": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.0", "babel-preset-env": "^1.6.0",
@@ -106,27 +126,30 @@
"cfonts": "^1.1.3", "cfonts": "^1.1.3",
"chai": "^4.0.0", "chai": "^4.0.0",
"chalk": "^2.1.0", "chalk": "^2.1.0",
"commitizen": "^3.0.7",
"conventional-changelog": "^3.0.6",
"copy-webpack-plugin": "^4.0.1", "copy-webpack-plugin": "^4.0.1",
"cross-env": "^5.0.5", "cross-env": "^5.0.5",
"css-loader": "^0.28.4", "css-loader": "^0.28.4",
"cz-customizable": "^5.10.0",
"del": "^3.0.0", "del": "^3.0.0",
"devtron": "^1.4.0", "devtron": "^1.4.0",
"electron": "1.8.7", "electron": "4.0.2",
"electron-builder": "^19.19.1", "electron-builder": "^20.38.4",
"electron-debug": "^1.4.0", "electron-debug": "^1.4.0",
"electron-devtools-installer": "^2.2.0", "electron-devtools-installer": "^2.2.0",
"eslint": "^4.4.1", "eslint": "^4.4.1",
"eslint-config-standard": "^10.2.1", "eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0", "eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.9.0", "eslint-loader": "^2.1.1",
"eslint-plugin-html": "^3.1.1", "eslint-plugin-html": "^3.1.1",
"eslint-plugin-import": "^2.7.0", "eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.1.1", "eslint-plugin-node": "^5.1.1",
"eslint-plugin-promise": "^3.5.0", "eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1", "eslint-plugin-standard": "^3.0.1",
"extract-text-webpack-plugin": "^3.0.0", "file-loader": "^3.0.1",
"file-loader": "^0.11.2", "html-webpack-plugin": "^3.2.0",
"html-webpack-plugin": "^2.30.1", "husky": "^1.3.1",
"inject-loader": "^3.0.0", "inject-loader": "^3.0.0",
"karma": "^1.3.0", "karma": "^1.3.0",
"karma-chai": "^0.1.0", "karma-chai": "^0.1.0",
@@ -136,24 +159,27 @@
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "^0.0.31", "karma-spec-reporter": "^0.0.31",
"karma-webpack": "^2.0.1", "karma-webpack": "^2.0.1",
"mini-css-extract-plugin": "0.4.0",
"mocha": "^3.0.2", "mocha": "^3.0.2",
"multispinner": "^0.2.1", "multispinner": "^0.2.1",
"node-loader": "^0.6.0", "node-loader": "^0.6.0",
"pug": "^2.0.0-rc.4", "pug": "^2.0.0-rc.4",
"pug-loader": "^2.3.0", "pug-loader": "^2.3.0",
"pug-plain-loader": "^1.0.0",
"require-dir": "^0.3.0", "require-dir": "^0.3.0",
"spectron": "^3.7.1", "spectron": "^3.7.1",
"style-loader": "^0.18.2", "style-loader": "^0.23.1",
"stylus": "^0.54.5", "stylus": "^0.54.5",
"stylus-loader": "^3.0.1", "stylus-loader": "^3.0.1",
"url-loader": "^0.5.9", "url-loader": "^1.1.2",
"vue-html-loader": "^1.2.4", "vue-html-loader": "^1.2.4",
"vue-loader": "^13.0.5", "vue-loader": "^15.4.2",
"vue-style-loader": "^3.0.1", "vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.4.2", "vue-template-compiler": "^2.4.2",
"webpack": "^3.5.2", "webpack": "^4.15.1",
"webpack-dev-server": "^2.7.1", "webpack-cli": "^3.0.8",
"webpack-hot-middleware": "^2.18.2", "webpack-dev-server": "^3.1.4",
"webpack-merge": "^4.1.0" "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 fs from 'fs-extra'
import { remote, app } from 'electron' 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 APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData') const STORE_PATH = APP.getPath('userData')
@@ -29,10 +36,46 @@ if (!db.has('picBed').value()) {
}).write() }).write()
} }
if (!db.has('shortKey').value()) { if (!db.has('settings.shortKey').value()) {
db.set('shortKey', { db.set('settings.shortKey', {
upload: 'CommandOrControl+Shift+P' upload: 'CommandOrControl+Shift+P'
}).write() }).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 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 */ /* eslint-disable */
// Set environment for development
process.env.NODE_ENV = 'development'
// Install `electron-debug` with `devtron` // Install `electron-debug` with `devtron`
require('electron-debug')({ showDevTools: false }) require('electron-debug')({ showDevTools: false })
@@ -24,15 +21,4 @@ require('electron').app.on('ready', () => {
}) })
// Require `main` process to boot app // Require `main` process to boot app
if (process.env.DEBUG_ENV === 'debug') { require('./index')
require('babel-core/register')({
'presets': [
['env', {
'targets': {
'node': true
}
}]
]
})
}
require('./index')
+150 -130
View File
@@ -1,12 +1,19 @@
'use strict' '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 { app, BrowserWindow, Tray, Menu, Notification, clipboard, ipcMain, globalShortcut, dialog } from 'electron'
import db from '../datastore' import db from '../datastore'
import beforeOpen from './utils/beforeOpen'
import pasteTemplate from './utils/pasteTemplate' import pasteTemplate from './utils/pasteTemplate'
import updateChecker from './utils/updateChecker' import updateChecker from './utils/updateChecker'
import { getPicBeds } from './utils/getPicBeds'
import pkg from '../../package.json' import pkg from '../../package.json'
import picBed from '../datastore/pic-bed' import picgoCoreIPC from './utils/picgoCoreIPC'
import fixPath from 'fix-path'
import { getUploadFiles } from './utils/handleArgv'
if (process.platform === 'darwin') {
beforeOpen()
}
/** /**
* Set `__static` path to static files in production * Set `__static` path to static files in production
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
@@ -34,18 +41,12 @@ const miniWinURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/#mini-page` ? `http://localhost:9080/#mini-page`
: `file://${__dirname}/index.html#mini-page` : `file://${__dirname}/index.html#mini-page`
const uploadFailed = () => { // fix the $PATH in macOS
const notification = new Notification({ fixPath()
title: '上传失败',
body: '请检查你的图床配置!'
})
notification.show()
}
function createTray () { function createContextMenu () {
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png` const picBeds = getPicBeds(app)
tray = new Tray(menubarPic) const submenu = picBeds.map(item => {
const submenu = picBed.map(item => {
return { return {
label: item.name, label: item.name,
type: 'radio', type: 'radio',
@@ -92,10 +93,17 @@ function createTray () {
{ {
label: '打开更新助手', label: '打开更新助手',
type: 'checkbox', type: 'checkbox',
checked: db.get('picBed.showUpdateTip').value(), checked: db.get('settings.showUpdateTip').value(),
click () { click () {
const value = db.read().get('picBed.showUpdateTip').value() const value = db.read().get('settings.showUpdateTip').value()
db.read().set('picBed.showUpdateTip', !value).write() db.read().set('settings.showUpdateTip', !value).write()
}
},
{
label: '重启应用',
click () {
app.relaunch()
app.exit(0)
} }
}, },
{ {
@@ -103,13 +111,19 @@ function createTray () {
label: '退出' label: '退出'
} }
]) ])
}
function createTray () {
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
tray = new Tray(menubarPic)
tray.on('right-click', () => { tray.on('right-click', () => {
if (window) { if (window) {
window.hide() window.hide()
} }
createContextMenu()
tray.popUpContextMenu(contextMenu) tray.popUpContextMenu(contextMenu)
}) })
tray.on('click', () => { tray.on('click', (event, bounds) => {
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
let img = clipboard.readImage() let img = clipboard.readImage()
let obj = [] let obj = []
@@ -122,7 +136,7 @@ function createTray () {
imgUrl imgUrl
}) })
} }
toggleWindow() toggleWindow(bounds)
setTimeout(() => { setTimeout(() => {
window.webContents.send('clipboardFiles', obj) window.webContents.send('clipboardFiles', obj)
}, 0) }, 0)
@@ -137,6 +151,9 @@ function createTray () {
settingWindow.show() settingWindow.show()
settingWindow.focus() settingWindow.focus()
} }
if (miniWindow) {
miniWindow.hide()
}
} }
}) })
@@ -149,11 +166,11 @@ function createTray () {
}) })
tray.on('drop-files', async (event, files) => { tray.on('drop-files', async (event, files) => {
const pasteStyle = db.read().get('picBed.pasteStyle').value() || 'markdown' const pasteStyle = db.read().get('settings.pasteStyle').value() || 'markdown'
const imgs = await uploader(files, 'imgFromPath', window.webContents) const imgs = await new Uploader(files, window.webContents).upload()
if (imgs !== false) { if (imgs !== false) {
for (let i in imgs) { for (let i in imgs) {
clipboard.writeText(pasteTemplate(pasteStyle, imgs[i].imgUrl)) clipboard.writeText(pasteTemplate(pasteStyle, imgs[i]))
const notification = new Notification({ const notification = new Notification({
title: '上传成功', title: '上传成功',
body: imgs[i].imgUrl, body: imgs[i].imgUrl,
@@ -162,10 +179,9 @@ function createTray () {
setTimeout(() => { setTimeout(() => {
notification.show() notification.show()
}, i * 100) }, i * 100)
db.read().get('uploaded').insert(imgs[i]).write()
} }
window.webContents.send('dragFiles', imgs) window.webContents.send('dragFiles', imgs)
} else {
uploadFailed()
} }
}) })
// toggleWindow() // toggleWindow()
@@ -198,6 +214,7 @@ const createWindow = () => {
window.on('blur', () => { window.on('blur', () => {
window.hide() window.hide()
}) })
return window
} }
const createMiniWidow = () => { const createMiniWidow = () => {
@@ -207,19 +224,20 @@ const createMiniWidow = () => {
let obj = { let obj = {
height: 64, height: 64,
width: 64, width: 64,
show: false, show: process.platform === 'linux',
frame: false, frame: false,
fullscreenable: false, fullscreenable: false,
skipTaskbar: true,
resizable: false, resizable: false,
transparent: true, transparent: process.platform !== 'linux',
icon: `${__static}/logo.png`, icon: `${__static}/logo.png`,
webPreferences: { webPreferences: {
backgroundThrottling: false backgroundThrottling: false
} }
} }
if (process.platform === 'linux') { if (db.read().get('settings.miniWindowOntop').value()) {
obj.transparent = false obj.alwaysOnTop = true
} }
miniWindow = new BrowserWindow(obj) miniWindow = new BrowserWindow(obj)
@@ -229,6 +247,7 @@ const createMiniWidow = () => {
miniWindow.on('closed', () => { miniWindow.on('closed', () => {
miniWindow = null miniWindow = null
}) })
return miniWindow
} }
const createSettingWindow = () => { const createSettingWindow = () => {
@@ -245,11 +264,12 @@ const createSettingWindow = () => {
transparent: true, transparent: true,
titleBarStyle: 'hidden', titleBarStyle: 'hidden',
webPreferences: { webPreferences: {
backgroundThrottling: false backgroundThrottling: false,
webSecurity: false
} }
} }
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
options.show = true options.show = false
options.frame = false options.frame = false
options.backgroundColor = '#3f3c37' options.backgroundColor = '#3f3c37'
options.transparent = false options.transparent = false
@@ -267,6 +287,7 @@ const createSettingWindow = () => {
}) })
createMenu() createMenu()
createMiniWidow() createMiniWidow()
return settingWindow
} }
const createMenu = () => { const createMenu = () => {
@@ -295,63 +316,40 @@ const createMenu = () => {
} }
} }
const getWindowPosition = () => { const toggleWindow = (bounds) => {
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 = () => {
if (window.isVisible()) { if (window.isVisible()) {
window.hide() window.hide()
} else { } else {
showWindow() showWindow(bounds)
} }
} }
const showWindow = () => { const showWindow = (bounds) => {
const position = getWindowPosition() window.setPosition(bounds.x - 98 + 11, bounds.y, false)
window.setPosition(position.x, position.y, false)
window.webContents.send('updateFiles') window.webContents.send('updateFiles')
window.show() window.show()
window.focus() window.focus()
} }
const uploadClipboardFiles = async () => { 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 let win
if (miniWindow.isVisible) { if (miniWindow.isVisible()) {
win = miniWindow win = miniWindow
} else { } else {
win = settingWindow || window win = settingWindow || window || createSettingWindow()
} }
img = await uploader(uploadImg, 'imgFromClipboard', win.webContents) let img = await new Uploader(undefined, win.webContents).upload()
if (img !== false) { if (img !== false) {
if (img.length > 0) { if (img.length > 0) {
const pasteStyle = db.read().get('picBed.pasteStyle').value() || 'markdown' const pasteStyle = db.read().get('settings.pasteStyle').value() || 'markdown'
clipboard.writeText(pasteTemplate(pasteStyle, img[0].imgUrl)) clipboard.writeText(pasteTemplate(pasteStyle, img[0]))
const notification = new Notification({ const notification = new Notification({
title: '上传成功', title: '上传成功',
body: img[0].imgUrl, body: img[0].imgUrl,
icon: img[0].imgUrl icon: img[0].imgUrl
}) })
notification.show() notification.show()
db.read().get('uploaded').insert(img[0]).write()
window.webContents.send('clipboardFiles', []) window.webContents.send('clipboardFiles', [])
window.webContents.send('uploadFiles', img) window.webContents.send('uploadFiles', img)
if (settingWindow) { if (settingWindow) {
@@ -364,60 +362,17 @@ const uploadClipboardFiles = async () => {
}) })
notification.show() notification.show()
} }
} else {
uploadFailed()
} }
} }
const updateDefaultPicBed = () => { const uploadChoosedFiles = async (webContents, files) => {
if (process.platform === 'darwin' || process.platform === 'win32') { const input = files.map(item => item.path)
const types = picBed.map(item => item.type) const imgs = await new Uploader(input, webContents).upload()
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
}
}
ipcMain.on('uploadClipboardFiles', async (evt, file) => {
const img = await uploader(file, 'imgFromClipboard', window.webContents)
if (img !== false) {
const pasteStyle = db.read().get('picBed.pasteStyle').value() || 'markdown'
clipboard.writeText(pasteTemplate(pasteStyle, img[0].imgUrl))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl,
// icon: file[0]
icon: img[0].imgUrl
})
notification.show()
window.webContents.send('clipboardFiles', [])
window.webContents.send('uploadFiles', img)
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
} else {
uploadFailed()
}
})
ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
uploadClipboardFiles()
})
ipcMain.on('uploadChoosedFiles', async (evt, files) => {
const imgs = await uploader(files, 'imgFromUploader', evt.sender)
if (imgs !== false) { if (imgs !== false) {
const pasteStyle = db.read().get('picBed.pasteStyle').value() || 'markdown' const pasteStyle = db.read().get('settings.pasteStyle').value() || 'markdown'
let pasteText = '' let pasteText = ''
for (let i in imgs) { for (let i in imgs) {
pasteText += pasteTemplate(pasteStyle, imgs[i].imgUrl) + '\r\n' pasteText += pasteTemplate(pasteStyle, imgs[i]) + '\r\n'
const notification = new Notification({ const notification = new Notification({
title: '上传成功', title: '上传成功',
body: imgs[i].imgUrl, body: imgs[i].imgUrl,
@@ -426,21 +381,52 @@ ipcMain.on('uploadChoosedFiles', async (evt, files) => {
setTimeout(() => { setTimeout(() => {
notification.show() notification.show()
}, i * 100) }, i * 100)
db.read().get('uploaded').insert(imgs[i]).write()
} }
clipboard.writeText(pasteText) clipboard.writeText(pasteText)
window.webContents.send('uploadFiles', imgs) window.webContents.send('uploadFiles', imgs)
if (settingWindow) { if (settingWindow) {
settingWindow.webContents.send('updateGallery') settingWindow.webContents.send('updateGallery')
} }
} else {
uploadFailed()
} }
}
picgoCoreIPC(app, ipcMain)
// from macOS tray
ipcMain.on('uploadClipboardFiles', async (evt, file) => {
const img = await new Uploader(undefined, window.webContents).upload()
if (img !== false) {
const pasteStyle = db.read().get('settings.pasteStyle').value() || 'markdown'
clipboard.writeText(pasteTemplate(pasteStyle, img[0]))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl,
// icon: file[0]
icon: img[0].imgUrl
})
notification.show()
db.read().get('uploaded').insert(img[0]).write()
window.webContents.send('clipboardFiles', [])
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
}
window.webContents.send('uploadFiles')
})
ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
uploadClipboardFiles()
})
ipcMain.on('uploadChoosedFiles', async (evt, files) => {
return uploadChoosedFiles(evt.sender, files)
}) })
ipcMain.on('updateShortKey', (evt, oldKey) => { ipcMain.on('updateShortKey', (evt, oldKey) => {
globalShortcut.unregisterAll() globalShortcut.unregisterAll()
for (let key in oldKey) { 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]() return shortKeyHash[key]()
}) })
} }
@@ -483,34 +469,51 @@ ipcMain.on('openMiniWindow', (evt) => {
settingWindow.hide() settingWindow.hide()
}) })
// update
ipcMain.on('updateDefaultPicBed', (evt) => {
updateDefaultPicBed()
})
// from mini window // from mini window
ipcMain.on('syncPicBed', (evt) => { ipcMain.on('syncPicBed', (evt) => {
if (settingWindow) { if (settingWindow) {
settingWindow.webContents.send('syncPicBed') settingWindow.webContents.send('syncPicBed')
} }
updateDefaultPicBed() })
ipcMain.on('getPicBeds', (evt) => {
const picBeds = getPicBeds(app)
evt.sender.send('getPicBeds', picBeds)
evt.returnValue = picBeds
}) })
const shortKeyHash = { const shortKeyHash = {
upload: uploadClipboardFiles upload: uploadClipboardFiles
} }
const isSecondInstance = app.makeSingleInstance(() => { const gotTheLock = app.requestSingleInstanceLock()
if (settingWindow) {
if (settingWindow.isMinimized()) {
settingWindow.restore()
}
settingWindow.focus()
}
})
if (isSecondInstance) { if (!gotTheLock) {
app.quit() app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
let files = getUploadFiles(commandLine, workingDirectory)
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动
if (files === null) {
uploadClipboardFiles()
} else {
let win
if (miniWindow && miniWindow.isVisible()) {
win = miniWindow
} else {
win = settingWindow || window || createSettingWindow()
}
uploadChoosedFiles(win.webContents, files)
}
} else {
if (settingWindow) {
if (settingWindow.isMinimized()) {
settingWindow.restore()
}
settingWindow.focus()
}
}
})
} }
if (process.platform === 'win32') { if (process.platform === 'win32') {
@@ -527,11 +530,28 @@ app.on('ready', () => {
if (process.platform === 'darwin' || process.platform === 'win32') { if (process.platform === 'darwin' || process.platform === 'win32') {
createTray() createTray()
} }
db.read().set('needReload', false).write()
updateChecker() updateChecker()
globalShortcut.register(db.read().get('shortKey.upload').value(), () => { globalShortcut.register(db.read().get('settings.shortKey.upload').value(), () => {
uploadClipboardFiles() uploadClipboardFiles()
}) })
if (process.env.NODE_ENV !== 'development') {
let files = getUploadFiles()
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动
if (files === null) {
uploadClipboardFiles()
} else {
let win
if (miniWindow && miniWindow.isVisible()) {
win = miniWindow
} else {
win = settingWindow || window || createSettingWindow()
}
uploadChoosedFiles(win.webContents, files)
}
}
}
}) })
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
@@ -554,7 +574,7 @@ app.on('will-quit', () => {
}) })
app.setLoginItemSettings({ 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
+23
View File
@@ -0,0 +1,23 @@
import fs from 'fs-extra'
import path from 'path'
import os from 'os'
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, '\\\\')
}
function beforeOpen () {
const dest = `${os.homedir}/Library/Services/Upload pictures with PicGo.workflow`
if (fs.existsSync(dest)) {
return true
} else {
try {
fs.copySync(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
} catch (e) {
console.log(e)
}
}
}
export default beforeOpen
+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
+124
View File
@@ -0,0 +1,124 @@
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) {
pasteText += pasteTemplate(pasteStyle, imgs[i]) + '\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 = {
title: '',
body: ''
}) {
const notification = new Notification({
title: options.title,
body: options.body
})
notification.show()
}
/**
*
* @param {Object} options
*/
showMessageBox (options = {
title: '',
message: '',
type: 'info',
buttons: ['Yes', 'No']
}) {
return new Promise((resolve, reject) => {
dialog.showMessageBox(
BrowserWindow.fromWebContents(this[WEBCONTENTS]),
options,
(result, checkboxChecked) => {
resolve({
result,
checkboxChecked
})
})
})
}
}
export default GuiApi
+37
View File
@@ -0,0 +1,37 @@
import path from 'path'
import fs from 'fs-extra'
const getUploadFiles = (argv = process.argv, cwd = process.cwd()) => {
let files = argv.slice(1)
if (files.length > 0 && files[0] === 'upload') {
if (files.length === 1) {
return null // for uploading images in clipboard
} else if (files.length > 1) {
files = argv.slice(1)
let result = []
if (files.length > 0) {
result = files.map(item => {
if (path.isAbsolute(item)) {
return {
path: item
}
} else {
let tempPath = path.join(cwd, item)
if (fs.existsSync(tempPath)) {
return {
path: tempPath
}
} else {
return null
}
}
}).filter(item => item !== null)
}
return result
}
}
return []
}
export {
getUploadFiles
}
-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
+21 -3
View File
@@ -1,13 +1,31 @@
import db from '../../datastore' import db from '../../datastore'
export default (style, url) => { const formatCustomLink = (customLink, item) => {
const customLink = db.read().get('customLink').value() || '$url' let fileName = item.fileName.replace(new RegExp(`\\${item.extname}$`), '')
let url = item.url || item.imgUrl
let formatObj = {
url,
fileName
}
let keys = Object.keys(formatObj)
keys.forEach(item => {
if (customLink.indexOf(`$${item}`) !== -1) {
let reg = new RegExp(`\\$${item}`, 'g')
customLink = customLink.replace(reg, formatObj[item])
}
})
return customLink
}
export default (style, item) => {
let url = item.url || item.imgUrl
const customLink = db.read().get('settings.customLink').value() || '$url'
const tpl = { const tpl = {
'markdown': `![](${url})`, 'markdown': `![](${url})`,
'HTML': `<img src="${url}"/>`, 'HTML': `<img src="${url}"/>`,
'URL': url, 'URL': url,
'UBB': `[IMG]${url}[/IMG]`, 'UBB': `[IMG]${url}[/IMG]`,
'Custom': customLink.replace(/\$url/g, url) 'Custom': formatCustomLink(customLink, item)
} }
return tpl[style] return tpl[style]
} }
+200
View File
@@ -0,0 +1,200 @@
import path from 'path'
import GuiApi from './guiApi'
import { dialog, shell } from 'electron'
// 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', async (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-/, ''))
})
picgo.on('failed', () => {
handleNPMError()
})
await pluginHandler.uninstall([msg])
pluginHandler.install([msg])
picgo.cmd.program.removeAllListeners()
})
}
const handlePluginUninstall = (ipcMain, CONFIG_PATH) => {
ipcMain.on('uninstallPlugin', async (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-/, ''))
})
picgo.on('failed', () => {
handleNPMError()
})
await pluginHandler.uninstall([msg])
picgo.cmd.program.removeAllListeners()
})
}
const handlePluginUpdate = (ipcMain, CONFIG_PATH) => {
ipcMain.on('updatePlugin', async (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-/, ''))
})
picgo.on('failed', () => {
handleNPMError()
})
await pluginHandler.update([msg])
picgo.cmd.program.removeAllListeners()
})
}
const handleNPMError = () => {
dialog.showMessageBox({
title: '发生错误',
message: '请安装Node.js并重启PicGo再继续操作',
buttons: ['Yes']
}, (res) => {
if (res === 0) {
shell.openExternal('https://nodejs.org/')
}
})
}
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)
const guiApi = new GuiApi(ipcMain, event.sender, picgo)
setTimeout(() => {
picgo.emit('remove', files, guiApi)
}, 500)
})
}
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 downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
const checkVersion = async () => { const checkVersion = async () => {
let showTip = db.read().get('picBed.showUpdateTip').value() let showTip = db.read().get('settings.showUpdateTip').value()
if (showTip === undefined) { if (showTip === undefined) {
db.read().set('picBed.showUpdateTip', true).write() db.read().set('settings.showUpdateTip', true).write()
showTip = true showTip = true
} }
if (showTip) { if (showTip) {
@@ -22,14 +22,14 @@ const checkVersion = async () => {
type: 'info', type: 'info',
title: '发现新版本', title: '发现新版本',
buttons: ['Yes', 'No'], buttons: ['Yes', 'No'],
message: '发现新版本,更新了很多功能,是否去下载最新的版本?', message: `发现新版本${latest},更新了很多功能,是否去下载最新的版本?`,
checkboxLabel: '以后不再提醒', checkboxLabel: '以后不再提醒',
checkboxChecked: false checkboxChecked: false
}, (res, checkboxChecked) => { }, (res, checkboxChecked) => {
if (res === 0) { // if selected yes if (res === 0) { // if selected yes
shell.openExternal(downloadUrl) shell.openExternal(downloadUrl)
} }
db.read().set('picBed.showUpdateTip', !checkboxChecked).write() db.read().set('settings.showUpdateTip', !checkboxChecked).write()
}) })
} }
} else { } else {
+150 -29
View File
@@ -1,35 +1,156 @@
import db from '../../datastore/index' import {
import { Notification } from 'electron' app,
import picBeds from '../../datastore/pic-bed-handler' Notification,
BrowserWindow,
ipcMain
} from 'electron'
import path from 'path'
import dayjs from 'dayjs'
// const checkUploader = (type) => { // eslint-disable-next-line
// const currentUploader = db.read().get(`picBed.${type}`).value() const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
// if (currentUploader) { const PicGo = requireFunc('picgo')
// return true const STORE_PATH = app.getPath('userData')
// } else { const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
// return false const renameURL = process.env.NODE_ENV === 'development' ? `http://localhost:9080/#rename-page` : `file://${__dirname}/index.html#rename-page`
// }
// }
const uploader = (img, type, webContents) => { const createRenameWindow = (win) => {
if (db.read().get('picBed.uploadNotification').value()) { let options = {
const notification = new Notification({ height: 175,
title: '上传进度', width: 300,
body: '正在上传' show: true,
}) fullscreenable: false,
notification.show() resizable: false,
vibrancy: 'ultra-dark',
webPreferences: {
backgroundThrottling: false
}
} }
const uploadType = db.read().get('picBed.current').value()
// if (checkUploader(uploadType)) { if (process.platform !== 'darwin') {
try { options.show = true
return picBeds[uploadType](img, type, webContents) options.backgroundColor = '#3f3c37'
} catch (e) { options.autoHideMenuBar = true
console.log(e) options.transparent = false
return 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,378 +0,0 @@
<template>
<div id="picgo-setting">
<div class="view-title">
PicGo设置
</div>
<el-row class="setting-list">
<el-col :span="15" :offset="4">
<el-row>
<el-form
label-width="120px"
label-position="right"
size="small"
>
<el-form-item
label="修改快捷键"
>
<el-button type="primary" round size="mini" @click="keyBindingVisible = true">点击设置</el-button>
</el-form-item>
<el-form-item
label="自定义链接格式"
>
<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="checkUpdate">点击设置</el-button>
</el-form-item>
<el-form-item
label="打开更新助手"
>
<el-switch
v-model="form.updateHelper"
active-text=""
inactive-text=""
@change="updateHelperChange"
></el-switch>
</el-form-item>
<el-form-item
label="开机自启"
>
<el-switch
v-model="form.autoStart"
active-text=""
inactive-text=""
@change="handleAutoStartChange"
></el-switch>
</el-form-item>
<el-form-item
label="上传前重命名"
>
<el-switch
v-model="form.rename"
active-text=""
inactive-text=""
@change="handleRename"
></el-switch>
</el-form-item>
<el-form-item
label="时间戳重命名"
>
<el-switch
v-model="form.autoRename"
active-text=""
inactive-text=""
@change="handleAutoRename"
></el-switch>
</el-form-item>
<el-form-item
label="开启上传提示"
>
<el-switch
v-model="form.uploadNotification"
active-text=""
inactive-text=""
@change="handleUploadNotification"
></el-switch>
</el-form-item>
<el-form-item
label="选择显示的图床"
>
<el-checkbox-group
v-model="form.showPicBedList"
@change="handleShowPicBedListChange"
>
<el-checkbox
v-for="item in picBed"
:label="item.name"
:key="item.name"
></el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</el-row>
</el-col>
</el-row>
<el-dialog
title="修改快捷键"
:visible.sync="keyBindingVisible"
:modal-append-to-body="false"
>
<el-form
label-width="80px"
>
<el-form-item
label="快捷上传"
>
<el-input
class="align-center"
@keydown.native.prevent="keyDetect('upload', $event)"
v-model="shortKey.upload"
:autofocus="true"
></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="cancelKeyBinding">取消</el-button>
<el-button type="primary" @click="confirmKeyBinding">确定</el-button>
</span>
</el-dialog>
<el-dialog
title="自定义链接格式"
:visible.sync="customLinkVisible"
:modal-append-to-body="false"
>
<el-form
label-position="top"
:model="customLink"
ref="customLink"
:rules="rules"
>
<el-form-item
label="用占位符$url来表示url的位置"
prop="value"
>
<el-input
class="align-center"
v-model="customLink.value"
:autofocus="true"
></el-input>
</el-form-item>
</el-form>
<div>
[]($url)
</div>
<span slot="footer">
<el-button @click="cancelCustomLink">取消</el-button>
<el-button type="primary" @click="confirmCustomLink">确定</el-button>
</span>
</el-dialog>
<el-dialog
title="检查更新"
:visible.sync="checkUpdateVisible"
:modal-append-to-body="false"
>
<div>
当前版本{{ version }}
</div>
<div>
最新版本{{ latestVersion ? latestVersion : '正在获取中...' }}
</div>
<div v-if="needUpdate">
PicGo更新啦请点击确定打开下载页面
</div>
<span slot="footer">
<el-button @click="cancelCheckVersion">取消</el-button>
<el-button type="primary" @click="confirmCheckVersion">确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import db from '../../../datastore'
import keyDetect from 'utils/key-binding'
import pkg from '../../../../package.json'
const release = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
export default {
name: 'picgo-setting',
computed: {
needUpdate () {
if (this.latestVersion) {
return this.compareVersion2Update(this.version, this.latestVersion)
} else {
return false
}
}
},
data () {
const customLinkRule = (rule, value, callback) => {
if (!/\$url/.test(value)) {
return callback(new Error('必须含有$url'))
} else {
return callback()
}
}
return {
form: {
updateHelper: this.$db.get('picBed.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
},
picBed: this.$picBed,
keyBindingVisible: false,
customLinkVisible: false,
checkUpdateVisible: false,
customLink: {
value: db.read().get('customLink').value() || '$url'
},
shortKey: {
upload: db.read().get('shortKey.upload').value()
},
rules: {
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
},
version: pkg.version,
latestVersion: ''
}
},
created () {
this.form.showPicBedList = this.picBed.map(item => {
if (item.visible) {
return item.name
}
})
},
methods: {
keyDetect (type, event) {
this.shortKey[type] = keyDetect(event).join('+')
},
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = db.read().get('shortKey').value()
},
confirmKeyBinding () {
const oldKey = db.read().get('shortKey').value()
db.read().set('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'
},
confirmCustomLink () {
this.$refs.customLink.validate((valid) => {
if (valid) {
db.read().set('customLink', this.customLink.value).write()
this.customLinkVisible = false
this.$electron.ipcRenderer.send('updateCustomLink')
} else {
return false
}
})
},
updateHelperChange (val) {
this.$db.read().set('picBed.showUpdateTip', val).write()
},
handleShowPicBedListChange (val) {
const list = this.picBed.map(item => {
if (!val.includes(item.name)) {
item.visible = false
} else {
item.visible = true
}
return item
})
this.$db.read().set('picBed.list', list).write()
},
handleAutoStartChange (val) {
this.$db.read().set('picBed.autoStart', val).write()
this.$electron.ipcRenderer.send('autoStart', val)
},
handleRename (val) {
this.$db.read().set('picBed.rename', val).write()
},
handleAutoRename (val) {
this.$db.read().set('picBed.autoRename', val).write()
},
compareVersion2Update (current, latest) {
const currentVersion = current.split('.').map(item => parseInt(item))
const latestVersion = latest.split('.').map(item => parseInt(item))
for (let i = 0; i < 3; i++) {
if (currentVersion[i] < latestVersion[i]) {
return true
}
if (currentVersion[i] > latestVersion[i]) {
return false
}
}
return false
},
checkUpdate () {
this.checkUpdateVisible = true
this.$http.get(release)
.then(res => {
this.latestVersion = res.data.name
}).catch(err => {
console.log(err)
})
},
confirmCheckVersion () {
if (this.needUpdate) {
this.$electron.remote.shell.openExternal(downloadUrl)
}
this.checkUpdateVisible = false
},
cancelCheckVersion () {
this.checkUpdateVisible = false
},
handleUploadNotification (val) {
this.$db.read().set('picBed.uploadNotification', val).write()
}
},
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('autoStart')
}
}
</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
.setting-list
height 360px
box-sizing border-box
overflow-y auto
overflow-x hidden
.setting-list
.el-form
label
line-height 32px
padding-bottom 0
color #eee
.el-button-group
width 100%
.el-button
width 50%
.el-input__inner
border-radius 19px
.el-radio-group
margin-left 25px
.el-switch__label
color #eee
&.is-active
color #409EFF
.el-icon-question
font-size 20px
float right
margin-top 9px
color #eee
cursor pointer
transition .2s color ease-in-out
&:hover
color #409EFF
.el-checkbox-group
label
margin-right 30px
width 100px
.el-checkbox+.el-checkbox
margin-right 30px
margin-left 0
.confirm-button
width 100%
</style>
@@ -1,7 +1,9 @@
<template> <template>
<div id="setting-page"> <div id="setting-page">
<div class="fake-title-bar"> <div class="fake-title-bar" :class="{ 'darwin': os === 'darwin' }">
PicGo - {{ version }} <div class="fake-title-bar__title">
PicGo - {{ version }}
</div>
<div class="handle-bar" v-if="os !== 'darwin'"> <div class="handle-bar" v-if="os !== 'darwin'">
<i class="el-icon-minus" @click="minimizeWindow"></i> <i class="el-icon-minus" @click="minimizeWindow"></i>
<i class="el-icon-circle-plus-outline" @click="openMiniWindow"></i> <i class="el-icon-circle-plus-outline" @click="openMiniWindow"></i>
@@ -36,7 +38,7 @@
> >
<el-menu-item <el-menu-item
v-if="item.visible" v-if="item.visible"
:index="item.type" :index="`picbeds-${item.type}`"
:key="item.type" :key="item.type"
> >
<!-- <i :class="`el-icon-ui-${item.type}`"></i> --> <!-- <i :class="`el-icon-ui-${item.type}`"></i> -->
@@ -48,11 +50,27 @@
<i class="el-icon-setting"></i> <i class="el-icon-setting"></i>
<span slot="title">PicGo设置</span> <span slot="title">PicGo设置</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="plugin">
<i class="el-icon-share"></i>
<span slot="title">插件设置</span>
</el-menu-item>
</el-menu> </el-menu>
<i class="el-icon-info setting-window" @click="openDialog"></i> <i class="el-icon-info setting-window" @click="openDialog"></i>
</el-col> </el-col>
<el-col :span="19" :offset="5" style="height: 428px"> <el-col
<router-view></router-view> :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-col>
</el-row> </el-row>
<el-dialog <el-dialog
@@ -125,16 +143,32 @@
<el-button type="primary" @click="confirmCustomLink">确定</el-button> <el-button type="primary" @click="confirmCustomLink">确定</el-button>
</span> </span>
</el-dialog> </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> </div>
</template> </template>
<script> <script>
import pkg from '../../../package.json' import pkg from 'root/package.json'
import keyDetect from 'utils/key-binding' import keyDetect from 'utils/key-binding'
import { remote } from 'electron' import { remote } from 'electron'
import db from '../../datastore' import db from '~/datastore'
import mixin from '@/utils/mixin'
const { Menu, dialog, BrowserWindow } = remote const { Menu, dialog, BrowserWindow } = remote
export default { export default {
name: 'setting-page', name: 'setting-page',
mixins: [mixin],
data () { data () {
const customLinkRule = (rule, value, callback) => { const customLinkRule = (rule, value, callback) => {
if (!/\$url/.test(value)) { if (!/\$url/.test(value)) {
@@ -162,18 +196,50 @@ export default {
shortKey: { shortKey: {
upload: db.read().get('shortKey.upload').value() upload: db.read().get('shortKey.upload').value()
}, },
picBed: this.$picBed picBed: [],
// for showInputBox
showInputBoxVisible: false,
inputBoxValue: '',
inputBoxOptions: {
title: '',
placeholder: ''
}
} }
}, },
created () { created () {
this.os = process.platform this.os = process.platform
this.buildMenu() this.buildMenu()
this.$electron.ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds)
this.$electron.ipcRenderer.on('showInputBox', (evt, options) => {
this.inputBoxValue = ''
this.inputBoxOptions.title = options.title || ''
this.inputBoxOptions.placeholder = options.placeholder || ''
this.showInputBoxVisible = true
})
}, },
methods: { methods: {
handleSelect (index) { handleSelect (index) {
this.$router.push({ const type = index.match(/picbeds-/)
name: index 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 () { minimizeWindow () {
const window = BrowserWindow.getFocusedWindow() const window = BrowserWindow.getFocusedWindow()
@@ -238,16 +304,40 @@ export default {
}, },
openMiniWindow () { openMiniWindow () {
this.$electron.ipcRenderer.send('openMiniWindow') this.$electron.ipcRenderer.send('openMiniWindow')
},
getPicBeds (event, picBeds) {
this.picBed = picBeds
},
handleInputBoxClose () {
this.$electron.ipcRenderer.send('showInputBox', this.inputBoxValue)
} }
}, },
beforeRouteEnter: (to, from, next) => { beforeRouteEnter: (to, from, next) => {
next(vm => { next(vm => {
vm.defaultActive = to.name vm.defaultActive = to.name
}) })
},
beforeDestroy () {
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
this.$electron.ipcRenderer.removeAllListeners('showInputBox')
} }
} }
</script> </script>
<style lang='stylus'> <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 #setting-page
.fake-title-bar .fake-title-bar
-webkit-app-region drag -webkit-app-region drag
@@ -259,6 +349,17 @@ export default {
line-height h line-height h
position fixed position fixed
z-index 100 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 .handle-bar
position absolute position absolute
top 2px top 2px
@@ -278,6 +379,9 @@ export default {
.el-icon-circle-plus-outline .el-icon-circle-plus-outline
&:hover &:hover
color #69C282 color #69C282
.main-wrapper
&.darwin
background $darwinBg
.side-bar-menu .side-bar-menu
position fixed position fixed
height calc(100vh - 22px) height calc(100vh - 22px)
@@ -288,7 +392,7 @@ export default {
position fixed position fixed
bottom 4px bottom 4px
left 4px left 4px
cursor pointer cursor poiter
color #878d99 color #878d99
transition .2s all ease-in-out transition .2s all ease-in-out
&:hover &:hover
@@ -341,12 +445,12 @@ export default {
.align-center .align-center
input input
text-align center text-align center
*::-webkit-scrollbar *::-webkit-scrollbar
width 8px width 8px
height 8px height 8px
*::-webkit-scrollbar-thumb *::-webkit-scrollbar-thumb
border-radius 4px border-radius 4px
background #6f6f6f background #6f6f6f
*::-webkit-scrollbar-track *::-webkit-scrollbar-track
background-color transparent background-color transparent
</style> </style>
+10 -2
View File
@@ -8,7 +8,6 @@ import store from './store'
import db from '../datastore/index' import db from '../datastore/index'
import { webFrame } from 'electron' import { webFrame } from 'electron'
import './assets/fonts/iconfont.css' import './assets/fonts/iconfont.css'
import picBed from '../datastore/pic-bed'
import VueLazyLoad from 'vue-lazyload' import VueLazyLoad from 'vue-lazyload'
Vue.use(ElementUI) Vue.use(ElementUI)
@@ -20,7 +19,16 @@ webFrame.setLayoutZoomLevelLimits(0, 0)
if (!process.env.IS_WEB) Vue.use(require('vue-electron')) if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.http = Vue.prototype.$http = axios Vue.http = Vue.prototype.$http = axios
Vue.prototype.$db = db Vue.prototype.$db = db
Vue.prototype.$picBed = picBed Vue.prototype.$builtInPicBed = [
'smms',
'weibo',
'imgur',
'qiniu',
'tcyun',
'upyun',
'aliyun',
'github'
]
Vue.config.productionTip = false Vue.config.productionTip = false
/* eslint-disable no-new */ /* eslint-disable no-new */
@@ -3,29 +3,58 @@
<div class="view-title"> <div class="view-title">
相册 - {{ filterList.length }} <i class="el-icon-caret-bottom" @click="toggleHandleBar" :class="{'active': handleBarActive}"></i> 相册 - {{ filterList.length }} <i class="el-icon-caret-bottom" @click="toggleHandleBar" :class="{'active': handleBarActive}"></i>
</div> </div>
<transition name="el-zoom-in-center"> <transition name="el-zoom-in-top">
<el-row v-show="handleBarActive"> <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-row class="handle-bar" :gutter="16">
<el-col :span="6" v-for="item in $picBed" :key="item.type"> <el-col :span="12">
<div class="pic-bed-item" @click="choosePicBed(item.type)" :class="{active: choosedPicBed.indexOf(item.type) !== -1}"> <el-select
{{ item.name }} v-model="choosedPicBed"
</div> 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-col>
</el-row> </el-row>
<el-row class="handle-bar" :gutter="16"> <el-row class="handle-bar" :gutter="16">
<el-col :span="12"> <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> <i slot="suffix" class="el-input__icon el-icon-close" v-if="searchText" @click="cleanSearch" style="cursor: pointer"></i>
</el-input> </el-input>
</el-col> </el-col>
<el-col :span="6"> <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> 批量复制 <i class="el-icon-document"></i> 批量复制
</div> </div>
</el-col> </el-col>
<el-col :span="6"> <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> 批量删除 <i class="el-icon-delete"></i> 批量删除
</div> </div>
</el-col> </el-col>
@@ -45,12 +74,12 @@
<el-col :span="6" v-for="(item, index) in images" :key="item.id" class="gallery-list__img"> <el-col :span="6" v-for="(item, index) in images" :key="item.id" class="gallery-list__img">
<div <div
class="gallery-list__item" class="gallery-list__item"
v-lazy:background-image="item.imgUrl"
@click="zoomImage(index)" @click="zoomImage(index)"
> >
<img v-lazy="item.imgUrl" class="gallery-list__item-img">
</div> </div>
<div class="gallery-list__tool-panel"> <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-edit-outline" @click="openDialog(item)"></i>
<i class="el-icon-delete" @click="remove(item.id)"></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> <el-checkbox v-model="choosedList[item.id]" class="pull-right" @change=" handleBarActive = true"></el-checkbox>
@@ -75,7 +104,7 @@
</template> </template>
<script> <script>
import gallerys from 'vue-gallery' import gallerys from 'vue-gallery'
import pasteStyle from '../../../main/utils/pasteTemplate' import pasteStyle from '~/main/utils/pasteTemplate'
export default { export default {
name: 'gallery', name: 'gallery',
components: { components: {
@@ -98,15 +127,34 @@ export default {
choosedList: {}, choosedList: {},
choosedPicBed: [], choosedPicBed: [],
searchText: '', searchText: '',
handleBarActive: false handleBarActive: false,
pasteStyle: '',
pasteStyleMap: {
Markdown: 'markdown',
HTML: 'HTML',
URL: 'URL',
UBB: 'UBB',
Custom: 'Custom'
},
picBed: []
} }
}, },
created () { beforeRouteEnter (to, from, next) {
this.getGallery() next(vm => {
this.$electron.ipcRenderer.on('updateGallery', (event) => { vm.getGallery()
this.filterList = this.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: { computed: {
filterList: { filterList: {
get () { get () {
@@ -118,6 +166,9 @@ export default {
} }
}, },
methods: { methods: {
getPicBeds (event, picBeds) {
this.picBed = picBeds
},
getGallery () { getGallery () {
if (this.choosedPicBed.length > 0) { if (this.choosedPicBed.length > 0) {
let arr = [] let arr = []
@@ -148,17 +199,26 @@ export default {
}, },
zoomImage (index) { zoomImage (index) {
this.idx = 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 () { handleClose () {
this.idx = null this.idx = null
this.changeZIndexForGallery(false)
}, },
copy (url) { copy (item) {
const style = this.$db.read().get('picBed.pasteStyle').value() || 'markdown' const style = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
const copyLink = pasteStyle(style, url) const copyLink = pasteStyle(style, item)
const obj = { const obj = {
title: '复制链接成功', title: '复制链接成功',
body: copyLink, body: copyLink,
icon: url icon: item.url || item.imgUrl
} }
const myNotification = new window.Notification(obj.title, obj) const myNotification = new window.Notification(obj.title, obj)
this.$electron.clipboard.writeText(copyLink) this.$electron.clipboard.writeText(copyLink)
@@ -172,7 +232,9 @@ export default {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
const file = this.$db.read().get('uploaded').getById(id).value()
this.$db.read().get('uploaded').removeById(id).write() this.$db.read().get('uploaded').removeById(id).write()
this.$electron.ipcRenderer.send('removeFiles', [file])
const obj = { const obj = {
title: '操作结果', title: '操作结果',
body: '删除成功' body: '删除成功'
@@ -222,15 +284,19 @@ export default {
isMultiple (obj) { isMultiple (obj) {
return Object.values(obj).some(item => item) 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)) { if (Object.values(this.choosedList).some(item => item)) {
this.$confirm('将删除刚才选中的图片,是否继续?', '提示', { this.$confirm('将删除刚才选中的图片,是否继续?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
let files = []
Object.keys(this.choosedList).forEach(key => { Object.keys(this.choosedList).forEach(key => {
if (this.choosedList[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() this.$db.read().get('uploaded').removeById(key).write()
} }
}) })
@@ -240,6 +306,7 @@ export default {
title: '操作结果', title: '操作结果',
body: '删除成功' body: '删除成功'
} }
this.$electron.ipcRenderer.send('removeFiles', files)
const myNotification = new window.Notification(obj.title, obj) const myNotification = new window.Notification(obj.title, obj)
myNotification.onclick = () => { myNotification.onclick = () => {
return true return true
@@ -252,10 +319,13 @@ export default {
multiCopy () { multiCopy () {
if (Object.values(this.choosedList).some(item => item)) { if (Object.values(this.choosedList).some(item => item)) {
let copyString = '' 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 => { Object.keys(this.choosedList).forEach(key => {
if (this.choosedList[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()
copyString += pasteStyle(style, item) + '\n'
this.choosedList[key] = false
} }
}) })
const obj = { const obj = {
@@ -271,10 +341,19 @@ export default {
}, },
toggleHandleBar () { toggleHandleBar () {
this.handleBarActive = !this.handleBarActive 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 () { beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('updateGallery') this.$electron.ipcRenderer.removeAllListeners('updateGallery')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
} }
} }
</script> </script>
@@ -291,14 +370,17 @@ export default {
transition all .2s ease-in-out transition all .2s ease-in-out
&.active &.active
transform: rotate(180deg) transform: rotate(180deg)
#gallery-view
height 100%
.item-base .item-base
background #2E2E2E background #2E2E2E
text-align center text-align center
margin-bottom 10px
padding 5px 0 padding 5px 0
cursor pointer cursor pointer
font-size 13px font-size 13px
transition all .2s ease-in-out transition all .2s ease-in-out
height: 28px
box-sizing: border-box
&.copy &.copy
cursor not-allowed cursor not-allowed
background #49B1F5 background #49B1F5
@@ -313,10 +395,10 @@ export default {
cursor pointer cursor pointer
background #F15140 background #F15140
color #fff color #fff
.long-list
width: calc(83.3333333% - 6px)
#gallery-view #gallery-view
position relative position relative
.round
border-radius 14px
.pull-right .pull-right
float right float right
.gallery-list .gallery-list
@@ -330,8 +412,8 @@ export default {
transition all .2s ease-in-out .1s transition all .2s ease-in-out .1s
width 100% width 100%
&.small &.small
height: 245px height: 287px
top: 152px top: 113px
&__img &__img
height 150px height 150px
position relative position relative
@@ -339,12 +421,11 @@ export default {
&__item &__item
width 100% width 100%
height 120px height 120px
background-size cover
background-position 50% 50%
background-repeat no-repeat
transition all .2s ease-in-out transition all .2s ease-in-out
cursor pointer cursor pointer
margin-bottom 8px margin-bottom 8px
overflow hidden
display flex
&-fake &-fake
position absolute position absolute
top 0 top 0
@@ -353,8 +434,10 @@ export default {
width 100% width 100%
z-index -1 z-index -1
&:hover &:hover
background-color #49B1F5
transform scale(1.1) transform scale(1.1)
&-img
width 100%
object-fit fill
&__tool-panel &__tool-panel
color #ddd color #ddd
i i
@@ -369,19 +452,9 @@ export default {
&.el-icon-delete &.el-icon-delete
&:hover &:hover
color #F15140 color #F15140
.blueimp-gallery
.title
top 30px
.handle-bar .handle-bar
color #ddd color #ddd
.pic-bed-item margin-bottom 10px
@extend .item-base
&:hover
background #A4D8FA
color #fff
&.active
background #49B1F5
color #fff
.el-input__inner .el-input__inner
border-radius 0 border-radius 14px
</style> </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" :class="{ 'is-dragover': dragover, uploading: showProgress, linux: os === 'linux' }" @drop.prevent="onDrop" @dragover.prevent="dragover = true" @dragleave.prevent="dragover = false"
:style="{ backgroundPosition: '0 ' + progress + '%'}" :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> <input type="file" id="file-uploader" @change="onChange" multiple>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import mixin from './mixin' import mixin from '@/utils/mixin'
import picBed from '../../datastore/pic-bed.js'
export default { export default {
name: 'mini-page', name: 'mini-page',
mixins: [mixin], mixins: [mixin],
data () { data () {
return { return {
logo: 'static/logo.png', logo: 'static/squareLogo.png',
dragover: false, dragover: false,
progress: 0, progress: 0,
showProgress: false, showProgress: false,
@@ -34,13 +33,12 @@ export default {
screenX: '', screenX: '',
screenY: '', screenY: '',
menu: null, menu: null,
os: '' os: '',
picBed: []
} }
}, },
created () { created () {
this.os = process.platform this.os = process.platform
},
mounted () {
this.$electron.ipcRenderer.on('uploadProgress', (event, progress) => { this.$electron.ipcRenderer.on('uploadProgress', (event, progress) => {
if (progress !== -1) { if (progress !== -1) {
this.showProgress = true this.showProgress = true
@@ -50,7 +48,9 @@ export default {
this.showError = true this.showError = true
} }
}) })
this.buildMenu() this.getPicBeds()
},
mounted () {
window.addEventListener('mousedown', this.handleMouseDown, false) window.addEventListener('mousedown', this.handleMouseDown, false)
window.addEventListener('mousemove', this.handleMouseMove, false) window.addEventListener('mousemove', this.handleMouseMove, false)
window.addEventListener('mouseup', this.handleMouseUp, false) window.addEventListener('mouseup', this.handleMouseUp, false)
@@ -69,11 +69,15 @@ export default {
} }
}, },
methods: { methods: {
getPicBeds () {
this.picBed = this.$electron.ipcRenderer.sendSync('getPicBeds')
this.buildMenu()
},
onDrop (e) { onDrop (e) {
this.dragover = false this.dragover = false
this.ipcSendFiles(e.dataTransfer.files) this.ipcSendFiles(e.dataTransfer.files)
}, },
openUplodWindow () { openUploadWindow () {
document.getElementById('file-uploader').click() document.getElementById('file-uploader').click()
}, },
onChange (e) { onChange (e) {
@@ -116,18 +120,9 @@ export default {
this.dragging = false this.dragging = false
if (this.screenX === e.screenX && this.screenY === e.screenY) { if (this.screenX === e.screenX && this.screenY === e.screenY) {
if (e.button === 0) { // left mouse if (e.button === 0) { // left mouse
this.openUplodWindow() this.openUploadWindow()
} else { } else {
let _this = this this.getPicBeds()
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.openContextMenu() this.openContextMenu()
} }
} }
@@ -137,7 +132,7 @@ export default {
}, },
buildMenu () { buildMenu () {
const _this = this const _this = this
const submenu = picBed.map(item => { const submenu = this.picBed.map(item => {
return { return {
label: item.name, label: item.name,
type: 'radio', type: 'radio',
@@ -166,6 +161,17 @@ export default {
_this.$electron.ipcRenderer.send('uploadClipboardFilesFromUploadPage') _this.$electron.ipcRenderer.send('uploadClipboardFilesFromUploadPage')
} }
}, },
{
label: '最小化窗口',
role: 'minimize'
},
{
label: '重启应用',
click () {
_this.$electron.remote.app.relaunch()
_this.$electron.remote.app.exit(0)
}
},
{ {
role: 'quit', role: 'quit',
label: '退出' label: '退出'
@@ -176,6 +182,7 @@ export default {
}, },
beforeDestroy () { beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('uploadProgress') this.$electron.ipcRenderer.removeAllListeners('uploadProgress')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
window.removeEventListener('mousedown', this.handleMouseDown, false) window.removeEventListener('mousedown', this.handleMouseDown, false)
window.removeEventListener('mousemove', this.handleMouseMove, false) window.removeEventListener('mousemove', this.handleMouseMove, false)
window.removeEventListener('mouseup', this.handleMouseUp, false) window.removeEventListener('mouseup', this.handleMouseUp, false)
+579
View File
@@ -0,0 +1,579 @@
<template>
<div id="picgo-setting">
<div class="view-title">
PicGo设置 - <i class="el-icon-document" @click="goConfigPage"></i>
</div>
<el-row class="setting-list">
<el-col :span="15" :offset="4">
<el-row>
<el-form
label-width="120px"
label-position="right"
size="small"
>
<el-form-item
label="打开配置文件"
>
<el-button type="primary" round size="mini" @click="openFile('data.json')">点击打开</el-button>
</el-form-item>
<el-form-item
label="设置日志文件"
>
<el-button type="primary" round size="mini" @click="openLogSetting">点击设置</el-button>
</el-form-item>
<el-form-item
label="修改上传快捷键"
>
<el-button type="primary" round size="mini" @click="keyBindingVisible = true">点击设置</el-button>
</el-form-item>
<el-form-item
label="自定义链接格式"
>
<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-form-item>
<el-form-item
label="打开更新助手"
>
<el-switch
v-model="form.updateHelper"
active-text=""
inactive-text=""
@change="updateHelperChange"
></el-switch>
</el-form-item>
<el-form-item
label="开机自启"
>
<el-switch
v-model="form.autoStart"
active-text=""
inactive-text=""
@change="handleAutoStartChange"
></el-switch>
</el-form-item>
<el-form-item
label="上传前重命名"
>
<el-switch
v-model="form.rename"
active-text=""
inactive-text=""
@change="handleRename"
></el-switch>
</el-form-item>
<el-form-item
label="时间戳重命名"
>
<el-switch
v-model="form.autoRename"
active-text=""
inactive-text=""
@change="handleAutoRename"
></el-switch>
</el-form-item>
<el-form-item
label="开启上传提示"
>
<el-switch
v-model="form.uploadNotification"
active-text=""
inactive-text=""
@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="选择显示的图床"
>
<el-checkbox-group
v-model="form.showPicBedList"
@change="handleShowPicBedListChange"
>
<el-checkbox
v-for="item in picBed"
:label="item.name"
:key="item.name"
></el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</el-row>
</el-col>
</el-row>
<el-dialog
title="修改上传快捷键"
:visible.sync="keyBindingVisible"
:modal-append-to-body="false"
>
<el-form
label-width="80px"
>
<el-form-item
label="快捷上传"
>
<el-input
class="align-center"
@keydown.native.prevent="keyDetect('upload', $event)"
v-model="shortKey.upload"
:autofocus="true"
></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="cancelKeyBinding" round>取消</el-button>
<el-button type="primary" @click="confirmKeyBinding" round>确定</el-button>
</span>
</el-dialog>
<el-dialog
title="自定义链接格式"
:visible.sync="customLinkVisible"
:modal-append-to-body="false"
>
<el-form
label-position="top"
:model="customLink"
ref="customLink"
:rules="rules"
size="small"
>
<el-form-item
prop="value"
>
<div class="custom-title">
用占位符 <b>$url</b> 来表示url的位置
</div>
<div class="custom-title">
用占位符 <b>$fileName</b> 来表示文件名的位置
</div>
<el-input
class="align-center"
v-model="customLink.value"
:autofocus="true"
></el-input>
</el-form-item>
</el-form>
<div>
[$fileName]($url)
</div>
<span slot="footer">
<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
title="检查更新"
:visible.sync="checkUpdateVisible"
:modal-append-to-body="false"
>
<div>
当前版本{{ version }}
</div>
<div>
最新版本{{ latestVersion ? latestVersion : '正在获取中...' }}
</div>
<div v-if="needUpdate">
PicGo更新啦请点击确定打开下载页面
</div>
<span slot="footer">
<el-button @click="cancelCheckVersion" round>取消</el-button>
<el-button type="primary" @click="confirmCheckVersion" round>确定</el-button>
</span>
</el-dialog>
<el-dialog
title="设置日志文件"
:visible.sync="logFileVisible"
:modal-append-to-body="false"
>
<el-form
label-position="right"
label-width="100px"
>
<el-form-item
label="日志文件"
>
<el-button type="primary" round size="mini" @click="openFile('picgo.log')">点击打开</el-button>
</el-form-item>
<el-form-item
label="日志记录等级"
>
<el-select
v-model="form.logLevel"
multiple
collapse-tags
>
<el-option
v-for="(value, key) of logLevel"
:key="key"
:label="value"
:value="key"
:disabled="handleLevelDisabled(key)"
></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="cancelLogLevelSetting" round>取消</el-button>
<el-button type="primary" @click="confirmLogLevelSetting" round>确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import keyDetect from 'utils/key-binding'
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 {
name: 'picgo-setting',
computed: {
needUpdate () {
if (this.latestVersion) {
return this.compareVersion2Update(this.version, this.latestVersion)
} else {
return false
}
}
},
data () {
const customLinkRule = (rule, value, callback) => {
if (!/\$url/.test(value)) {
return callback(new Error('必须含有$url'))
} else {
return callback()
}
}
let logLevel = this.$db.read().get('settings.logLevel').value()
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
return {
form: {
updateHelper: this.$db.read().get('settings.showUpdateTip').value(),
showPicBedList: [],
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,
logLevel
},
picBed: [],
logFileVisible: false,
keyBindingVisible: false,
customLinkVisible: false,
checkUpdateVisible: false,
proxyVisible: false,
customLink: {
value: this.$db.read().get('settings.customLink').value() || '$url'
},
shortKey: {
upload: this.$db.read().get('settings.shortKey.upload').value()
},
proxy: this.$db.read().get('picBed.proxy').value() || undefined,
rules: {
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
},
logLevel: {
all: '全部-All',
success: '成功-Success',
error: '错误-Error',
info: '普通-Info',
warn: '提醒-Warn',
none: '不记录日志-None'
},
version: pkg.version,
latestVersion: '',
os: ''
}
},
created () {
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
}
})
},
openFile (file) {
const { app, shell } = this.$electron.remote
const STORE_PATH = app.getPath('userData')
const FILE = path.join(STORE_PATH, `/${file}`)
shell.openItem(FILE)
},
openLogSetting () {
this.logFileVisible = true
},
keyDetect (type, event) {
this.shortKey[type] = keyDetect(event).join('+')
},
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = this.$db.read().get('settings.shortKey').value()
},
confirmKeyBinding () {
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 = this.$db.read().get('settings.customLink').value() || '$url'
},
confirmCustomLink () {
this.$refs.customLink.validate((valid) => {
if (valid) {
this.$db.read().set('settings.customLink', this.customLink.value).write()
this.customLinkVisible = false
this.$electron.ipcRenderer.send('updateCustomLink')
} else {
return false
}
})
},
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('settings.showUpdateTip', val).write()
},
handleShowPicBedListChange (val) {
const list = this.picBed.map(item => {
if (!val.includes(item.name)) {
item.visible = false
} else {
item.visible = true
}
return item
})
this.$db.read().set('picBed.list', list).write()
this.$electron.ipcRenderer.send('getPicBeds')
},
handleAutoStartChange (val) {
this.$db.read().set('settings.autoStart', val).write()
this.$electron.ipcRenderer.send('autoStart', val)
},
handleRename (val) {
this.$db.read().set('settings.rename', val).write()
},
handleAutoRename (val) {
this.$db.read().set('settings.autoRename', val).write()
},
compareVersion2Update (current, latest) {
const currentVersion = current.split('.').map(item => parseInt(item))
const latestVersion = latest.split('.').map(item => parseInt(item))
for (let i = 0; i < 3; i++) {
if (currentVersion[i] < latestVersion[i]) {
return true
}
if (currentVersion[i] > latestVersion[i]) {
return false
}
}
return false
},
checkUpdate () {
this.checkUpdateVisible = true
this.$http.get(release)
.then(res => {
this.latestVersion = res.data.name
}).catch(err => {
console.log(err)
})
},
confirmCheckVersion () {
if (this.needUpdate) {
this.$electron.remote.shell.openExternal(downloadUrl)
}
this.checkUpdateVisible = false
},
cancelCheckVersion () {
this.checkUpdateVisible = false
},
handleUploadNotification (val) {
this.$db.read().set('settings.uploadNotification', val).write()
},
handleMiniWindowOntop (val) {
this.$db.read().set('settings.miniWindowOntop', val).write()
this.$message('需要重启生效')
},
confirmLogLevelSetting () {
if (this.form.logLevel.length === 0) {
return this.$message.error('请选择日志记录等级')
}
this.$db.read().set('settings.logLevel', this.form.logLevel).write()
const successNotification = new window.Notification('设置日志', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
this.logFileVisible = false
},
cancelLogLevelSetting () {
this.logFileVisible = false
let logLevel = this.$db.read().get('settings.logLevel').value()
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
this.form.logLevel = logLevel
},
handleLevelDisabled (val) {
let currentLevel = val
let flagLevel
let result = this.form.logLevel.some(item => {
if (item === 'all' || item === 'none') {
flagLevel = item
}
return (item === 'all' || item === 'none')
})
if (result) {
if (currentLevel !== flagLevel) {
return true
}
} else if (this.form.logLevel.length > 0) {
if (val === 'all' || val === 'none') {
return true
}
}
return false
},
goConfigPage () {
this.$electron.remote.shell.openExternal('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
}
},
beforeDestroy () {
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}
}
</script>
<style lang='stylus'>
.el-message
left 60%
.view-title
.el-icon-document
cursor pointer
transition color .2s ease-in-out
&:hover
color #49B1F5
#picgo-setting
.sub-title
font-size 14px
.setting-list
height 360px
box-sizing border-box
overflow-y auto
overflow-x hidden
.setting-list
.el-form
label
line-height 32px
padding-bottom 0
color #eee
.el-button-group
width 100%
.el-button
width 50%
.el-input__inner
border-radius 19px
.el-radio-group
margin-left 25px
.el-switch__label
color #eee
&.is-active
color #409EFF
.el-icon-question
font-size 20px
float right
margin-top 9px
color #eee
cursor pointer
transition .2s color ease-in-out
&:hover
color #409EFF
.el-checkbox-group
label
margin-right 30px
width 100px
.el-checkbox+.el-checkbox
margin-right 30px
margin-left 0
.confirm-button
width 100%
</style>
+565
View File
@@ -0,0 +1,565 @@
<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>
</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: ''
}
},
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()
},
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)
}
},
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')
}
}
</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
transition color .2s ease-in-out
&:hover
color #49B1F5
.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> </div>
</template> </template>
<script> <script>
import mixin from '@/utils/mixin'
export default { export default {
name: 'rename-page', name: 'rename-page',
mixins: [mixin],
data () { data () {
return { return {
fileName: '', fileName: '',
@@ -4,15 +4,18 @@
<div class="content"> <div class="content">
<div class="wait-upload-img" v-if="clipboardFiles.length > 0"> <div class="wait-upload-img" v-if="clipboardFiles.length > 0">
<div class="list-title">等待上传</div> <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 v-for="(item, index) in clipboardFiles" :key="index" class="img-list">
<div class="upload-img__container" @click="uploadClipboardFiles"> <div
class="upload-img__container"
:class="{ upload: uploadFlag }"
@click="uploadClipboardFiles">
<img :src="item.imgUrl" class="upload-img"> <img :src="item.imgUrl" class="upload-img">
</div> </div>
</div> </div>
</div> </div>
<div class="uploaded-img"> <div class="uploaded-img">
<div class="list-title">已上传</div> <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">
<div class="upload-img__container" @click="copyTheLink(item)"> <div class="upload-img__container" @click="copyTheLink(item)">
<img :src="item.imgUrl" class="upload-img"> <img :src="item.imgUrl" class="upload-img">
</div> </div>
@@ -23,9 +26,11 @@
</template> </template>
<script> <script>
import pasteTemplate from '../../main/utils/pasteTemplate' import mixin from '@/utils/mixin'
import pasteTemplate from '~/main/utils/pasteTemplate'
export default { export default {
name: 'tray-page', name: 'tray-page',
mixins: [mixin],
data () { data () {
return { return {
files: [], files: [],
@@ -34,7 +39,8 @@
body: '', body: '',
icon: '' icon: ''
}, },
clipboardFiles: [] clipboardFiles: [],
uploadFlag: false
} }
}, },
computed: { computed: {
@@ -54,11 +60,10 @@
this.$electron.ipcRenderer.on('clipboardFiles', (event, files) => { this.$electron.ipcRenderer.on('clipboardFiles', (event, files) => {
this.clipboardFiles = files this.clipboardFiles = files
}) })
this.$electron.ipcRenderer.on('uploadFiles', (event, files) => { this.$electron.ipcRenderer.on('uploadFiles', (event) => {
files.forEach(item => {
this.$db.read().get('uploaded').insert(item).write()
})
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value() this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
console.log(this.files)
this.uploadFlag = false
}) })
this.$electron.ipcRenderer.on('updateFiles', (event) => { this.$electron.ipcRenderer.on('updateFiles', (event) => {
this.getData() this.getData()
@@ -78,8 +83,8 @@
this.notification.body = item.imgUrl this.notification.body = item.imgUrl
this.notification.icon = item.imgUrl this.notification.icon = item.imgUrl
const myNotification = new window.Notification(this.notification.title, this.notification) 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)) this.$electron.clipboard.writeText(pasteTemplate(pasteStyle, item))
myNotification.onclick = () => { myNotification.onclick = () => {
return true return true
} }
@@ -98,14 +103,21 @@
}, false) }, false)
}, },
uploadClipboardFiles () { uploadClipboardFiles () {
this.$electron.ipcRenderer.send('uploadClipboardFiles', this.clipboardFiles[0]) if (this.uploadFlag) {
return
}
this.uploadFlag = true
this.$electron.ipcRenderer.send('uploadClipboardFiles')
} }
} }
} }
</script> </script>
<style lang="stylus"> <style lang="stylus">
body::-webkit-scrollbar
width 0px
#tray-page #tray-page
background-color transparent
.list-title .list-title
text-align center text-align center
color #858585 color #858585
@@ -135,11 +147,11 @@
top 0px top 0px
width 100% width 100%
.img-list .img-list
padding 16px 8px padding 8px 8px
display flex display flex
justify-content space-between justify-content space-between
align-items center align-items center
height 45px // height 45px
cursor pointer cursor pointer
transition all .2s ease-in-out transition all .2s ease-in-out
&:hover &:hover
@@ -147,11 +159,13 @@
.upload-img__index .upload-img__index
color #fff color #fff
.upload-img .upload-img
height 100%
width 100% width 100%
object-fit scale-down
margin 0 auto margin 0 auto
&__container &__container
width 100% width 100%
padding 8px 10px padding 8px 10px
height 100% height 100%
&.upload
cursor not-allowed
</style> </style>
@@ -3,11 +3,14 @@
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="20" :offset="2"> <el-col :span="20" :offset="2">
<div class="view-title"> <div class="view-title">
图片上传 - {{ picBed }} 图片上传 - {{ picBedName }} <i class="el-icon-caret-bottom" @click="handleChangePicBed"></i>
</div> </div>
<div <div
id="upload-area" 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"> <div id="upload-dragger" @click="openUplodWindow">
<i class="el-icon-upload"></i> <i class="el-icon-upload"></i>
@@ -22,7 +25,7 @@
:show-text="false" :show-text="false"
class="upload-progress" class="upload-progress"
:class="{ 'show': showProgress }" :class="{ 'show': showProgress }"
:status="showError ? 'exception' : ''" :status="showError ? 'exception' : 'text'"
></el-progress> ></el-progress>
<div class="paste-style"> <div class="paste-style">
<div class="el-col-16"> <div class="el-col-16">
@@ -53,10 +56,8 @@
</div> </div>
</template> </template>
<script> <script>
import mixin from '../mixin'
export default { export default {
name: 'upload', name: 'upload',
mixins: [mixin],
data () { data () {
return { return {
dragover: false, dragover: false,
@@ -64,7 +65,9 @@ export default {
showProgress: false, showProgress: false,
showError: false, showError: false,
pasteStyle: '', pasteStyle: '',
picBed: '' picBed: [],
picBedName: '',
menu: null
} }
}, },
mounted () { mounted () {
@@ -82,6 +85,8 @@ export default {
this.$electron.ipcRenderer.on('syncPicBed', () => { this.$electron.ipcRenderer.on('syncPicBed', () => {
this.getDefaultPicBed() this.getDefaultPicBed()
}) })
this.$electron.ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds)
}, },
watch: { watch: {
progress (val) { progress (val) {
@@ -99,6 +104,7 @@ export default {
beforeDestroy () { beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('uploadProgress') this.$electron.ipcRenderer.removeAllListeners('uploadProgress')
this.$electron.ipcRenderer.removeAllListeners('syncPicBed') this.$electron.ipcRenderer.removeAllListeners('syncPicBed')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}, },
methods: { methods: {
onDrop (e) { onDrop (e) {
@@ -124,10 +130,10 @@ export default {
this.$electron.ipcRenderer.send('uploadChoosedFiles', sendFiles) this.$electron.ipcRenderer.send('uploadChoosedFiles', sendFiles)
}, },
getPasteStyle () { getPasteStyle () {
this.pasteStyle = this.$db.read().get('picBed.pasteStyle').value() || 'markdown' this.pasteStyle = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
}, },
handlePasteStyleChange (val) { handlePasteStyleChange (val) {
this.$db.read().set('picBed.pasteStyle', val) this.$db.read().set('settings.pasteStyle', val)
.write() .write()
}, },
uploadClipboardFiles () { uploadClipboardFiles () {
@@ -135,11 +141,34 @@ export default {
}, },
getDefaultPicBed () { getDefaultPicBed () {
const current = this.$db.read().get('picBed.current').value() const current = this.$db.read().get('picBed.current').value()
this.$picBed.forEach(item => { this.picBed.forEach(item => {
if (item.type === current) { 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 color #eee
font-size 20px font-size 20px
text-align center text-align center
margin 20px auto margin 10px auto
#upload-view #upload-view
.view-title
margin 20px auto
#upload-area #upload-area
height 220px height 220px
border 2px dashed #dddddd border 2px dashed #dddddd
@@ -65,10 +65,10 @@
</div> </div>
</template> </template>
<script> <script>
import mixin from '../mixin' import mixin from '@/utils/ConfirmButtonMixin'
export default { export default {
name: 'aliyun',
mixins: [mixin], mixins: [mixin],
name: 'aliyun',
data () { data () {
return { return {
form: { form: {
@@ -109,11 +109,6 @@ export default {
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#aliyun-view #aliyun-view
.el-form .el-form
label label
@@ -33,7 +33,7 @@
:rules="{ :rules="{
required: true, message: 'Token不能为空', trigger: 'blur' required: true, message: 'Token不能为空', trigger: 'blur'
}"> }">
<el-input v-model="form.token" @keyup.native.enter="confirm" placeholder="token"></el-input> <el-input v-model="form.token" @keyup.native.enter="confirm" placeholder="token" type="password"></el-input>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label="指定存储路径" label="指定存储路径"
@@ -57,9 +57,9 @@
</div> </div>
</template> </template>
<script> <script>
import mixin from '../mixin' import mixin from '@/utils/ConfirmButtonMixin'
export default { export default {
name: 'upyun', name: 'github',
mixins: [mixin], mixins: [mixin],
data () { data () {
return { return {
@@ -100,11 +100,6 @@ export default {
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#github-view #github-view
.el-form .el-form
label label
@@ -37,7 +37,7 @@
</div> </div>
</template> </template>
<script> <script>
import mixin from '../mixin' import mixin from '@/utils/ConfirmButtonMixin'
export default { export default {
name: 'imgur', name: 'imgur',
mixins: [mixin], mixins: [mixin],
@@ -77,11 +77,6 @@ export default {
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#imgur-view #imgur-view
.el-form .el-form
label 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>
@@ -45,13 +45,10 @@
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label="确认存储区域" label="确认存储区域"
> :rules="{
<el-radio-group v-model="form.area" size="mini"> required: true, message: '区域代码不能为空', trigger: 'blur'
<el-radio-button label="z0">华东</el-radio-button> }">
<el-radio-button label="z1">华北</el-radio-button> <el-input v-model="form.area" placeholder="例如z0"></el-input>
<el-radio-button label="z2">华南</el-radio-button>
<el-radio-button label="na0">北美</el-radio-button>
</el-radio-group>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label="设定网址后缀" label="设定网址后缀"
@@ -75,10 +72,10 @@
</div> </div>
</template> </template>
<script> <script>
import mixin from '../mixin' import mixin from '@/utils/ConfirmButtonMixin'
export default { export default {
name: 'qiniu',
mixins: [mixin], mixins: [mixin],
name: 'qiniu',
data () { data () {
return { return {
form: { form: {
@@ -86,7 +83,7 @@ export default {
secretKey: '', secretKey: '',
bucket: '', bucket: '',
url: '', url: '',
area: 'z0', area: '',
options: '', options: '',
path: '' path: ''
} }
@@ -120,11 +117,6 @@ export default {
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#qiniu-view #qiniu-view
.el-form .el-form
label label
@@ -16,10 +16,10 @@
</div> </div>
</template> </template>
<script> <script>
import mixin from '../mixin' import mixin from '@/utils/ConfirmButtonMixin'
export default { export default {
name: 'upyun',
mixins: [mixin], mixins: [mixin],
name: 'upyun',
data () { data () {
return {} return {}
}, },
@@ -38,11 +38,6 @@ export default {
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#smms-view #smms-view
.content .content
text-align center text-align center
@@ -86,10 +86,10 @@
</div> </div>
</template> </template>
<script> <script>
import mixin from '../mixin' import mixin from '@/utils/ConfirmButtonMixin'
export default { export default {
name: 'tcyun',
mixins: [mixin], mixins: [mixin],
name: 'tcyun',
data () { data () {
return { return {
form: { form: {
@@ -129,17 +129,12 @@ export default {
}) })
}, },
openWiki () { 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> </script>
<style lang='stylus'> <style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#tcyun-view #tcyun-view
.el-form .el-form
label label
@@ -65,7 +65,7 @@
</div> </div>
</template> </template>
<script> <script>
import mixin from '../mixin' import mixin from '@/utils/ConfirmButtonMixin'
export default { export default {
name: 'upyun', name: 'upyun',
mixins: [mixin], mixins: [mixin],
@@ -108,11 +108,6 @@ export default {
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#tcyun-view #tcyun-view
.el-form .el-form
label label
@@ -62,7 +62,7 @@
</div> </div>
</template> </template>
<script> <script>
import mixin from '../mixin' import mixin from '@/utils/ConfirmButtonMixin'
export default { export default {
name: 'weibo', name: 'weibo',
mixins: [mixin], mixins: [mixin],
@@ -113,7 +113,7 @@ export default {
this.$refs['weiboForm'].resetFields() this.$refs['weiboForm'].resetFields()
}, },
openWiki () { 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'> <style lang='stylus'>
.el-message .el-message
left 60% left 60%
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
#weibo-view #weibo-view
.el-form .el-form
label label
+29 -16
View File
@@ -8,77 +8,90 @@ export default new Router({
{ {
path: '/', path: '/',
name: 'tray-page', name: 'tray-page',
component: require('@/components/TrayPage').default component: require('@/pages/TrayPage').default
}, },
{ {
path: '/rename-page', path: '/rename-page',
name: 'rename-page', name: 'rename-page',
component: require('@/components/RenamePage').default component: require('@/pages/RenamePage').default
}, },
{ {
path: '/mini-page', path: '/mini-page',
name: 'mini-page', name: 'mini-page',
component: require('@/components/MiniPage').default component: require('@/pages/MiniPage').default
}, },
{ {
path: '/setting', path: '/setting',
name: 'setting-page', name: 'setting-page',
component: require('@/components/SettingPage').default, component: require('@/layouts/SettingPage').default,
children: [ children: [
{ {
path: 'upload', path: 'upload',
component: require('@/components/SettingView/Upload').default, component: require('@/pages/Upload').default,
name: 'upload' name: 'upload'
}, },
{ {
path: 'weibo', path: 'weibo',
component: require('@/components/SettingView/Weibo').default, component: require('@/pages/picbeds/Weibo').default,
name: 'weibo' name: 'weibo'
}, },
{ {
path: 'qiniu', path: 'qiniu',
component: require('@/components/SettingView/Qiniu').default, component: require('@/pages/picbeds/Qiniu').default,
name: 'qiniu' name: 'qiniu'
}, },
{ {
path: 'tcyun', path: 'tcyun',
component: require('@/components/SettingView/TcYun').default, component: require('@/pages/picbeds/TcYun').default,
name: 'tcyun' name: 'tcyun'
}, },
{ {
path: 'upyun', path: 'upyun',
component: require('@/components/SettingView/UpYun').default, component: require('@/pages/picbeds/UpYun').default,
name: 'upyun' name: 'upyun'
}, },
{ {
path: 'github', path: 'github',
component: require('@/components/SettingView/GitHub').default, component: require('@/pages/picbeds/GitHub').default,
name: 'github' name: 'github'
}, },
{ {
path: 'smms', path: 'smms',
component: require('@/components/SettingView/SMMS').default, component: require('@/pages/picbeds/SMMS').default,
name: 'smms' name: 'smms'
}, },
{ {
path: 'aliyun', path: 'aliyun',
component: require('@/components/SettingView/AliYun').default, component: require('@/pages/picbeds/AliYun').default,
name: 'aliyun' name: 'aliyun'
}, },
{ {
path: 'imgur', path: 'imgur',
component: require('@/components/SettingView/Imgur').default, component: require('@/pages/picbeds/Imgur').default,
name: 'imgur' name: 'imgur'
}, },
{
path: 'others/:type',
component: require('@/pages/picbeds/Others').default,
name: 'others'
},
{ {
path: 'gallery', path: 'gallery',
component: require('@/components/SettingView/Gallery').default, component: require('@/pages/Gallery').default,
name: 'gallery' name: 'gallery',
meta: {
keepAlive: true
}
}, },
{ {
path: 'setting', path: 'setting',
component: require('@/components/SettingView/PicGoSetting').default, component: require('@/pages/PicGoSetting').default,
name: 'setting' 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 { export default {
mounted () { mounted () {
this.disableDragEvent() this.disableDragEvent()
}, },
data () {
return {
defaultPicBed: db.read().get('picBed.current').value()
}
},
methods: { methods: {
disableDragEvent () { disableDragEvent () {
window.addEventListener('dragenter', this.disableDrag, false) window.addEventListener('dragenter', this.disableDrag, false)
@@ -21,17 +15,6 @@ export default {
e.dataTransfer.effectAllowed = 'none' e.dataTransfer.effectAllowed = 'none'
e.dataTransfer.dropEffect = '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 () { beforeDestroy () {
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSServices</key>
<array>
<dict>
<key>NSBackgroundColorName</key>
<string>background</string>
<key>NSBackgroundSystemColorName</key>
<string>systemBlueColor</string>
<key>NSIconName</key>
<string>NSTouchBarShare</string>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Upload pictures with PicGo</string>
</dict>
<key>NSMessage</key>
<string>runWorkflowAsService</string>
<key>NSRequiredContext</key>
<dict>
<key>NSApplicationIdentifier</key>
<string>com.apple.finder</string>
</dict>
<key>NSSendFileTypes</key>
<array>
<string>public.image</string>
</array>
</dict>
</array>
</dict>
</plist>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1,314 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AMApplicationBuild</key>
<string>444.42</string>
<key>AMApplicationVersion</key>
<string>2.9</string>
<key>AMDocumentVersion</key>
<string>2</string>
<key>actions</key>
<array>
<dict>
<key>action</key>
<dict>
<key>AMAccepts</key>
<dict>
<key>Container</key>
<string>List</string>
<key>Optional</key>
<true/>
<key>Types</key>
<array>
<string>com.apple.cocoa.string</string>
</array>
</dict>
<key>AMActionVersion</key>
<string>2.0.3</string>
<key>AMApplication</key>
<array>
<string>自动操作</string>
</array>
<key>AMParameterProperties</key>
<dict>
<key>COMMAND_STRING</key>
<dict/>
<key>CheckedForUserDefaultShell</key>
<dict/>
<key>inputMethod</key>
<dict/>
<key>shell</key>
<dict/>
<key>source</key>
<dict/>
</dict>
<key>AMProvides</key>
<dict>
<key>Container</key>
<string>List</string>
<key>Types</key>
<array>
<string>com.apple.cocoa.string</string>
</array>
</dict>
<key>ActionBundlePath</key>
<string>/System/Library/Automator/Run Shell Script.action</string>
<key>ActionName</key>
<string>运行 Shell 脚本</string>
<key>ActionParameters</key>
<dict>
<key>COMMAND_STRING</key>
<string>/Applications/PicGo.app/Contents/MacOS/PicGo upload "$@" &gt; /dev/null 2&gt;&amp;1 &amp;</string>
<key>CheckedForUserDefaultShell</key>
<true/>
<key>inputMethod</key>
<integer>1</integer>
<key>shell</key>
<string>/bin/bash</string>
<key>source</key>
<string></string>
</dict>
<key>BundleIdentifier</key>
<string>com.apple.RunShellScript</string>
<key>CFBundleVersion</key>
<string>2.0.3</string>
<key>CanShowSelectedItemsWhenRun</key>
<false/>
<key>CanShowWhenRun</key>
<true/>
<key>Category</key>
<array>
<string>AMCategoryUtilities</string>
</array>
<key>Class Name</key>
<string>RunShellScriptAction</string>
<key>InputUUID</key>
<string>79609224-28DD-4ADE-AA8F-5A6C68C18C18</string>
<key>Keywords</key>
<array>
<string>Shell</string>
<string>脚本</string>
<string>命令</string>
<string>运行</string>
<string>Unix</string>
</array>
<key>OutputUUID</key>
<string>35CD6B4C-A616-4F89-8D76-DCD3249C5B4E</string>
<key>UUID</key>
<string>4350A83B-E7E6-4D2B-9768-B1D676CF58F3</string>
<key>UnlocalizedApplications</key>
<array>
<string>Automator</string>
</array>
<key>arguments</key>
<dict>
<key>0</key>
<dict>
<key>default value</key>
<integer>0</integer>
<key>name</key>
<string>inputMethod</string>
<key>required</key>
<string>0</string>
<key>type</key>
<string>0</string>
<key>uuid</key>
<string>0</string>
</dict>
<key>1</key>
<dict>
<key>default value</key>
<string></string>
<key>name</key>
<string>source</string>
<key>required</key>
<string>0</string>
<key>type</key>
<string>0</string>
<key>uuid</key>
<string>1</string>
</dict>
<key>2</key>
<dict>
<key>default value</key>
<false/>
<key>name</key>
<string>CheckedForUserDefaultShell</string>
<key>required</key>
<string>0</string>
<key>type</key>
<string>0</string>
<key>uuid</key>
<string>2</string>
</dict>
<key>3</key>
<dict>
<key>default value</key>
<string></string>
<key>name</key>
<string>COMMAND_STRING</string>
<key>required</key>
<string>0</string>
<key>type</key>
<string>0</string>
<key>uuid</key>
<string>3</string>
</dict>
<key>4</key>
<dict>
<key>default value</key>
<string>/bin/sh</string>
<key>name</key>
<string>shell</string>
<key>required</key>
<string>0</string>
<key>type</key>
<string>0</string>
<key>uuid</key>
<string>4</string>
</dict>
</dict>
<key>isViewVisible</key>
<true/>
<key>location</key>
<string>449.000000:305.000000</string>
<key>nibPath</key>
<string>/System/Library/Automator/Run Shell Script.action/Contents/Resources/Base.lproj/main.nib</string>
</dict>
<key>isViewVisible</key>
<true/>
</dict>
</array>
<key>connectors</key>
<dict/>
<key>workflowMetaData</key>
<dict>
<key>applicationBundleID</key>
<string>com.apple.finder</string>
<key>applicationBundleIDsByPath</key>
<dict>
<key>/System/Library/CoreServices/Finder.app</key>
<string>com.apple.finder</string>
</dict>
<key>applicationPath</key>
<string>/System/Library/CoreServices/Finder.app</string>
<key>applicationPaths</key>
<array>
<string>/System/Library/CoreServices/Finder.app</string>
</array>
<key>backgroundColor</key>
<data>
YnBsaXN0MDDUAQIDBAUGNjdYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVy
VCR0b3ASAAGGoKoHCBMUFR4kKC8zVSRudWxs1QkKCwwNDg8QERJWJGNsYXNz
W05TQ29sb3JOYW1lXE5TQ29sb3JTcGFjZV1OU0NhdGFsb2dOYW1lV05TQ29s
b3KACYADEAaAAoAEVlN5c3RlbV8QD3N5c3RlbUJsdWVDb2xvctUWFwsYCRka
GxwOXE5TQ29tcG9uZW50c1VOU1JHQl8QEk5TQ3VzdG9tQ29sb3JTcGFjZUcw
IDAgMSAxTxARMCAwIDAuOTk4MTg4OTcyNQAQAYAFgAnTHyAJISIjVE5TSURV
TlNJQ0MQB4AGgAjSJQkmJ1dOUy5kYXRhTxEMSAAADEhMaW5vAhAAAG1udHJS
R0IgWFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAA
AAAAAAAAAAD21gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGE
AAAAbHd0cHQAAAHwAAAAFGJrcHQAAAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoA
AAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJUAAAAcGRtZGQAAALEAAAAiHZ1
ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAAFG1lYXMAAAQMAAAA
JHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJUUkMAAAQ8
AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2Fy
ZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAA
AAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAA
AAEWzFhZWiAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BY
WVogAAAAAAAAYpkAALeFAAAY2lhZWiAAAAAAAAAkoAAAD4QAALbPZGVzYwAA
AAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAWSUVDIGh0
dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVm
YXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2
MTk2Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAA
AAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2lu
ZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJl
bmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAAE6T+ABRfLgAQzxQAA+3MAAQT
CwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFzAAAAAAAAAAEAAAAA
AAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAAAAAABAAA
AAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt
AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA
4ADlAOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFn
AW4BdQF8AYMBiwGSAZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQC
HQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1
AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kE
BgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6
BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0G
rwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghG
CFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEK
Jwo9ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwq
DEMMXAx1DI4MpwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQO
fw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1
ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QT
xRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwWjxay
FtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0a
BBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1w
HZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwh
SCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4
JWgllyXHJfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWsp
nSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4W
Lkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQz
DTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgU
OFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9
oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6
Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1J
Y0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+T
T91QJ1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9W
XFapVvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0n
XXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBk
lGTpZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/
bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6cZVx8HJLcqZzAXNdc7h0
FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7wnwh
fIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE
44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Y
jf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+X
Cpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBp
oNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyq
j6sCq3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSc
tRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/
er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8yTrJuco4
yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V
0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE
4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHt
nO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH
+lf65/t3/Af8mP0p/br+S/7c/23//4AH0ikqKyxaJGNsYXNzbmFtZVgkY2xh
c3Nlc11OU011dGFibGVEYXRhoystLlZOU0RhdGFYTlNPYmplY3TSKSowMVxO
U0NvbG9yU3BhY2WiMi5cTlNDb2xvclNwYWNl0ikqNDVXTlNDb2xvcqI0Ll8Q
D05TS2V5ZWRBcmNoaXZlctE4OVRyb290gAEACAARABoAIwAtADIANwBCAEgA
UwBaAGYAcwCBAIkAiwCNAI8AkQCTAJoArAC3AMQAygDfAOcA+wD9AP8BAQEI
AQ0BEwEVARcBGQEeASYNcg10DXkNhA2NDZsNnw2mDa8NtA3BDcQN0Q3WDd4N
4Q3zDfYN+wAAAAAAAAIBAAAAAAAAADoAAAAAAAAAAAAAAAAAAA39
</data>
<key>backgroundColorName</key>
<string>systemBlueColor</string>
<key>inputTypeIdentifier</key>
<string>com.apple.Automator.fileSystemObject.image</string>
<key>outputTypeIdentifier</key>
<string>com.apple.Automator.nothing</string>
<key>presentationMode</key>
<integer>15</integer>
<key>processesInput</key>
<integer>0</integer>
<key>serviceApplicationBundleID</key>
<string>com.apple.finder</string>
<key>serviceApplicationPath</key>
<string>/System/Library/CoreServices/Finder.app</string>
<key>serviceInputTypeIdentifier</key>
<string>com.apple.Automator.fileSystemObject.image</string>
<key>serviceOutputTypeIdentifier</key>
<string>com.apple.Automator.nothing</string>
<key>serviceProcessesInput</key>
<integer>0</integer>
<key>systemImageName</key>
<string>NSTouchBarShare</string>
<key>useAutomaticInputType</key>
<integer>0</integer>
<key>workflowTypeIdentifier</key>
<string>com.apple.Automator.servicesMenu</string>
</dict>
</dict>
</plist>
+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
+4487 -2445
View File
File diff suppressed because it is too large Load Diff