Compare commits

..
55 Commits
Author SHA1 Message Date
Molunerfinn 962c9f2f37 🎉 Release: v2.2.2 2020-01-20 15:47:49 +08:00
Molunerfinn 4ebd76ff11 🐛 Fix: picgo-server upload clipboard file's result bug 2020-01-20 15:44:06 +08:00
Molunerfinn c3fe613696 🔨 Refactor: move more codes from background.ts -> apis/* 2020-01-10 22:34:08 +08:00
Molunerfinn ee46ab1fb7 🐛 Fix: releaseUrl may can't get latest version 2020-01-09 16:35:21 +08:00
Molunerfinn 3caeccc492 🎉 Release: v2.2.1 2020-01-09 11:07:43 +08:00
Molunerfinn c4f0a30c51 🐛 Fix: removeById handler error
ISSUES CLOSED: #382
2020-01-09 11:05:01 +08:00
Molunerfinn 762dc9b2cc 🔨 Refactor: move some utils to apis 2020-01-08 19:26:25 +08:00
Molunerfinn 73870a5326 🐛 Fix: server may never start 2020-01-08 10:57:19 +08:00
PiEgg 5f2b7c7802 🐛 Fix: miniWindow minimize bug 2020-01-07 23:16:35 +08:00
Molunerfinn 0d6fdc4727 🔨 Refactor(background): convergence of BrowserWindow creation to windowManager 2020-01-07 20:49:27 +08:00
Molunerfinn ded7998fbc 🔨 Refactor: fileName case 2020-01-06 20:21:49 +08:00
PiEgg 3913cef1e6 🎉 Release: v2.2.0 2020-01-01 20:03:02 +08:00
PiEgg b4f10c61f0 🐛 Fix: release script 2020-01-01 18:17:55 +08:00
PiEgg a04a25c7e0 🔨 Refactor: change shortKey page style 2020-01-01 18:01:38 +08:00
PiEgg e1a0cbb8a1 🐛 Fix: busApi event register first && emit later 2020-01-01 17:30:06 +08:00
PiEgg 6b57cf7981 Feature: add server config settings 2020-01-01 11:58:09 +08:00
Molunerfinn 9a8b95e356 🔨 Refactor: guiApi without creating with webContents 2020-01-01 00:47:47 +08:00
Molunerfinn 81e6acb29b 🐛 Fix: handle empty request-body 2020-01-01 00:06:48 +08:00
Molunerfinn c56d4efa79 Feature(server): add http server for uploading images by a http request
port 37766
2019-12-31 23:51:31 +08:00
PiEgg 3fd95725cf 🐛 Fix: settingPage && miniPage style in windows 2019-12-30 22:45:50 +08:00
Molunerfinn c6ead5b8cd 🐛 Fix: save debug mode && PICGO_ENV into config file 2019-12-30 11:06:00 +08:00
Molunerfinn f2750e1234 🐛 Fix: decrease title-bar z-index when config-form dialog shows 2019-12-28 16:50:29 +08:00
Molunerfinn 9d4d605acf Feature: only shows visible pic-beds
ISSUES CLOSED: #310
2019-12-28 16:23:16 +08:00
Molunerfinn cd30a6c70c 🐛 Fix: beforeOpen handler in windows 2019-12-28 15:19:27 +08:00
Molunerfinn a33f1adeb3 Feature: add aliyun oss options
ISSUES CLOSED: #347
2019-12-27 10:47:33 +08:00
Molunerfinn 1b3522e536 📝 Docs: update readme 2019-12-26 20:25:26 +08:00
Molunerfinn 3418560c63 🔨 Refactor: most config handle by picgo-core
for config syncing with picgo-core && picgo-plugins
2019-12-26 20:15:41 +08:00
Molunerfinn fec4360043 🚧 WIP: almost finish shortKey system 2019-12-22 17:22:32 +08:00
Molunerfinn b8ec879e23 🔨 Refactor: change vue-js -> vue-ts && add typos 2019-12-21 17:28:29 +08:00
Molunerfinn 29a55ed855 🐛 Fix: rename page not work 2019-12-21 00:50:13 +08:00
PiEgg 514fc40f71 🐛 Fix: plugin config-form && default plugin logo 2019-12-20 23:41:38 +08:00
Molunerfinn bc8e641fda 🐛 Fix: launch error in new structrue 2019-12-20 18:46:39 +08:00
Molunerfinn 4e3fa28be6 🐛 Fix: enum type error 2019-12-20 18:45:26 +08:00
Molunerfinn 87638a2882 ⬆️ Upgrade: ts & electron version 2019-12-20 10:56:57 +08:00
Molunerfinn 1d98601757 🔨 Refactor: remove .electron-vue folder 2019-12-19 19:19:04 +08:00
Molunerfinn 4e67a75be0 🔨 Refactor(ts): change js -> ts 2019-12-19 19:17:21 +08:00
PiEgg 72e6e2aed5 🚧 WIP: handle shortcut setting time 2019-09-25 16:34:29 +08:00
PiEgg b63ac03b84 🚧 WIP: finished shortKey page 2019-09-18 19:21:13 +08:00
PiEgg 554cc0dbf8 🔨 Refactor: db class 2019-09-11 19:30:08 +08:00
PiEgg 1274893154 🚧 WIP: shortcut setting page 2019-09-11 15:33:13 +08:00
Haoshen ZhongandPiEgg f491209bd6 📝 Docs: update README 2019-09-11 09:25:01 +08:00
PiEgg f21940ef23 🚧 WIP: shortcut system 2019-09-10 14:38:08 +08:00
Molunerfinn fe9e19a84d 📝 Docs: update site 2019-08-18 10:20:30 +08:00
Molunerfinn 91b397de9a 📝 Docs: update installation by scoop
ISSUES CLOSED: #295
2019-06-24 20:13:50 +08:00
Molunerfinn d06be462ff ⬆️ Upgrade(ws): for security issue 2019-06-05 10:47:37 +08:00
Molunerfinn 8fcbef5b0e ⬆️ Upgrade(axios): for security issue 2019-05-31 09:12:37 +08:00
Molunerfinn c5adf3b68c 🐛 Fix: add new tray icon for macOS dark-mode
ISSUES CLOSED: #267
2019-05-23 00:13:12 +08:00
Molunerfinn 5a064830c6 Feature: add alias for plugin config name 2019-05-12 19:48:11 +08:00
Molunerfinn e38e6f038c ⬆️ Upgrade(core): picgo-core -> 1.3.7
fix macOS clipboard upload error

ISSUES CLOSED: #264, #199
2019-05-12 13:26:34 +08:00
Molunerfinn c7ea9a8628 ⬆️ Upgrade: bump-version -> 1.0.3 2019-05-12 11:50:24 +08:00
Molunerfinn a5b6526fa4 🐛 Fix(website): website pictures error 2019-05-12 11:50:24 +08:00
Li YimingandMolunerfinn 46a49ed9d3 📝 Docs: add note for windows electron mirror 2019-05-12 11:49:58 +08:00
PiEgg 9344d7d762 ⬆️ Upgrade(electron): electron from 4 to 5 2019-05-05 22:22:03 +08:00
PiEgg e81b8f428c 📝 Docs: remove weibo picbed 2019-04-27 15:51:10 +08:00
PiEgg 6f1fca7f7f ⬆️ Upgrade: inquirer.js version for picgo bump-version 2019-04-20 11:59:47 +08:00
131 changed files with 10843 additions and 8879 deletions
-39
View File
@@ -1,39 +0,0 @@
{
"comments": false,
"env": {
"test": {
"presets": [
["env", {
"targets": { "node": 7 }
}],
"stage-0"
],
"plugins": ["istanbul"]
},
"main": {
"presets": [
["env", {
"targets": { "node": 7 }
}],
"stage-0"
]
},
"renderer": {
"presets": [
["env", {
"modules": false
}],
"stage-0"
]
},
"production": {
"presets": [
["env", {
"modules": false
}],
"stage-0"
]
}
},
"plugins": ["transform-runtime"]
}
-132
View File
@@ -1,132 +0,0 @@
'use strict'
process.env.NODE_ENV = 'production'
const { say } = require('cfonts')
const chalk = require('chalk')
const del = require('del')
const { spawn } = require('child_process')
const webpack = require('webpack')
const Multispinner = require('multispinner')
const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')
// const webConfig = require('./webpack.web.config')
const doneLog = chalk.bgGreen.white(' DONE ') + ' '
const errorLog = chalk.bgRed.white(' ERROR ') + ' '
const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
const isCI = process.env.CI || false
if (process.env.BUILD_TARGET === 'clean') clean()
else if (process.env.BUILD_TARGET === 'web') web()
else build()
function clean () {
del.sync(['build/*', '!build/icons', '!build/icons/icon.*'])
console.log(`\n${doneLog}\n`)
process.exit()
}
function build () {
greeting()
del.sync(['dist/electron/*', '!.gitkeep'])
const tasks = ['main', 'renderer']
const m = new Multispinner(tasks, {
preText: 'building',
postText: 'process'
})
let results = ''
m.on('success', () => {
process.stdout.write('\x1B[2J\x1B[0f')
console.log(`\n\n${results}`)
console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
process.exit()
})
pack(mainConfig).then(result => {
results += result + '\n\n'
m.success('main')
}).catch(err => {
m.error('main')
console.log(`\n ${errorLog}failed to build main process`)
console.error(`\n${err}\n`)
process.exit(1)
})
pack(rendererConfig).then(result => {
results += result + '\n\n'
m.success('renderer')
}).catch(err => {
m.error('renderer')
console.log(`\n ${errorLog}failed to build renderer process`)
console.error(`\n${err}\n`)
process.exit(1)
})
}
function pack (config) {
return new Promise((resolve, reject) => {
config.mode = 'production'
webpack(config, (err, stats) => {
if (err) reject(err.stack || err)
else if (stats.hasErrors()) {
let err = ''
stats.toString({
chunks: false,
colors: true
})
.split(/\r?\n/)
.forEach(line => {
err += ` ${line}\n`
})
reject(err)
} else {
resolve(stats.toString({
chunks: false,
colors: true
}))
}
})
})
}
// function web () {
// del.sync(['dist/web/*', '!.gitkeep'])
// webConfig.mode = 'production'
// webpack(webConfig, (err, stats) => {
// if (err || stats.hasErrors()) console.log(err)
// console.log(stats.toString({
// chunks: false,
// colors: true
// }))
// process.exit()
// })
// }
function greeting () {
const cols = process.stdout.columns
let text = ''
if (cols > 85) text = 'lets-build'
else if (cols > 60) text = 'lets-|build'
else text = false
if (text && !isCI) {
say(text, {
colors: ['yellow'],
font: 'simple3d',
space: false
})
} else console.log(chalk.yellow.bold('\n lets-build'))
console.log()
}
-40
View File
@@ -1,40 +0,0 @@
const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(event => {
/**
* Reload browser when HTMLWebpackPlugin emits a new index.html
*
* Currently disabled until jantimon/html-webpack-plugin#680 is resolved.
* https://github.com/SimulatedGREG/electron-vue/issues/437
* https://github.com/jantimon/html-webpack-plugin/issues/680
*/
// if (event.action === 'reload') {
// window.location.reload()
// }
/**
* Notify `mainWindow` when `main` process is compiling,
* giving notice for an expected reload of the `electron` process
*/
if (event.action === 'compiling') {
document.body.innerHTML += `
<style>
#dev-client {
background: #4fc08d;
border-radius: 4px;
bottom: 20px;
box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
color: #fff;
font-family: 'Source Sans Pro', sans-serif;
left: 20px;
padding: 8px 12px;
position: absolute;
}
</style>
<div id="dev-client">
Compiling Main Process...
</div>
`
}
})
-190
View File
@@ -1,190 +0,0 @@
'use strict'
const chalk = require('chalk')
const electron = require('electron')
const path = require('path')
const { say } = require('cfonts')
const { spawn } = require('child_process')
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const webpackHotMiddleware = require('webpack-hot-middleware')
const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')
let electronProcess = null
let manualRestart = false
let hotMiddleware
function logStats (proc, data) {
let log = ''
log += chalk.yellow.bold(`${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`)
log += '\n\n'
if (typeof data === 'object') {
data.toString({
colors: true,
chunks: false
}).split(/\r?\n/).forEach(line => {
log += ' ' + line + '\n'
})
} else {
log += ` ${data}\n`
}
log += '\n' + chalk.yellow.bold(`${new Array(28 + 1).join('-')}`) + '\n'
console.log(log)
}
function startRenderer () {
return new Promise((resolve, reject) => {
rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
rendererConfig.mode = 'development'
const compiler = webpack(rendererConfig)
hotMiddleware = webpackHotMiddleware(compiler, {
log: false,
heartbeat: 2500
})
compiler.hooks.compilation.tap('compilation', compilation => {
compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
compiler.hooks.done.tap('done', stats => {
logStats('Renderer', stats)
})
const server = new WebpackDevServer(
compiler,
{
contentBase: path.join(__dirname, '../'),
quiet: true,
before (app, ctx) {
app.use(hotMiddleware)
ctx.middleware.waitUntilValid(() => {
resolve()
})
}
}
)
server.listen(9080)
})
}
function startMain () {
return new Promise((resolve, reject) => {
mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
mainConfig.mode = 'development'
const compiler = webpack(mainConfig)
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
logStats('Main', chalk.white.bold('compiling...'))
hotMiddleware.publish({ action: 'compiling' })
done()
})
compiler.watch({}, (err, stats) => {
if (err) {
console.log(err)
return
}
logStats('Main', stats)
if (electronProcess && electronProcess.kill) {
manualRestart = true
process.kill(electronProcess.pid)
electronProcess = null
startElectron()
setTimeout(() => {
manualRestart = false
}, 5000)
}
resolve()
})
})
}
function startElectron () {
var args = [
'--inspect=5858',
path.join(__dirname, '../dist/electron/main.js')
]
// detect yarn or npm and process commandline args accordingly
if (process.env.npm_execpath.endsWith('yarn.js')) {
args = args.concat(process.argv.slice(3))
} else if (process.env.npm_execpath.endsWith('npm-cli.js')) {
args = args.concat(process.argv.slice(2))
}
electronProcess = spawn(electron, args)
electronProcess.stdout.on('data', data => {
electronLog(data, 'blue')
})
electronProcess.stderr.on('data', data => {
electronLog(data, 'red')
})
electronProcess.on('close', () => {
if (!manualRestart) process.exit()
})
}
function electronLog (data, color) {
let log = ''
data = data.toString().split(/\r?\n/)
data.forEach(line => {
log += ` ${line}\n`
})
if (/[0-9A-z]+/.test(log)) {
console.log(
chalk[color].bold('┏ Electron -------------------') +
'\n\n' +
log +
chalk[color].bold('┗ ----------------------------') +
'\n'
)
}
}
function greeting () {
const cols = process.stdout.columns
let text = ''
if (cols > 104) text = 'electron-vue'
else if (cols > 76) text = 'electron-|vue'
else text = false
if (text) {
say(text, {
colors: ['yellow'],
font: 'simple3d',
space: false
})
} else console.log(chalk.yellow.bold('\n electron-vue'))
console.log(chalk.blue(' getting ready...') + '\n')
}
function init () {
greeting()
Promise.all([startRenderer(), startMain()])
.then(() => {
startElectron()
})
.catch(err => {
console.error(err)
})
}
init()
-120
View File
@@ -1,120 +0,0 @@
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const webpack = require('webpack')
module.exports = {
mode: 'development',
context: path.resolve(__dirname, '../docs'),
entry: './main.js',
output: {
path: path.resolve(__dirname, '../docs/dist'),
filename: "index.js"
},
resolve: {
alias: {
'icons': path.resolve(__dirname, '../build/icons')
}
},
module: {
rules: [
{
test: /\.(js|vue)$/,
enforce: 'pre',
exclude: /node_modules/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-friendly-formatter')
}
}
},
{
test: /\.styl(us)?$/,
use: [
'vue-style-loader',
'css-loader',
'stylus-loader'
]
},
{
test: /\.pug$/, loader: "pug-plain-loader"
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.html$/,
use: 'vue-html-loader'
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.node$/,
use: 'node-loader'
},
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
}
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'imgs/[name]--[folder].[ext]'
}
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'media/[name]--[folder].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'fonts/[name]--[folder].[ext]'
}
}
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../docs/template.html')
}),
new MiniCssExtractPlugin({filename: 'styles.css'}),
]
}
if (process.env.NODE_ENV === 'production') {
// module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.mode = 'production'
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
-83
View File
@@ -1,83 +0,0 @@
'use strict'
process.env.BABEL_ENV = 'main'
const path = require('path')
const { dependencies } = require('../package.json')
const webpack = require('webpack')
const BabiliWebpackPlugin = require('babili-webpack-plugin')
let mainConfig = {
entry: {
main: path.join(__dirname, '../src/main/index.js')
},
externals: [
...Object.keys(dependencies || {})
],
module: {
rules: [
{
test: /\.(js)$/,
enforce: 'pre',
exclude: /node_modules/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-friendly-formatter')
}
}
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.node$/,
use: 'node-loader'
}
]
},
node: {
__dirname: process.env.NODE_ENV !== 'production',
__filename: process.env.NODE_ENV !== 'production'
},
output: {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../dist/electron')
},
plugins: [
new webpack.NoEmitOnErrorsPlugin()
],
resolve: {
extensions: ['.js', '.json', '.node']
},
target: 'electron-main'
}
/**
* Adjust mainConfig for development settings
*/
if (process.env.NODE_ENV !== 'production') {
mainConfig.plugins.push(
new webpack.DefinePlugin({
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
})
)
}
/**
* Adjust mainConfig for production settings
*/
if (process.env.NODE_ENV === 'production') {
mainConfig.plugins.push(
new BabiliWebpackPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
})
)
}
module.exports = mainConfig
-194
View File
@@ -1,194 +0,0 @@
'use strict'
process.env.BABEL_ENV = 'renderer'
const path = require('path')
const { dependencies } = require('../package.json')
const webpack = require('webpack')
const BabiliWebpackPlugin = require('babili-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
/**
* List of node_modules to include in webpack bundle
*
* Required for specific packages like Vue UI libraries
* that provide pure *.vue files that need compiling
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
*/
let whiteListedModules = ['vue']
let rendererConfig = {
devtool: '#cheap-module-eval-source-map',
entry: {
renderer: path.join(__dirname, '../src/renderer/main.js')
},
externals: [
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))
],
module: {
rules: [
{
test: /\.(js|vue)$/,
enforce: 'pre',
exclude: /node_modules/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-friendly-formatter')
}
}
},
{
test: /\.styl(us)?$/,
use: [
'vue-style-loader',
'css-loader',
'stylus-loader'
]
},
{
test: /\.less$/,
use: ['vue-style-loader', 'css-loader', 'less-loader']
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.html$/,
use: 'vue-html-loader'
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.node$/,
use: 'node-loader'
},
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
extractCSS: process.env.NODE_ENV === 'production',
loaders: {
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
scss: 'vue-style-loader!css-loader!sass-loader',
less: 'vue-style-loader!css-loader!less-loader',
stylus: 'vue-style-loader!css-loader!stylus-loader'
}
}
}
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'imgs/[name]--[folder].[ext]'
}
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'media/[name]--[folder].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'fonts/[name]--[folder].[ext]'
}
}
}
]
},
node: {
__dirname: process.env.NODE_ENV !== 'production',
__filename: process.env.NODE_ENV !== 'production'
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({filename: 'styles.css'}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true
},
nodeModules: process.env.NODE_ENV !== 'production'
? path.resolve(__dirname, '../node_modules')
: false
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
],
output: {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../dist/electron')
},
resolve: {
alias: {
'@': path.join(__dirname, '../src/renderer'),
'vue$': 'vue/dist/vue.esm.js',
'utils': path.join(__dirname, '../src/renderer/utils'),
'~': path.join(__dirname, '../src'),
'root': path.join(__dirname, '../')
},
extensions: ['.js', '.vue', '.json', '.css', '.node']
},
target: 'electron-renderer'
}
/**
* Adjust rendererConfig for development settings
*/
if (process.env.NODE_ENV !== 'production') {
rendererConfig.plugins.push(
new webpack.DefinePlugin({
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
})
)
}
/**
* Adjust rendererConfig for production settings
*/
if (process.env.NODE_ENV === 'production') {
rendererConfig.devtool = ''
rendererConfig.plugins.push(
new BabiliWebpackPlugin(),
new CopyWebpackPlugin([
{
from: path.join(__dirname, '../static'),
to: path.join(__dirname, '../dist/electron/static'),
ignore: ['.*']
}
]),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
)
}
module.exports = rendererConfig
-139
View File
@@ -1,139 +0,0 @@
'use strict'
process.env.BABEL_ENV = 'web'
const path = require('path')
const webpack = require('webpack')
const BabiliWebpackPlugin = require('babili-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
let webConfig = {
devtool: '#cheap-module-eval-source-map',
entry: {
web: path.join(__dirname, '../src/renderer/main.js')
},
module: {
rules: [
{
test: /\.(js|vue)$/,
enforce: 'pre',
exclude: /node_modules/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-friendly-formatter')
}
}
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
},
{
test: /\.html$/,
use: 'vue-html-loader'
},
{
test: /\.js$/,
use: 'babel-loader',
include: [ path.resolve(__dirname, '../src/renderer') ],
exclude: /node_modules/
},
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
extractCSS: true,
loaders: {
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
scss: 'vue-style-loader!css-loader!sass-loader'
}
}
}
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'imgs/[name].[ext]'
}
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'fonts/[name].[ext]'
}
}
}
]
},
plugins: [
new ExtractTextPlugin('styles.css'),
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true
},
nodeModules: false
}),
new webpack.DefinePlugin({
'process.env.IS_WEB': 'true'
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
],
output: {
filename: '[name].js',
path: path.join(__dirname, '../dist/web')
},
resolve: {
alias: {
'@': path.join(__dirname, '../src/renderer'),
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['.js', '.vue', '.json', '.css']
},
target: 'web'
}
/**
* Adjust webConfig for production settings
*/
if (process.env.NODE_ENV === 'production') {
webConfig.devtool = ''
webConfig.plugins.push(
new BabiliWebpackPlugin(),
new CopyWebpackPlugin([
{
from: path.join(__dirname, '../static'),
to: path.join(__dirname, '../dist/web/static'),
ignore: ['.*']
}
]),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
)
}
module.exports = webConfig
+14 -17
View File
@@ -1,26 +1,23 @@
module.exports = { module.exports = {
root: true, root: true,
parser: 'babel-eslint', globals: {
parserOptions: { __static: 'readonly'
sourceType: 'module'
}, },
env: { env: {
browser: true,
node: true node: true
}, },
extends: 'standard', parser: "vue-eslint-parser",
globals: { 'extends': [
__static: true 'plugin:vue/essential',
}, '@vue/standard',
plugins: [ '@vue/typescript'
'html'
], ],
'rules': { 'plugins': ['@typescript-eslint'],
// allow paren-less arrow functions rules: {
'arrow-parens': 0, 'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
// allow async-await 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
'generator-star-spacing': 0, },
// allow debugger during development parserOptions: {
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 parser: '@typescript-eslint/parser'
} }
} }
+4
View File
@@ -12,3 +12,7 @@ thumbs.db
!.gitkeep !.gitkeep
yarn-error.log yarn-error.log
docs/dist/ docs/dist/
# local env files
.env.local
.env.*.local
dist_electron/
+9 -9
View File
@@ -38,15 +38,15 @@ script:
#- xvfb-maybe node_modules/.bin/karma start test/unit/karma.conf.js #- xvfb-maybe node_modules/.bin/karma start test/unit/karma.conf.js
#- yarn run pack && xvfb-maybe node_modules/.bin/mocha test/e2e #- yarn run pack && xvfb-maybe node_modules/.bin/mocha test/e2e
- npm run release - npm run release
- yarn run build:docs # - yarn run build:docs
branches: branches:
only: only:
- master - master
after_script: # after_script:
- cd docs/dist # - cd docs/dist
- git init # - git init
- git config user.name "Molunerfinn" # - git config user.name "Molunerfinn"
- git config user.email "marksz@teamsz.xyz" # - git config user.email "marksz@teamsz.xyz"
- git add . # - git add .
- git commit -m "Travis build docs" # - git commit -m "Travis build docs"
- git push --force --quiet "https://${GH_TOKEN}@github.com/Molunerfinn/PicGo.git" master:gh-pages # - git push --force --quiet "https://${GH_TOKEN}@github.com/Molunerfinn/PicGo.git" master:gh-pages
+22 -1
View File
@@ -1,4 +1,25 @@
{ {
"eslint.enable": true, "eslint.enable": true,
"eslint.autoFixOnSave": true "eslint.alwaysShowStatus": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"vue",
"typescriptreact"
],
"[stylus]": {
"editor.formatOnSave": true
},
"stylusSupremacy.insertSemicolons": false,
"stylusSupremacy.insertBraces": false,
"stylusSupremacy.insertNewLineBetweenSelectors": true,
"stylusSupremacy.insertParenthesisAroundIfCondition": false,
"stylusSupremacy.alwaysUseNoneOverZero": true,
"stylusSupremacy.alwaysUseZeroWithoutUnit": true,
"stylusSupremacy.sortProperties": "grouped",
"stylusSupremacy.quoteChar": "\"",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
} }
+93
View File
@@ -1,3 +1,96 @@
## :tada: 2.2.2 (2020-01-20)
### :bug: Bug Fixes
* picgo-server upload clipboard file's result bug ([4ebd76f](https://github.com/Molunerfinn/PicGo/commit/4ebd76f))
* releaseUrl may can't get latest version ([ee46ab1](https://github.com/Molunerfinn/PicGo/commit/ee46ab1))
## :tada: 2.2.1 (2020-01-09)
### :sparkles: Features
* add alias for plugin config name ([5a06483](https://github.com/Molunerfinn/PicGo/commit/5a06483))
* add aliyun oss options ([a33f1ad](https://github.com/Molunerfinn/PicGo/commit/a33f1ad)), closes [#347](https://github.com/Molunerfinn/PicGo/issues/347)
* **server:** add http server for uploading images by a http request ([c56d4ef](https://github.com/Molunerfinn/PicGo/commit/c56d4ef))
* add server config settings ([6b57cf7](https://github.com/Molunerfinn/PicGo/commit/6b57cf7))
* only shows visible pic-beds ([9d4d605](https://github.com/Molunerfinn/PicGo/commit/9d4d605)), closes [#310](https://github.com/Molunerfinn/PicGo/issues/310)
### :bug: Bug Fixes
* decrease title-bar z-index when config-form dialog shows ([f2750e1](https://github.com/Molunerfinn/PicGo/commit/f2750e1))
* **website:** website pictures error ([a5b6526](https://github.com/Molunerfinn/PicGo/commit/a5b6526))
* add new tray icon for macOS dark-mode ([c5adf3b](https://github.com/Molunerfinn/PicGo/commit/c5adf3b)), closes [#267](https://github.com/Molunerfinn/PicGo/issues/267)
* beforeOpen handler in windows ([cd30a6c](https://github.com/Molunerfinn/PicGo/commit/cd30a6c))
* busApi event register first && emit later ([e1a0cbb](https://github.com/Molunerfinn/PicGo/commit/e1a0cbb))
* enum type error ([4e3fa28](https://github.com/Molunerfinn/PicGo/commit/4e3fa28))
* handle empty request-body ([81e6acb](https://github.com/Molunerfinn/PicGo/commit/81e6acb))
* launch error in new structrue ([bc8e641](https://github.com/Molunerfinn/PicGo/commit/bc8e641))
* miniWindow minimize bug ([5f2b7c7](https://github.com/Molunerfinn/PicGo/commit/5f2b7c7))
* plugin config-form && default plugin logo ([514fc40](https://github.com/Molunerfinn/PicGo/commit/514fc40))
* release script ([b4f10c6](https://github.com/Molunerfinn/PicGo/commit/b4f10c6))
* removeById handler error ([c4f0a30](https://github.com/Molunerfinn/PicGo/commit/c4f0a30)), closes [#382](https://github.com/Molunerfinn/PicGo/issues/382)
* rename page not work ([29a55ed](https://github.com/Molunerfinn/PicGo/commit/29a55ed))
* save debug mode && PICGO_ENV into config file ([c6ead5b](https://github.com/Molunerfinn/PicGo/commit/c6ead5b))
* server may never start ([73870a5](https://github.com/Molunerfinn/PicGo/commit/73870a5))
* settingPage && miniPage style in windows ([3fd9572](https://github.com/Molunerfinn/PicGo/commit/3fd9572))
### :pencil: Documentation
* add note for windows electron mirror ([46a49ed](https://github.com/Molunerfinn/PicGo/commit/46a49ed))
* remove weibo picbed ([e81b8f4](https://github.com/Molunerfinn/PicGo/commit/e81b8f4))
* update installation by scoop ([91b397d](https://github.com/Molunerfinn/PicGo/commit/91b397d)), closes [#295](https://github.com/Molunerfinn/PicGo/issues/295)
* update readme ([1b3522e](https://github.com/Molunerfinn/PicGo/commit/1b3522e))
* update README ([f491209](https://github.com/Molunerfinn/PicGo/commit/f491209))
* update site ([fe9e19a](https://github.com/Molunerfinn/PicGo/commit/fe9e19a))
# :tada: 2.2.0 (2020-01-01)
### :sparkles: Features
* add alias for plugin config name ([5a06483](https://github.com/Molunerfinn/PicGo/commit/5a06483))
* add aliyun oss options ([a33f1ad](https://github.com/Molunerfinn/PicGo/commit/a33f1ad)), closes [#347](https://github.com/Molunerfinn/PicGo/issues/347)
* **server:** add http server for uploading images by a http request ([c56d4ef](https://github.com/Molunerfinn/PicGo/commit/c56d4ef))
* add server config settings ([6b57cf7](https://github.com/Molunerfinn/PicGo/commit/6b57cf7))
* only shows visible pic-beds ([9d4d605](https://github.com/Molunerfinn/PicGo/commit/9d4d605)), closes [#310](https://github.com/Molunerfinn/PicGo/issues/310)
### :bug: Bug Fixes
* beforeOpen handler in windows ([cd30a6c](https://github.com/Molunerfinn/PicGo/commit/cd30a6c))
* **website:** website pictures error ([a5b6526](https://github.com/Molunerfinn/PicGo/commit/a5b6526))
* add new tray icon for macOS dark-mode ([c5adf3b](https://github.com/Molunerfinn/PicGo/commit/c5adf3b)), closes [#267](https://github.com/Molunerfinn/PicGo/issues/267)
* busApi event register first && emit later ([e1a0cbb](https://github.com/Molunerfinn/PicGo/commit/e1a0cbb))
* decrease title-bar z-index when config-form dialog shows ([f2750e1](https://github.com/Molunerfinn/PicGo/commit/f2750e1))
* enum type error ([4e3fa28](https://github.com/Molunerfinn/PicGo/commit/4e3fa28))
* handle empty request-body ([81e6acb](https://github.com/Molunerfinn/PicGo/commit/81e6acb))
* launch error in new structrue ([bc8e641](https://github.com/Molunerfinn/PicGo/commit/bc8e641))
* plugin config-form && default plugin logo ([514fc40](https://github.com/Molunerfinn/PicGo/commit/514fc40))
* release script ([b4f10c6](https://github.com/Molunerfinn/PicGo/commit/b4f10c6))
* rename page not work ([29a55ed](https://github.com/Molunerfinn/PicGo/commit/29a55ed))
* save debug mode && PICGO_ENV into config file ([c6ead5b](https://github.com/Molunerfinn/PicGo/commit/c6ead5b))
* settingPage && miniPage style in windows ([3fd9572](https://github.com/Molunerfinn/PicGo/commit/3fd9572))
### :pencil: Documentation
* add note for windows electron mirror ([46a49ed](https://github.com/Molunerfinn/PicGo/commit/46a49ed))
* remove weibo picbed ([e81b8f4](https://github.com/Molunerfinn/PicGo/commit/e81b8f4))
* update installation by scoop ([91b397d](https://github.com/Molunerfinn/PicGo/commit/91b397d)), closes [#295](https://github.com/Molunerfinn/PicGo/issues/295)
* update readme ([1b3522e](https://github.com/Molunerfinn/PicGo/commit/1b3522e))
* update README ([f491209](https://github.com/Molunerfinn/PicGo/commit/f491209))
* update site ([fe9e19a](https://github.com/Molunerfinn/PicGo/commit/fe9e19a))
## :tada: 2.1.2 (2019-04-19) ## :tada: 2.1.2 (2019-04-19)
+18 -10
View File
@@ -1,13 +1,21 @@
The 996ICU License (996ICU) The MIT License (MIT)
Version 0.1, March 2019
PACKAGE is distributed under LICENSE with the following restriction: Copyright (c) 2017-present, Molunerfinn
The above license is only granted to entities that act in concordance Permission is hereby granted, free of charge, to any person obtaining a copy
with local labor laws. In addition, the following requirements must be of this software and associated documentation files (the "Software"), to deal
observed: in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
* The licencee must not, explicitly or implicitly, request or schedule The above copyright notice and this permission notice shall be included in
their employees to work more than 45 hours in any single week. all copies or substantial portions of the Software.
* The licencee must not, explicitly or implicitly, request or schedule
their employees to be at work consecutively for 10 hours. 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.
+34 -35
View File
@@ -1,11 +1,7 @@
# PicGo <div align="center">
> 图片上传+管理新体验
<p align="center">
<img src="https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/New%20LOGO-150.png" alt=""> <img src="https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/New%20LOGO-150.png" alt="">
</p> <h1>PicGo</h1>
<p align="center"> <blockquote>图片上传+管理新体验 </blockquote>
<a href="https://github.com/feross/standard"> <a href="https://github.com/feross/standard">
<img src="https://img.shields.io/badge/code%20style-standard-green.svg?style=flat-square" alt=""> <img src="https://img.shields.io/badge/code%20style-standard-green.svg?style=flat-square" alt="">
</a> </a>
@@ -21,45 +17,47 @@
<a href="https://github.com/PicGo/bump-version"> <a href="https://github.com/PicGo/bump-version">
<img src="https://img.shields.io/badge/picgo-convention-blue.svg?style=flat-square" alt=""> <img src="https://img.shields.io/badge/picgo-convention-blue.svg?style=flat-square" alt="">
</a> </a>
</p> </div>
## 应用说明 ## 应用说明
**PicGo在上传图片之后自动会将图片链接复制到你的剪贴板里,可选5种复制的链接格式。** **PicGo 在上传图片之后自动会将图片链接复制到你的剪贴板里,可选 5 种复制的链接格式。**
PicGo目前支持了 PicGo 目前支持了
- `微博图床` v1.0 - ~~`微博图床` v1.0~~ **微博图床从 2019 年 4 月开始进行防盗链,不建议继续使用**
- `七牛图床` v1.0 - `七牛图床` v1.0
- `腾讯云COS v4\v5版本` v1.1 & v1.5.0 - `腾讯云 COS v4\v5版本` v1.1 & v1.5.0
- `又拍云` v1.2.0 - `又拍云` v1.2.0
- `GitHub` v1.5.0 - `GitHub` v1.5.0
- `SM.MS` v1.5.1 - `SM.MS` v1.5.1
- `阿里云OSS` v1.6.0 - `阿里云 OSS` v1.6.0
- `Imgur` v1.6.0 - `Imgur` v1.6.0
**本体不再增加默认的图床支持。你可以自行开发第三方图床插件。详见[PicGo-Core](https://picgo.github.io/PicGo-Core-Doc/)** **本体不再增加默认的图床支持。你可以自行开发第三方图床插件。详见 [PicGo-Core](https://picgo.github.io/PicGo-Core-Doc/)**
第三方插件以及使用了PicGo底层的应用可以在[Awesome-PicGo](https://github.com/PicGo/Awesome-PicGo)到。欢迎贡献! 第三方插件以及使用了 PicGo 底层的应用可以在 [Awesome-PicGo](https://github.com/PicGo/Awesome-PicGo)到。欢迎贡献!
支持macOS、windows 64位(>= v1.3.1),linux>= v1.6.0)。 PicGo 支持 macOS、Windows 64位(>= v1.3.1),Linux>= v1.6.0)。
支持快捷键`command+shift+p`macOS)或者`control+shift+p`windows\linux)用以支持快捷上传剪贴板里的图片(第一张)。 支持快捷键`command+shift+p`macOS)或者`control+shift+p`Windows\Linux)用以支持快捷上传剪贴板里的图片(第一张)。
PicGo支持自定义快捷键,使用方法见[配置手册](https://picgo.github.io/PicGo-Doc/zh/guide/config.html)。 PicGo 支持自定义快捷键,使用方法见[配置手册](https://picgo.github.io/PicGo-Doc/zh/guide/config.html)。
开发进度可以查看[Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度。 开发进度可以查看 [Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度。
**如果第一次使用,请参考应用使用[快速上手](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html)。遇到问题了还可以看看[FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md)以及被关闭的[issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed)。** **如果第一次使用,请参考应用使用[快速上手](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html)。遇到问题了还可以看看 [FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md) 以及被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed)。**
## 下载安装 ## 下载安装
点击此处下载[应用](https://github.com/Molunerfinn/PicGo/releases)。 点击此处下载[应用](https://github.com/Molunerfinn/PicGo/releases)。
macOS用户请下载最新版本的`dmg`文件,windows用户请下载最新版本的`exe`文件,linux用户请下载`AppImage`文件。 macOS 用户请下载最新版本的 `dmg` 文件,Windows 用户请下载最新版本的 `exe` 文件,Linux用户请下载 `AppImage` 文件。
**如果你是Arch类的Linux用户,可以直接通过`aurman -S picgo-appimage`来安装PicGo。感谢 @houbaron 的贡献!** **如果你是 Arch 类的 Linux 用户,可以直接通过 `aurman -S picgo-appimage` 来安装 PicGo。感谢 @houbaron 的贡献!**
**如果你是macOS用户,可以使用brew cask来安装PicGo: `brew cask install picgo`。感谢 @womeimingzi11 的贡献!** **如果你是 macOS 用户,可以使用 `brew cask` 来安装 PicGo: `brew cask install picgo`。感谢 @womeimingzi11 的贡献!**
**如果你是 Windows 用户,还可以使用 [Scoop](https://scoop.sh/) 来安装 PicGo: `scoop bucket add helbing https://github.com/helbing/scoop-bucket` & `scoop install picgo`。 感谢 @helbing 的贡献!**
## 应用截图 ## 应用截图
@@ -69,20 +67,20 @@ macOS用户请下载最新版本的`dmg`文件,windows用户请下载最新版
## 开发说明 ## 开发说明
> 目前仅针对Mac、Windows。Linux平台并未测试。 > 目前仅针对 Mac、Windows。Linux 平台并未测试。
如果你想要学习、开发、修改或自行构建PicGo,可以依照下面的指示: 如果你想要学习、开发、修改或自行构建 PicGo,可以依照下面的指示:
> 如果想学习Electron-vue的开发,可以查看我写的系列教程——[Electron-vue开发实战](https://molunerfinn.com/tags/Electron-vue/) > 如果想学习 Electron-vue 的开发,可以查看我写的系列教程——[Electron-vue 开发实战](https://molunerfinn.com/tags/Electron-vue/)
1. 你需要有node、git环境。需要了解npm的相关知识。 1. 你需要有 Node、Git环境了解 npm 的相关知识。
2. `git clone https://github.com/Molunerfinn/PicGo.git` 并进入项目 2. `git clone https://github.com/Molunerfinn/PicGo.git` 并进入项目
3. `npm install` 下载依赖 3. `npm install` 下载依赖
4. Mac需要有Xcode环境,Windows需要有VS环境。 4. Mac 需要有 Xcode 环境,Windows 需要有 VS 环境。
### 开发模式 ### 开发模式
输入`npm run dev`进入开发模式,开发模式具有热重载特性。不过需要注意的是,开发模式不稳定,会有进程崩溃的情况。此时需要: 输入 `npm run electron:serve` 进入开发模式,开发模式具有热重载特性。不过需要注意的是,开发模式不稳定,会有进程崩溃的情况。此时需要:
```bash ```bash
ctrl+c # 退出开发模式 ctrl+c # 退出开发模式
@@ -91,24 +89,25 @@ npm run dev # 重新进入开发模式
### 生产模式 ### 生产模式
如果你需要自行构建,可以`npm run build`开始进行构建。构建成功后,会在`build`目录里出现构建成功的相应安装文件。 如果你需要自行构建,可以 `npm run electron:build` 开始进行构建。构建成功后,会在 `dist_electron` 目录里出现构建成功的相应安装文件。
**注意**:如果你的网络环境不太好,可能会出现`electron-builder`下载`electron`二进制文件失败的情况。这个时候需要在`npm run build`之前指定一下`electron`的源为国内源: **注意**:如果你的网络环境不太好,可能会出现 `electron-builder` 下载 `electron` 二进制文件失败的情况。这个时候需要在 `npm run build` 之前指定一下 `electron` 的源为国内源:
```bash ```bash
export ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/" export ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/"
# 在 Windows 上,则可以使用 set ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/ (无需引号)
npm run build 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版。 - [vs-picgo](https://github.com/Spades-S/vs-picgo)PicGo 的 VS Code 版。
## 赞助 ## 赞助
如果你喜欢PicGo并且它对你确实有帮助,欢迎给我打赏一杯咖啡哈~ 如果你喜欢 PicGo 并且它对你确实有帮助,欢迎给我打赏一杯咖啡哈~
支付宝: 支付宝:
@@ -122,4 +121,4 @@ npm run build
[MIT](http://opensource.org/licenses/MIT) [MIT](http://opensource.org/licenses/MIT)
Copyright (c) 2017 - 2019 Molunerfinn Copyright (c) 2017 - 2019 Molunerfinn
+2 -3
View File
@@ -11,7 +11,6 @@ platform:
- x64 - x64
cache: cache:
- node_modules
- '%APPDATA%\npm-cache' - '%APPDATA%\npm-cache'
- '%USERPROFILE%\.electron' - '%USERPROFILE%\.electron'
- '%USERPROFILE%\AppData\Local\Yarn\cache' - '%USERPROFILE%\AppData\Local\Yarn\cache'
@@ -20,9 +19,9 @@ init:
- git config --global core.autocrlf input - git config --global core.autocrlf input
install: install:
- ps: Install-Product node 8 x64 - ps: Install-Product node 12 x64
- git reset --hard HEAD - git reset --hard HEAD
- npm install - yarn
- node --version - node --version
build_script: build_script:
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
View File
+9 -9
View File
@@ -13,10 +13,10 @@
h3.desc h3.desc
| 支持macOS,Windows,Linux | 支持macOS,Windows,Linux
h3.desc h3.desc
| 支持插件系统让PicGo更强大 | 支持#[a(href="https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E6%8F%92%E4%BB%B6%E8%AE%BE%E7%BD%AE%EF%BC%88v2-0%EF%BC%89" target="_blank") 插件系统]让PicGo更强大
#container.container-fluid #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://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/first.png")
.row.ex-width.display-list .row.ex-width.display-list
.display-list__item(v-for="(item, index) in itemList" :key="index" :class="{ 'o-item': index % 2 !== 0 }") .display-list__item(v-for="(item, index) in itemList" :key="index" :class="{ 'o-item': index % 2 !== 0 }")
.col-xs-10.col-xs-offset-1.col-md-7.col-md-offset-0 .col-xs-10.col-xs-offset-1.col-md-7.col-md-offset-0
@@ -37,32 +37,32 @@ export default {
year: new Date().getFullYear(), year: new Date().getFullYear(),
itemList: [ itemList: [
{ {
url: 'https://ws1.sinaimg.cn/large/8700af19ly1fma907llb5j20m30ed46a', url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/second.png',
title: '精致设计', title: '精致设计',
desc: 'macOS系统下,支持拖拽至menubar图标实现上传。menubar app 窗口显示最新上传的5张图片以及剪贴板里的图片。点击图片自动将上传的链接复制到剪贴板。(Windows平台不支持)' desc: 'macOS系统下,支持拖拽至menubar图标实现上传。menubar app 窗口显示最新上传的5张图片以及剪贴板里的图片。点击图片自动将上传的链接复制到剪贴板。(Windows平台不支持)'
}, },
{ {
url: 'https://i.loli.net/2018/07/11/5b45768fb1276.png', url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/third.png',
title: 'Mini小窗', title: 'Mini小窗',
desc: 'Windows以及Linux系统下提供一个mini悬浮窗用于用户拖拽上传,节约你宝贵的桌面空间。' desc: 'Windows以及Linux系统下提供一个mini悬浮窗用于用户拖拽上传,节约你宝贵的桌面空间。'
}, },
{ {
url: 'https://ws1.sinaimg.cn/large/8700af19ly1fmd56zm2nej218g0p0teb', url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/forth.png',
title: '便捷管理', title: '便捷管理',
desc: '查看你的上传记录,重复使用更方便。支持点击图片大图查看。支持删除图片(仅本地记录),让界面更加干净。' desc: '查看你的上传记录,重复使用更方便。支持点击图片大图查看。支持删除图片(仅本地记录),让界面更加干净。'
}, },
{ {
url: 'https://ws1.sinaimg.cn/large/8700af19ly1fmd5ck9m0wj20lr0cxmzs', url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/fifth.png',
title: '可选图床', title: '可选图床',
desc: '默认支持微博图床、七牛图床、腾讯云COS、又拍云、GitHub、SM.MS、阿里云OSS、Imgur。方便不同图床的上传需求。2.0版本开始更可以自己开发插件实现其他图床的上传需求。' desc: '默认支持微博图床、七牛图床、腾讯云COS、又拍云、GitHub、SM.MS、阿里云OSS、Imgur。方便不同图床的上传需求。2.0版本开始更可以自己开发插件实现其他图床的上传需求。'
}, },
{ {
url: 'https://ws1.sinaimg.cn/large/8700af19gy1fmayjwttnbj218g0p0q4e', url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/sixth.png',
title: '多样链接', title: '多样链接',
desc: '支持5种默认剪贴板链接格式,包括一种自定义格式,让你的文本编辑游刃有余。' desc: '支持5种默认剪贴板链接格式,包括一种自定义格式,让你的文本编辑游刃有余。'
}, },
{ {
url: 'https://i.loli.net/2019/01/12/5c39a2f60a32a.png', url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/seventh.png',
title: '插件系统', title: '插件系统',
desc: '2.0版本开始支持插件系统,让PicGo发挥无限潜能,成为一个极致的效率工具。' desc: '2.0版本开始支持插件系统,让PicGo发挥无限潜能,成为一个极致的效率工具。'
} }
@@ -108,7 +108,7 @@ h1
#header #header
height 100vh height 100vh
width 100% width 100%
background-image url("https://ws1.sinaimg.cn/large/8700af19ly1fm9ru6fqvjj22p81stdta") background-image url("https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/bg.jpeg")
background-attachment fixed background-attachment fixed
background-size cover background-size cover
background-position center background-position center
+51 -153
View File
@@ -1,35 +1,20 @@
{ {
"name": "picgo", "name": "picgo",
"version": "2.1.2", "version": "2.2.2",
"author": "Molunerfinn <marksz@teamsz.xyz>", "private": true,
"description": "Easy to upload your pic & copy to write",
"license": "MIT",
"main": "./dist/electron/main.js",
"scripts": { "scripts": {
"render": "webpack-dev-server --hot --colors --config .electron-vue/webpack.renderer.config.js --port 9080 --content-base app/dist", "serve": "vue-cli-service serve",
"build": "node .electron-vue/build.js && electron-builder", "build": "vue-cli-service build",
"release": "node .electron-vue/build.js && electron-builder", "lint": "vue-cli-service lint",
"build:dir": "node .electron-vue/build.js && electron-builder --dir", "electron:build": "vue-cli-service electron:build",
"build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js", "electron:serve": "vue-cli-service electron:serve",
"build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js", "postinstall": "electron-builder install-app-deps",
"dev": "node .electron-vue/dev-runner.js", "postuninstall": "electron-builder install-app-deps",
"e2e": "npm run pack && mocha test/e2e",
"lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src test",
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src test",
"pack": "npm run pack:main && npm run pack:renderer",
"pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js",
"pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js",
"test": "npm run unit && npm run e2e",
"unit": "karma start test/unit/karma.conf.js",
"postinstall": "npm run lint:fix",
"build:docs": "cross-env NODE_ENV=production webpack -p --config .electron-vue/webpack.docs.config.js",
"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",
"minor": "npm version minor && git push origin master && git push origin --tags",
"major": "npm version major && git push origin master && git push origin --tags",
"cz": "git-cz", "cz": "git-cz",
"bump": "bump-version" "bump": "bump-version",
"release": "vue-cli-service electron:build --publish always"
}, },
"main": "background.js",
"husky": { "husky": {
"hooks": { "hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS" "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
@@ -48,138 +33,51 @@
"./node_modules/@picgo/bump-version/commitlint-picgo" "./node_modules/@picgo/bump-version/commitlint-picgo"
] ]
}, },
"build": {
"productName": "PicGo",
"appId": "com.molunerfinn.picgo",
"directories": {
"output": "build"
},
"files": [
"dist/electron/**/*"
],
"dmg": {
"contents": [
{
"x": 410,
"y": 150,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 150,
"type": "file"
}
]
},
"mac": {
"icon": "build/icons/icon.icns",
"extendInfo": {
"LSUIElement": 1
}
},
"win": {
"icon": "build/icons/icon.ico",
"target": "nsis"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
},
"linux": {
"icon": "build/icons/"
}
},
"dependencies": { "dependencies": {
"axios": "^0.16.1", "axios": "^0.19.0",
"dateformat": "^3.0.3", "core-js": "^3.3.2",
"element-ui": "^2.4.11", "element-ui": "^2.13.0",
"fix-path": "^2.1.0", "fix-path": "^2.1.0",
"fs-extra": "^4.0.2", "fs-extra": "^8.1.0",
"image-size": "^0.6.1", "keycode": "^2.2.0",
"keycode": "^2.1.9",
"lodash-id": "^0.14.0", "lodash-id": "^0.14.0",
"lowdb": "^1.0.0", "lowdb": "^1.0.0",
"md5": "^2.2.1", "picgo": "^1.4.4",
"melody.css": "^1.0.2", "vue": "^2.6.10",
"picgo": "^1.3.5", "vue-gallery": "^2.0.1",
"qiniu": "^7.1.1",
"vue": "^2.3.3",
"vue-electron": "^1.0.6",
"vue-gallery": "^1.2.4",
"vue-lazyload": "^1.2.6", "vue-lazyload": "^1.2.6",
"vue-router": "^2.5.3", "vue-router": "^3.1.3"
"vuex": "^2.3.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^7.5.2", "@commitlint/cli": "^8.2.0",
"@picgo/bump-version": "^1.0.2", "@picgo/bump-version": "^1.0.3",
"babel-core": "^6.25.0", "@types/fs-extra": "^8.0.1",
"babel-eslint": "^7.2.3", "@types/inquirer": "^6.5.0",
"babel-loader": "^7.1.1", "@types/lowdb": "^1.0.9",
"babel-plugin-istanbul": "^4.1.1", "@types/node": "10.17.6",
"babel-plugin-transform-runtime": "^6.23.0", "@types/request-promise-native": "^1.0.17",
"babel-preset-env": "^1.6.0", "@vue/cli-plugin-babel": "^4.0.0",
"babel-preset-stage-0": "^6.24.1", "@vue/cli-plugin-eslint": "^4.0.0",
"babel-register": "^6.24.1", "@vue/cli-plugin-router": "^4.0.0",
"babili-webpack-plugin": "^0.1.2", "@vue/cli-plugin-typescript": "^4.0.0",
"cfonts": "^1.1.3", "@vue/cli-service": "^4.0.0",
"chai": "^4.0.0", "@vue/eslint-config-standard": "^4.0.0",
"chalk": "^2.1.0", "@vue/eslint-config-typescript": "^4.0.0",
"commitizen": "^3.0.7", "commitizen": "^4.0.3",
"conventional-changelog": "^3.0.6", "conventional-changelog": "^3.1.18",
"copy-webpack-plugin": "^4.0.1", "cz-customizable": "^6.2.0",
"cross-env": "^5.0.5", "electron": "^6.0.0",
"css-loader": "^0.28.4", "eslint": "^5.16.0",
"cz-customizable": "^5.10.0", "eslint-plugin-vue": "^5.0.0",
"del": "^3.0.0", "husky": "^3.1.0",
"devtron": "^1.4.0", "stylus": "^0.54.7",
"electron": "4.0.2", "stylus-loader": "^3.0.2",
"electron-builder": "^20.38.4", "typescript": "~3.7.3",
"electron-debug": "^1.4.0", "vue-cli-plugin-electron-builder": "^1.4.2",
"electron-devtools-installer": "^2.2.0", "vue-property-decorator": "^8.3.0",
"eslint": "^4.4.1", "vue-template-compiler": "^2.6.10"
"eslint-config-standard": "^10.2.1", },
"eslint-friendly-formatter": "^3.0.0", "resolutions": {
"eslint-loader": "^2.1.1", "@types/node": "12.0.2"
"eslint-plugin-html": "^3.1.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.1.1",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1",
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"husky": "^1.3.1",
"inject-loader": "^3.0.0",
"karma": "^1.3.0",
"karma-chai": "^0.1.0",
"karma-coverage": "^1.1.1",
"karma-electron": "^5.1.1",
"karma-mocha": "^1.2.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "^0.0.31",
"karma-webpack": "^2.0.1",
"mini-css-extract-plugin": "0.4.0",
"mocha": "^3.0.2",
"multispinner": "^0.2.1",
"node-loader": "^0.6.0",
"pug": "^2.0.0-rc.4",
"pug-loader": "^2.3.0",
"pug-plain-loader": "^1.0.0",
"require-dir": "^0.3.0",
"spectron": "^3.7.1",
"style-loader": "^0.23.1",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.1",
"url-loader": "^1.1.2",
"vue-html-loader": "^1.2.4",
"vue-loader": "^15.4.2",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.4.2",
"webpack": "^4.15.1",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.4",
"webpack-hot-middleware": "^2.22.2",
"webpack-merge": "^4.1.3"
} }
} }
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+18
View File
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="referrer" content="never">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>PicGo</title>
</head>
<body>
<noscript>
<strong>We're sorry but picgo-new doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
View File
View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 497 B

After

Width:  |  Height:  |  Size: 497 B

Before

Width:  |  Height:  |  Size: 915 B

After

Width:  |  Height:  |  Size: 915 B

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

+26
View File
@@ -0,0 +1,26 @@
param($imagePath)
# Adapted from https://github.com/octan3/img-clipboard-dump/blob/master/dump-clipboard-png.ps1
Add-Type -Assembly PresentationCore
$img = [Windows.Clipboard]::GetImage()
if ($img -eq $null) {
"no image"
Exit 1
}
if (-not $imagePath) {
"no image"
Exit 1
}
$fcb = new-object Windows.Media.Imaging.FormatConvertedBitmap($img, [Windows.Media.PixelFormats]::Rgb24, $null, 0)
$stream = [IO.File]::Open($imagePath, "OpenOrCreate")
$encoder = New-Object Windows.Media.Imaging.PngBitmapEncoder
$encoder.Frames.Add([Windows.Media.Imaging.BitmapFrame]::Create($fcb)) | out-null
$encoder.Save($stream) | out-null
$stream.Dispose() | out-null
$imagePath
+175
View File
@@ -0,0 +1,175 @@
import {
app,
globalShortcut,
protocol
} from 'electron'
import {
createProtocol,
installVueDevtools
} from 'vue-cli-plugin-electron-builder/lib'
import db from '#/datastore'
import { IWindowList } from '~/main/apis/window/constants'
import windowManager from '~/main/apis/window/windowManager'
import {
uploadChoosedFiles,
uploadClipboardFiles
} from '~/main/apis/uploader/api'
import beforeOpen from '~/main/utils/beforeOpen'
import updateChecker from '~/main/utils/updateChecker'
import ipcList from '~/main/apis/eventCenter/ipcList'
import busEventList from '~/main/apis/eventCenter/busEventList'
import fixPath from 'fix-path'
import { getUploadFiles } from '~/main/utils/handleArgv'
import bus from '~/main/utils/eventBus'
import {
updateShortKeyFromVersion212
} from '~/main/migrate'
import shortKeyHandler from '~/main/apis/shortKey/shortKeyHandler'
import server from '~/main/server/index'
import {
createTray
} from '~/main/apis/app'
const isDevelopment = process.env.NODE_ENV !== 'production'
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
beforeOpen()
// fix the $PATH in macOS
fixPath()
ipcList.listen()
busEventList.listen()
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
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 {
const win = windowManager.getAvailableWindow()
uploadChoosedFiles(win.webContents, files)
}
} else {
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
if (settingWindow.isMinimized()) {
settingWindow.restore()
}
settingWindow.focus()
}
}
})
}
if (process.platform === 'win32') {
app.setAppUserModelId('com.molunerfinn.picgo')
}
if (process.env.XDG_CURRENT_DESKTOP && process.env.XDG_CURRENT_DESKTOP.includes('Unity')) {
process.env.XDG_CURRENT_DESKTOP = 'Unity'
}
app.on('ready', async () => {
createProtocol('picgo')
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installVueDevtools()
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
windowManager.create(IWindowList.TRAY_WINDOW)
windowManager.create(IWindowList.SETTING_WINDOW)
if (process.platform === 'darwin' || process.platform === 'win32') {
createTray()
}
db.set('needReload', false)
updateChecker()
// 不需要阻塞
process.nextTick(() => {
updateShortKeyFromVersion212(db, db.get('settings.shortKey'))
shortKeyHandler.init()
})
server.startup()
if (process.env.NODE_ENV !== 'development') {
let files = getUploadFiles()
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动
if (files === null) {
uploadClipboardFiles()
} else {
const win = windowManager.getAvailableWindow()
uploadChoosedFiles(win.webContents, files)
}
}
}
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
createProtocol('picgo')
if (!windowManager.has(IWindowList.TRAY_WINDOW)) {
windowManager.create(IWindowList.TRAY_WINDOW)
}
if (!windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.create(IWindowList.SETTING_WINDOW)
}
})
app.on('will-quit', () => {
globalShortcut.unregisterAll()
bus.removeAllListeners()
server.shutdown()
})
app.setLoginItemSettings({
openAtLogin: db.get('settings.autoStart') || false
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', data => {
if (data === 'graceful-exit') {
app.quit()
server.shutdown()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
server.shutdown()
})
}
}
/**
* Auto Updater
*
* Uncomment the following code below and install `electron-updater` to
* support auto updating. Code Signing with a valid certificate is required.
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
*/
// import { autoUpdater } from 'electron-updater'
// autoUpdater.on('update-downloaded', () => {
// autoUpdater.quitAndInstall()
// })
// app.on('ready', () => {
// if (process.env.NODE_ENV === 'production') {
// autoUpdater.checkForUpdates()
// }
// })
-81
View File
@@ -1,81 +0,0 @@
import Datastore from 'lowdb'
import LodashId from 'lodash-id'
import FileSync from 'lowdb/adapters/FileSync'
import path from 'path'
import fs from 'fs-extra'
import { remote, app } from 'electron'
if (process.env.NODE_ENV !== 'development') {
global.__static = path.join(__dirname, '/static').replace(/\\/g, '\\\\')
}
if (process.env.DEBUG_ENV === 'debug') {
global.__static = path.join(__dirname, '../../static').replace(/\\/g, '\\\\')
}
const APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData')
if (process.type !== 'renderer') {
if (!fs.pathExistsSync(STORE_PATH)) {
fs.mkdirpSync(STORE_PATH)
}
}
const adapter = new FileSync(path.join(STORE_PATH, '/data.json'))
const db = Datastore(adapter)
db._.mixin(LodashId)
if (!db.has('uploaded').value()) {
db.set('uploaded', []).write()
}
if (!db.has('picBed').value()) {
db.set('picBed', {
current: 'weibo'
}).write()
}
if (!db.has('settings.shortKey').value()) {
db.set('settings.shortKey', {
upload: 'CommandOrControl+Shift+P'
}).write()
}
// init generate clipboard image files
let clipboardFiles = getClipboardFiles()
if (!fs.pathExistsSync(path.join(STORE_PATH, 'windows.ps1'))) {
clipboardFiles.forEach(item => {
fs.copyFileSync(item.origin, item.dest)
})
} else {
clipboardFiles.forEach(item => {
diffFilesAndUpdate(item.origin, item.dest)
})
}
function diffFilesAndUpdate (filePath1, filePath2) {
let file1 = fs.readFileSync(filePath1)
let file2 = fs.readFileSync(filePath2)
if (!file1.equals(file2)) {
fs.copyFileSync(filePath1, filePath2)
}
}
function getClipboardFiles () {
let files = [
'/linux.sh',
'/mac.applescript',
'/windows.ps1'
]
return files.map(item => {
return {
origin: path.join(__static, item),
dest: path.join(STORE_PATH, item)
}
})
}
export default db
-56
View File
@@ -1,56 +0,0 @@
import db from './index'
let picBed = [
{
type: 'weibo',
name: '微博图床',
visible: true
},
{
type: 'qiniu',
name: '七牛图床',
visible: true
},
{
type: 'tcyun',
name: '腾讯云COS',
visible: true
},
{
type: 'upyun',
name: '又拍云图床',
visible: true
},
{
type: 'github',
name: 'GitHub图床',
visible: true
},
{
type: 'smms',
name: 'SM.MS图床',
visible: true
},
{
type: 'aliyun',
name: '阿里云OSS',
visible: true
},
{
type: 'imgur',
name: 'Imgur图床',
visible: true
}
]
let picBedFromDB = db.read().get('picBed.list').value() || []
let oldLength = picBedFromDB.length
let newLength = picBed.length
if (oldLength !== newLength) {
for (let i = oldLength; i < newLength; i++) {
picBedFromDB.push(picBed[i])
}
}
export default picBedFromDB
-24
View File
@@ -1,24 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="referrer" content="never">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>PicGo</title>
<% if (htmlWebpackPlugin.options.nodeModules) { %>
<!-- Add `node_modules/` to global paths so `require` works properly in development -->
<script>
require('module').globalPaths.push("<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>")
</script>
<% } %>
</head>
<body>
<div id="app"></div>
<!-- Set `__static` path to static files in production -->
<script>
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
</script>
<!-- webpack builds are automatically injected -->
</body>
</html>
+14 -18
View File
@@ -1,24 +1,18 @@
import Vue from 'vue' import Vue from 'vue'
import axios from 'axios' import App from './renderer/App.vue'
import router from './renderer/router'
import db from '#/datastore/index'
import ElementUI from 'element-ui' import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App'
import router from './router'
import store from './store'
import db from '../datastore/index'
import { webFrame } from 'electron' import { webFrame } from 'electron'
import './assets/fonts/iconfont.css' import 'element-ui/lib/theme-chalk/index.css'
import VueLazyLoad from 'vue-lazyload' import VueLazyLoad from 'vue-lazyload'
import axios from 'axios'
Vue.use(ElementUI) import mainMixin from './renderer/utils/mainMixin'
Vue.use(VueLazyLoad)
webFrame.setVisualZoomLevelLimits(1, 1) webFrame.setVisualZoomLevelLimits(1, 1)
webFrame.setLayoutZoomLevelLimits(0, 0) webFrame.setLayoutZoomLevelLimits(0, 0)
if (!process.env.IS_WEB) Vue.use(require('vue-electron')) Vue.config.productionTip = false
Vue.http = Vue.prototype.$http = axios
Vue.prototype.$db = db
Vue.prototype.$builtInPicBed = [ Vue.prototype.$builtInPicBed = [
'smms', 'smms',
'weibo', 'weibo',
@@ -29,12 +23,14 @@ Vue.prototype.$builtInPicBed = [
'aliyun', 'aliyun',
'github' 'github'
] ]
Vue.config.productionTip = false Vue.prototype.$db = db
Vue.prototype.$http = axios
Vue.use(ElementUI)
Vue.use(VueLazyLoad)
Vue.mixin(mainMixin)
/* eslint-disable no-new */
new Vue({ new Vue({
components: { App },
router, router,
store, render: h => h(App)
template: '<App/>'
}).$mount('#app') }).$mount('#app')
+206
View File
@@ -0,0 +1,206 @@
import {
app,
Menu,
Tray,
dialog,
clipboard,
systemPreferences,
Notification
} from 'electron'
import uploader from '~/main/apis/uploader'
import getPicBeds from '~/main/utils/getPicBeds'
import db from '#/datastore'
import windowManager from '~/main/apis/window/windowManager'
import { IWindowList } from '~/main/apis/window/constants'
import picgo from '~/main/apis/picgo'
import pasteTemplate from '#/utils/pasteTemplate'
import pkg from 'root/package.json'
let contextMenu: Menu | null
let menu: Menu | null
let tray: Tray | null
export function createContextMenu () {
const picBeds = getPicBeds()
const submenu = picBeds.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: db.get('picBed.current') === item.type,
click () {
picgo.saveConfig({
'picBed.current': item.type
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
}
}
})
contextMenu = Menu.buildFromTemplate([
{
label: '关于',
click () {
dialog.showMessageBox({
title: 'PicGo',
message: 'PicGo',
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
})
}
},
{
label: '打开详细窗口',
click () {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
settingWindow!.show()
settingWindow!.focus()
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
},
{
label: '选择默认图床',
type: 'submenu',
// @ts-ignore
submenu
},
// @ts-ignore
{
label: '打开更新助手',
type: 'checkbox',
checked: db.get('settings.showUpdateTip'),
click () {
const value = db.get('settings.showUpdateTip')
db.set('settings.showUpdateTip', !value)
}
},
{
label: '重启应用',
click () {
app.relaunch()
app.exit(0)
}
},
// @ts-ignore
{
role: 'quit',
label: '退出'
}
])
}
export function createTray () {
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
tray = new Tray(menubarPic)
tray.on('right-click', () => {
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
}
createContextMenu()
tray!.popUpContextMenu(contextMenu!)
})
tray.on('click', (event, bounds) => {
if (process.platform === 'darwin') {
toggleWindow(bounds)
setTimeout(() => {
let img = clipboard.readImage()
let obj: ImgInfo[] = []
if (!img.isEmpty()) {
// 从剪贴板来的图片默认转为png
// @ts-ignore
const imgUrl = 'data:image/png;base64,' + Buffer.from(img.toPNG(), 'binary').toString('base64')
obj.push({
width: img.getSize().width,
height: img.getSize().height,
imgUrl
})
}
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('clipboardFiles', obj)
}, 0)
} else {
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
}
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
settingWindow!.show()
settingWindow!.focus()
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
})
tray.on('drag-enter', () => {
if (systemPreferences.isDarkMode()) {
tray!.setImage(`${__static}/upload-dark.png`)
} else {
tray!.setImage(`${__static}/upload.png`)
}
})
tray.on('drag-end', () => {
tray!.setImage(`${__static}/menubar.png`)
})
tray.on('drop-files', async (event: Event, files: string[]) => {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
const imgs = await uploader
.setWebContents(trayWindow.webContents)
.upload(files)
if (imgs !== false) {
for (let i = 0; i < imgs.length; i++) {
clipboard.writeText(pasteTemplate(pasteStyle, imgs[i]))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl!,
icon: files[i]
})
setTimeout(() => {
notification.show()
}, i * 100)
db.insert('uploaded', imgs[i])
}
trayWindow.webContents.send('dragFiles', imgs)
}
})
// toggleWindow()
}
export function createMenu () {
if (process.env.NODE_ENV !== 'development') {
const template = [{
label: 'Edit',
submenu: [
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
{ label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
{ type: 'separator' },
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' },
{
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click () {
app.quit()
}
}
]
}]
// @ts-ignore
menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
}
const toggleWindow = (bounds: IBounds) => {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
if (trayWindow.isVisible()) {
trayWindow.hide()
} else {
trayWindow.setPosition(bounds.x - 98 + 11, bounds.y, false)
trayWindow.webContents.send('updateFiles')
trayWindow.show()
trayWindow.focus()
}
}
+9
View File
@@ -0,0 +1,9 @@
export const GET_WINDOW_ID = 'GET_WINDOW_ID' // get a current window
export const GET_WINDOW_ID_REPONSE = 'GET_WINDOW_ID_REPONSE'
export const GET_SETTING_WINDOW_ID = 'GET_SETTING_WINDOW_ID' // get setting window
export const GET_SETTING_WINDOW_ID_RESPONSE = 'GET_SETTING_WINDOW_ID_RESPONSE'
export const UPLOAD_WITH_FILES = 'UPLOAD_WITH_FILES'
export const UPLOAD_WITH_FILES_RESPONSE = 'UPLOAD_WITH_FILES_RESPONSE'
export const UPLOAD_WITH_CLIPBOARD_FILES = 'UPLOAD_WITH_CLIPBOARD_FILES'
export const UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE = 'UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE'
export const CREATE_APP_MENU = 'CREATE_APP_MENU'
+74
View File
@@ -0,0 +1,74 @@
import bus from '../../utils/eventBus'
import {
UPLOAD_WITH_FILES,
UPLOAD_WITH_FILES_RESPONSE,
UPLOAD_WITH_CLIPBOARD_FILES,
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
GET_WINDOW_ID,
GET_WINDOW_ID_REPONSE,
GET_SETTING_WINDOW_ID,
GET_SETTING_WINDOW_ID_RESPONSE
} from './constants'
export const uploadWithClipboardFiles = (): Promise<{
success: boolean,
result?: string[]
}> => {
return new Promise((resolve, reject) => {
bus.once(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, (result: string) => {
if (result) {
return resolve({
success: true,
result: [result]
})
} else {
return resolve({
success: false
})
}
})
bus.emit(UPLOAD_WITH_CLIPBOARD_FILES)
})
}
export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
success: boolean,
result?: string[]
}> => {
return new Promise((resolve, reject) => {
bus.once(UPLOAD_WITH_FILES_RESPONSE, (result: string[]) => {
if (result.length) {
return resolve({
success: true,
result
})
} else {
return resolve({
success: false
})
}
})
bus.emit(UPLOAD_WITH_FILES, pathList)
})
}
// get available window id:
// miniWindow or settingWindow or trayWindow
export const getWindowId = (): Promise<number> => {
return new Promise((resolve, reject) => {
bus.once(GET_WINDOW_ID_REPONSE, (id: number) => {
resolve(id)
})
bus.emit(GET_WINDOW_ID)
})
}
// get settingWindow id:
export const getSettingWindowId = (): Promise<number> => {
return new Promise((resolve, reject) => {
bus.once(GET_SETTING_WINDOW_ID_RESPONSE, (id: number) => {
resolve(id)
})
bus.emit(GET_SETTING_WINDOW_ID)
})
}
+61
View File
@@ -0,0 +1,61 @@
import bus from '~/main/utils/eventBus'
import {
uploadClipboardFiles,
uploadChoosedFiles
} from '~/main/apis/uploader/api'
import {
createMenu
} from '~/main/apis/app'
import { IWindowList } from '~/main/apis/window/constants'
import windowManager from '~/main/apis/window/windowManager'
import {
UPLOAD_WITH_FILES,
UPLOAD_WITH_FILES_RESPONSE,
UPLOAD_WITH_CLIPBOARD_FILES,
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
GET_WINDOW_ID,
GET_WINDOW_ID_REPONSE,
GET_SETTING_WINDOW_ID,
GET_SETTING_WINDOW_ID_RESPONSE,
CREATE_APP_MENU
} from '~/main/apis/bus/constants'
function initEventCenter () {
const eventList: any = {
'picgo:upload': uploadClipboardFiles,
[UPLOAD_WITH_CLIPBOARD_FILES]: busCallUploadClipboardFiles,
[UPLOAD_WITH_FILES]: busCallUploadFiles,
[GET_WINDOW_ID]: busCallGetWindowId,
[GET_SETTING_WINDOW_ID]: busCallGetSettingWindowId,
[CREATE_APP_MENU]: createMenu
}
for (let i in eventList) {
bus.on(i, eventList[i])
}
}
async function busCallUploadClipboardFiles () {
const imgUrl = await uploadClipboardFiles()
bus.emit(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, imgUrl)
}
async function busCallUploadFiles (pathList: IFileWithPath[]) {
const win = windowManager.getAvailableWindow()
const urls = await uploadChoosedFiles(win.webContents, pathList)
bus.emit(UPLOAD_WITH_FILES_RESPONSE, urls)
}
function busCallGetWindowId () {
const win = windowManager.getAvailableWindow()
bus.emit(GET_WINDOW_ID_REPONSE, win.id)
}
function busCallGetSettingWindowId () {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
bus.emit(GET_SETTING_WINDOW_ID_RESPONSE, settingWindow.id)
}
export default {
listen () {
initEventCenter()
}
}
+143
View File
@@ -0,0 +1,143 @@
import {
app,
ipcMain,
clipboard,
Notification,
IpcMainEvent
} from 'electron'
import windowManager from '~/main/apis/window/windowManager'
import { IWindowList } from '~/main/apis/window/constants'
import uploader from '~/main/apis/uploader'
import pasteTemplate from '#/utils/pasteTemplate'
import db from '#/datastore'
import server from '~/main/server'
import getPicBeds from '~/main/utils/getPicBeds'
import shortKeyHandler from '~/main/apis/shortKey/shortKeyHandler'
import bus from '~/main/utils/eventBus'
import {
uploadClipboardFiles,
uploadChoosedFiles
} from '~/main/apis/uploader/api'
import picgoCoreIPC from './picgoCoreIPC'
export default {
listen () {
picgoCoreIPC.listen()
// from macOS tray
ipcMain.on('uploadClipboardFiles', async () => {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
const img = await uploader.setWebContents(trayWindow.webContents).upload()
if (img !== false) {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
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.insert('uploaded', img[0])
trayWindow.webContents.send('clipboardFiles', [])
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
}
}
trayWindow.webContents.send('uploadFiles')
})
ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
uploadClipboardFiles()
})
ipcMain.on('uploadChoosedFiles', async (evt: IpcMainEvent, files: IFileWithPath[]) => {
return uploadChoosedFiles(evt.sender, files)
})
ipcMain.on('updateShortKey', (evt: IpcMainEvent, item: IShortKeyConfig, oldKey: string, from: string) => {
const result = shortKeyHandler.updateShortKey(item, oldKey, from)
evt.sender.send('updateShortKeyResponse', result)
if (result) {
const notification = new Notification({
title: '操作成功',
body: '你的快捷键已经修改成功'
})
notification.show()
} else {
const notification = new Notification({
title: '操作失败',
body: '快捷键冲突,请重新设置'
})
notification.show()
}
})
ipcMain.on('bindOrUnbindShortKey', (evt: IpcMainEvent, item: IShortKeyConfig, from: string) => {
const result = shortKeyHandler.bindOrUnbindShortKey(item, from)
if (result) {
const notification = new Notification({
title: '操作成功',
body: '你的快捷键已经修改成功'
})
notification.show()
} else {
const notification = new Notification({
title: '操作失败',
body: '快捷键冲突,请重新设置'
})
notification.show()
}
})
ipcMain.on('updateCustomLink', () => {
const notification = new Notification({
title: '操作成功',
body: '你的自定义链接格式已经修改成功'
})
notification.show()
})
ipcMain.on('autoStart', (evt: IpcMainEvent, val: boolean) => {
app.setLoginItemSettings({
openAtLogin: val
})
})
ipcMain.on('openSettingWindow', () => {
windowManager.get(IWindowList.SETTING_WINDOW)!.show()
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
})
ipcMain.on('openMiniWindow', () => {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
miniWindow.show()
miniWindow.focus()
settingWindow.hide()
})
// from mini window
ipcMain.on('syncPicBed', () => {
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
})
ipcMain.on('getPicBeds', (evt: IpcMainEvent) => {
const picBeds = getPicBeds()
evt.sender.send('getPicBeds', picBeds)
evt.returnValue = picBeds
})
ipcMain.on('toggleShortKeyModifiedMode', (evt: IpcMainEvent, val: boolean) => {
bus.emit('toggleShortKeyModifiedMode', val)
})
ipcMain.on('updateServer', () => {
server.restart()
})
},
dispose () {}
}
@@ -1,15 +1,36 @@
import path from 'path' import path from 'path'
import GuiApi from './guiApi' import GuiApi from '../gui'
import { dialog, shell } from 'electron' import {
dialog,
shell,
IpcMainEvent,
ipcMain,
app
} from 'electron'
import PicGoCore from '~/universal/types/picgo'
import { IPicGoHelperType } from '#/types/enum'
import shortKeyHandler from '../shortKey/shortKeyHandler'
import picgo from '~/main/apis/picgo'
// eslint-disable-next-line // eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo') // const PluginHandler = requireFunc('picgo/dist/lib/PluginHandler').default
const PluginHandler = requireFunc('picgo/dist/lib/PluginHandler').default const STORE_PATH = app.getPath('userData')
// const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
type PicGoNotice = {
title: string,
body: string[]
}
interface GuiMenuItem {
label: string
handle: (arg0: PicGoCore, arg1: GuiApi) => Promise<void>
}
// get uploader or transformer config // get uploader or transformer config
const getConfig = (name, type, ctx) => { const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => {
let config = [] let config: any[] = []
if (name === '') { if (name === '') {
return config return config
} else { } else {
@@ -23,7 +44,7 @@ const getConfig = (name, type, ctx) => {
} }
} }
const handleConfigWithFunction = config => { const handleConfigWithFunction = (config: any[]) => {
for (let i in config) { for (let i in config) {
if (typeof config[i].default === 'function') { if (typeof config[i].default === 'function') {
config[i].default = config[i].default() config[i].default = config[i].default()
@@ -35,9 +56,8 @@ const handleConfigWithFunction = config => {
return config return config
} }
const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => { const handleGetPluginList = () => {
ipcMain.on('getPluginList', event => { ipcMain.on('getPluginList', (event: IpcMainEvent) => {
const picgo = new PicGo(CONFIG_PATH)
const pluginList = picgo.pluginLoader.getList() const pluginList = picgo.pluginLoader.getList()
const list = [] const list = []
for (let i in pluginList) { for (let i in pluginList) {
@@ -56,7 +76,7 @@ const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => {
gui = true gui = true
} }
} }
const obj = { const obj: IPicGoPlugin = {
name: pluginList[i].replace(/picgo-plugin-/, ''), name: pluginList[i].replace(/picgo-plugin-/, ''),
author: pluginPKG.author.name || pluginPKG.author, author: pluginPKG.author.name || pluginPKG.author,
description: pluginPKG.description, description: pluginPKG.description,
@@ -70,11 +90,11 @@ const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => {
}, },
uploader: { uploader: {
name: uploaderName, name: uploaderName,
config: handleConfigWithFunction(getConfig(uploaderName, 'uploader', picgo)) config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.uploader, picgo))
}, },
transformer: { transformer: {
name: transformerName, name: transformerName,
config: handleConfigWithFunction(getConfig(uploaderName, 'transformer', picgo)) config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.transformer, picgo))
} }
}, },
enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`), enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`),
@@ -89,48 +109,49 @@ const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => {
}) })
} }
const handlePluginInstall = (ipcMain, CONFIG_PATH) => { const handlePluginInstall = () => {
ipcMain.on('installPlugin', async (event, msg) => { ipcMain.on('installPlugin', async (event: IpcMainEvent, msg: string) => {
const picgo = new PicGo(CONFIG_PATH) picgo.once('installSuccess', (notice: PicGoNotice) => {
const pluginHandler = new PluginHandler(picgo)
picgo.on('installSuccess', notice => {
event.sender.send('installSuccess', notice.body[0].replace(/picgo-plugin-/, '')) event.sender.send('installSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
shortKeyHandler.registerPluginShortKey(notice.body[0])
picgo.removeAllListeners('installFailed')
}) })
picgo.on('failed', () => { picgo.once('installFailed', () => {
handleNPMError() handleNPMError()
picgo.removeAllListeners('installSuccess')
}) })
await pluginHandler.uninstall([msg]) await picgo.pluginHandler.install([msg])
pluginHandler.install([msg])
picgo.cmd.program.removeAllListeners() picgo.cmd.program.removeAllListeners()
}) })
} }
const handlePluginUninstall = (ipcMain, CONFIG_PATH) => { const handlePluginUninstall = () => {
ipcMain.on('uninstallPlugin', async (event, msg) => { ipcMain.on('uninstallPlugin', async (event: IpcMainEvent, msg: string) => {
const picgo = new PicGo(CONFIG_PATH) picgo.once('uninstallSuccess', (notice: PicGoNotice) => {
const pluginHandler = new PluginHandler(picgo)
picgo.on('uninstallSuccess', notice => {
event.sender.send('uninstallSuccess', notice.body[0].replace(/picgo-plugin-/, '')) event.sender.send('uninstallSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
shortKeyHandler.unregisterPluginShortKey(notice.body[0])
picgo.removeAllListeners('uninstallFailed')
}) })
picgo.on('failed', () => { picgo.once('uninstallFailed', () => {
handleNPMError() handleNPMError()
picgo.removeAllListeners('uninstallSuccess')
}) })
await pluginHandler.uninstall([msg]) await picgo.pluginHandler.uninstall([msg])
picgo.cmd.program.removeAllListeners() picgo.cmd.program.removeAllListeners()
}) })
} }
const handlePluginUpdate = (ipcMain, CONFIG_PATH) => { const handlePluginUpdate = () => {
ipcMain.on('updatePlugin', async (event, msg) => { ipcMain.on('updatePlugin', async (event: IpcMainEvent, msg: string) => {
const picgo = new PicGo(CONFIG_PATH) picgo.once('updateSuccess', (notice: { body: string[], title: string }) => {
const pluginHandler = new PluginHandler(picgo)
picgo.on('updateSuccess', notice => {
event.sender.send('updateSuccess', notice.body[0].replace(/picgo-plugin-/, '')) event.sender.send('updateSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
picgo.removeAllListeners('updateFailed')
}) })
picgo.on('failed', () => { picgo.once('updateFailed', () => {
handleNPMError() handleNPMError()
picgo.removeAllListeners('updateSuccess')
}) })
await pluginHandler.update([msg]) await picgo.pluginHandler.update([msg])
picgo.cmd.program.removeAllListeners() picgo.cmd.program.removeAllListeners()
}) })
} }
@@ -140,16 +161,15 @@ const handleNPMError = () => {
title: '发生错误', title: '发生错误',
message: '请安装Node.js并重启PicGo再继续操作', message: '请安装Node.js并重启PicGo再继续操作',
buttons: ['Yes'] buttons: ['Yes']
}, (res) => { }).then((res) => {
if (res === 0) { if (res.response === 0) {
shell.openExternal('https://nodejs.org/') shell.openExternal('https://nodejs.org/')
} }
}) })
} }
const handleGetPicBedConfig = (ipcMain, CONFIG_PATH) => { const handleGetPicBedConfig = () => {
ipcMain.on('getPicBedConfig', (event, type) => { ipcMain.on('getPicBedConfig', (event: IpcMainEvent, type: string) => {
const picgo = new PicGo(CONFIG_PATH)
const name = picgo.helper.uploader.get(type).name || type const name = picgo.helper.uploader.get(type).name || type
if (picgo.helper.uploader.get(type).config) { if (picgo.helper.uploader.get(type).config) {
const config = handleConfigWithFunction(picgo.helper.uploader.get(type).config(picgo)) const config = handleConfigWithFunction(picgo.helper.uploader.get(type).config(picgo))
@@ -161,13 +181,12 @@ const handleGetPicBedConfig = (ipcMain, CONFIG_PATH) => {
}) })
} }
const handlePluginActions = (ipcMain, CONFIG_PATH) => { const handlePluginActions = () => {
ipcMain.on('pluginActions', (event, name, label) => { ipcMain.on('pluginActions', (event: IpcMainEvent, name: string, label: string) => {
const picgo = new PicGo(CONFIG_PATH)
const plugin = picgo.pluginLoader.getPlugin(`picgo-plugin-${name}`) const plugin = picgo.pluginLoader.getPlugin(`picgo-plugin-${name}`)
const guiApi = new GuiApi(ipcMain, event.sender, picgo) const guiApi = new GuiApi()
if (plugin.guiMenu && plugin.guiMenu(picgo).length > 0) { if (plugin.guiMenu && plugin.guiMenu(picgo).length > 0) {
const menu = plugin.guiMenu(picgo) const menu: GuiMenuItem[] = plugin.guiMenu(picgo)
menu.forEach(item => { menu.forEach(item => {
if (item.label === label) { if (item.label === label) {
item.handle(picgo, guiApi) item.handle(picgo, guiApi)
@@ -177,24 +196,30 @@ const handlePluginActions = (ipcMain, CONFIG_PATH) => {
}) })
} }
const handleRemoveFiles = (ipcMain, CONFIG_PATH) => { const handleRemoveFiles = () => {
ipcMain.on('removeFiles', (event, files) => { ipcMain.on('removeFiles', (event: IpcMainEvent, files: ImgInfo[]) => {
const picgo = new PicGo(CONFIG_PATH) const guiApi = new GuiApi()
const guiApi = new GuiApi(ipcMain, event.sender, picgo)
setTimeout(() => { setTimeout(() => {
picgo.emit('remove', files, guiApi) picgo.emit('remove', files, guiApi)
}, 500) }, 500)
}) })
} }
export default (app, ipcMain) => { const handlePicGoSaveData = () => {
const STORE_PATH = app.getPath('userData') ipcMain.on('picgoSaveData', (event: IpcMainEvent, data: IObj) => {
const CONFIG_PATH = path.join(STORE_PATH, '/data.json') picgo.saveConfig(data)
handleGetPluginList(ipcMain, STORE_PATH, CONFIG_PATH) })
handlePluginInstall(ipcMain, CONFIG_PATH) }
handlePluginUninstall(ipcMain, CONFIG_PATH)
handlePluginUpdate(ipcMain, CONFIG_PATH) export default {
handleGetPicBedConfig(ipcMain, CONFIG_PATH) listen () {
handlePluginActions(ipcMain, CONFIG_PATH) handleGetPluginList()
handleRemoveFiles(ipcMain, CONFIG_PATH) handlePluginInstall()
handlePluginUninstall()
handlePluginUpdate()
handleGetPicBedConfig()
handlePluginActions()
handleRemoveFiles()
handlePicGoSaveData()
}
} }
+119
View File
@@ -0,0 +1,119 @@
import {
dialog,
BrowserWindow,
clipboard,
Notification,
ipcMain
} from 'electron'
import db from '#/datastore'
import uploader from '../uploader'
import pasteTemplate from '#/utils/pasteTemplate'
import {
getWindowId,
getSettingWindowId
} from '~/main/apis/bus'
class GuiApi implements IGuiApi {
private windowId: number = -1
private settingWindowId: number = -1
private async showSettingWindow () {
this.settingWindowId = await getSettingWindowId()
const settingWindow = BrowserWindow.fromId(this.settingWindowId)
if (settingWindow.isVisible()) {
return true
}
settingWindow.show()
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000) // TODO: a better way to wait page loaded.
})
}
private getWebcontentsByWindowId (id: number) {
return BrowserWindow.fromId(id).webContents
}
async showInputBox (options: IShowInputBoxOption = {
title: '',
placeholder: ''
}) {
await this.showSettingWindow()
this.getWebcontentsByWindowId(this.settingWindowId)
.send('showInputBox', options)
return new Promise<string>((resolve, reject) => {
ipcMain.once('showInputBox', (event: Event, value: string) => {
resolve(value)
})
})
}
showFileExplorer (options: IShowFileExplorerOption = {}) {
return new Promise<string>(async (resolve, reject) => {
this.windowId = await getWindowId()
dialog.showOpenDialog(BrowserWindow.fromId(this.windowId), options, (filename: string) => {
resolve(filename)
})
})
}
async upload (input: IUploadOption) {
this.windowId = await getWindowId()
const webContents = this.getWebcontentsByWindowId(this.windowId)
const imgs = await uploader.setWebContents(webContents).upload(input)
if (imgs !== false) {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
let pasteText = ''
for (let i = 0; i < imgs.length; i++) {
pasteText += pasteTemplate(pasteStyle, imgs[i]) + '\r\n'
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl as string,
icon: imgs[i].imgUrl
})
setTimeout(() => {
notification.show()
}, i * 100)
db.insert('uploaded', imgs[i])
}
clipboard.writeText(pasteText)
webContents.send('uploadFiles', imgs)
webContents.send('updateGallery')
return imgs
}
return []
}
showNotification (options: IShowNotificationOption = {
title: '',
body: ''
}) {
const notification = new Notification({
title: options.title,
body: options.body
})
notification.show()
}
showMessageBox (options: IShowMessageBoxOption = {
title: '',
message: '',
type: 'info',
buttons: ['Yes', 'No']
}) {
return new Promise<IShowMessageBoxResult>(async (resolve, reject) => {
this.windowId = await getWindowId()
dialog.showMessageBox(
BrowserWindow.fromId(this.windowId),
options
).then((res) => {
resolve({
result: res.response,
checkboxChecked: res.checkboxChecked
})
})
})
}
}
export default GuiApi
+19
View File
@@ -0,0 +1,19 @@
import PicGoCore from '~/universal/types/picgo'
import {
app
} from 'electron'
import path from 'path'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo') as typeof PicGoCore
const STORE_PATH = app.getPath('userData')
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
const picgo = new PicGo(CONFIG_PATH)
picgo.saveConfig({
debug: true,
PICGO_ENV: 'GUI'
})
export default picgo as PicGoCore
+171
View File
@@ -0,0 +1,171 @@
import bus from '../../utils/eventBus'
import PicGoCore from '~/universal/types/picgo'
import path from 'path'
import {
app,
globalShortcut,
BrowserWindow
} from 'electron'
import logger from '../../utils/logger'
import GuiApi from '../gui'
import db from '#/datastore'
import shortKeyService from './shortKeyService'
import picgo from '../picgo'
class ShortKeyHandler {
private isInModifiedMode: boolean = false
constructor () {
bus.on('toggleShortKeyModifiedMode', flag => {
this.isInModifiedMode = flag
})
}
init () {
this.initBuiltInShortKey()
this.initPluginsShortKey()
}
private initBuiltInShortKey () {
const commands = db.get('settings.shortKey') as IShortKeyConfigs
Object.keys(commands)
.filter(item => item.includes('picgo:'))
.map(command => {
const config = commands[command]
globalShortcut.register(config.key, () => {
this.handler(command)
})
})
}
private initPluginsShortKey () {
const pluginList = picgo.pluginLoader.getList()
for (let item of pluginList) {
const plugin = picgo.pluginLoader.getPlugin(item)
// if a plugin has commands
if (plugin && plugin.commands) {
if (typeof plugin.commands !== 'function') {
logger.warn(`${item}'s commands is not a function`)
continue
}
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
for (let cmd of commands) {
const command = `${item}:${cmd.name}`
if (db.has(`settings.shortKey[${command}]`)) {
const commandConfig = db.get(`settings.shortKey.${command}`) as IShortKeyConfig
this.registerShortKey(commandConfig, command, cmd.handle, false)
} else {
this.registerShortKey(cmd, command, cmd.handle, true)
}
}
} else {
continue
}
}
}
private registerShortKey (config: IShortKeyConfig | IPluginShortKeyConfig, command: string, handler: IShortKeyHandler, writeFlag: boolean) {
shortKeyService.registerCommand(command, handler)
if (config.key) {
globalShortcut.register(config.key, () => {
this.handler(command)
})
} else {
logger.warn(`${command} do not provide a key to bind`)
}
if (writeFlag) {
picgo.saveConfig({
[`settings.shortKey.${command}`]: {
enable: true,
name: config.name,
label: config.label,
key: config.key
}
})
}
}
// enable or disable shortKey
bindOrUnbindShortKey (item: IShortKeyConfig, from: string): boolean {
const command = `${from}:${item.name}`
if (item.enable === false) {
globalShortcut.unregister(item.key)
picgo.saveConfig({
[`settings.shortKey.${command}.enable`]: false
})
return true
} else {
if (globalShortcut.isRegistered(item.key)) {
return false
} else {
picgo.saveConfig({
[`settings.shortKey.${command}.enable`]: true
})
globalShortcut.register(item.key, () => {
this.handler(command)
})
return true
}
}
}
// update shortKey bindings
updateShortKey (item: IShortKeyConfig, oldKey: string, from: string): boolean {
const command = `${from}:${item.name}`
if (globalShortcut.isRegistered(item.key)) return false
globalShortcut.unregister(oldKey)
picgo.saveConfig({
[`settings.shortKey.${command}.key`]: item.key
})
globalShortcut.register(item.key, () => {
this.handler(`${from}:${item.name}`)
})
return true
}
private async handler (command: string) {
if (this.isInModifiedMode) {
return
}
if (command.includes('picgo:')) {
bus.emit(command)
} else if (command.includes('picgo-plugin-')) {
const handler = shortKeyService.getShortKeyHandler(command)
if (handler) {
const guiApi = new GuiApi()
return handler(picgo, guiApi)
}
} else {
logger.warn(`can not find command: ${command}`)
}
}
registerPluginShortKey (pluginName: string) {
const plugin = picgo.pluginLoader.getPlugin(pluginName)
if (plugin && plugin.commands) {
if (typeof plugin.commands !== 'function') {
logger.warn(`${pluginName}'s commands is not a function`)
return
}
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
for (let cmd of commands) {
const command = `${pluginName}:${cmd.name}`
if (db.has(`settings.shortKey[${command}]`)) {
const commandConfig = db.get(`settings.shortKey[${command}]`) as IShortKeyConfig
this.registerShortKey(commandConfig, command, cmd.handle, false)
} else {
this.registerShortKey(cmd, command, cmd.handle, true)
}
}
}
}
unregisterPluginShortKey (pluginName: string) {
const commands = db.get('settings.shortKey') as IShortKeyConfigs
const keyList = Object.keys(commands)
.filter(command => command.includes(pluginName))
.map(command => {
return {
command,
key: commands[command].key
}
}) as IKeyCommandType[]
keyList.forEach(item => {
globalShortcut.unregister(item.key)
shortKeyService.unregisterCommand(item.command)
picgo.unsetConfig('settings.shortKey', item.command)
})
}
}
export default new ShortKeyHandler()
+21
View File
@@ -0,0 +1,21 @@
import logger from '../../utils/logger'
class ShortKeyService {
private commandList: Map<string, IShortKeyHandler> = new Map()
registerCommand (command: string, handler: IShortKeyHandler) {
this.commandList.set(command, handler)
}
unregisterCommand (command: string) {
this.commandList.delete(command)
}
getShortKeyHandler (command: string): IShortKeyHandler | null {
const handler = this.commandList.get(command)
if (handler) return handler
logger.warn(`cannot find command: ${command}`)
return null
}
getCommandList () {
return [...this.commandList.keys()]
}
}
export default new ShortKeyService()
+74
View File
@@ -0,0 +1,74 @@
import {
clipboard,
Notification,
WebContents
} from 'electron'
import windowManager from '~/main/apis/window/windowManager'
import { IWindowList } from '~/main/apis/window/constants'
import uploader from './'
import pasteTemplate from '#/utils/pasteTemplate'
import db from '#/datastore'
export const uploadClipboardFiles = async (): Promise<string> => {
const win = windowManager.getAvailableWindow()
let img = await uploader.setWebContents(win!.webContents).upload()
if (img !== false) {
if (img.length > 0) {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
clipboard.writeText(pasteTemplate(pasteStyle, img[0]))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl!,
icon: img[0].imgUrl
})
notification.show()
db.insert('uploaded', img[0])
trayWindow.webContents.send('clipboardFiles', [])
trayWindow.webContents.send('uploadFiles', img)
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
}
return img[0].imgUrl as string
} else {
const notification = new Notification({
title: '上传不成功',
body: '你剪贴板最新的一条记录不是图片哦'
})
notification.show()
return ''
}
} else {
return ''
}
}
export const uploadChoosedFiles = async (webContents: WebContents, files: IFileWithPath[]): Promise<string[]> => {
const input = files.map(item => item.path)
const imgs = await uploader.setWebContents(webContents).upload(input)
const result = []
if (imgs !== false) {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
let pasteText = ''
for (let i = 0; i < imgs.length; i++) {
pasteText += pasteTemplate(pasteStyle, imgs[i]) + '\r\n'
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl!,
icon: files[i].path
})
setTimeout(() => {
notification.show()
}, i * 100)
db.insert('uploaded', imgs[i])
result.push(imgs[i].imgUrl!)
}
clipboard.writeText(pasteText)
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('uploadFiles', imgs)
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
}
return result
} else {
return []
}
}
+117
View File
@@ -0,0 +1,117 @@
import {
app,
Notification,
BrowserWindow,
ipcMain,
WebContents
} from 'electron'
import dayjs from 'dayjs'
import picgo from '~/main/apis/picgo'
import db from '#/datastore'
import windowManager from '~/main/apis/window/windowManager'
import { IWindowList } from '~/main/apis/window/constants'
const waitForShow = (webcontent: WebContents) => {
return new Promise((resolve, reject) => {
webcontent.on('did-finish-load', () => {
resolve()
})
})
}
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
return new Promise((resolve, reject) => {
const windowId = window.id
ipcMain.once(`rename${id}`, (evt: Event, newName: string) => {
resolve(newName)
window.close()
})
window.on('close', () => {
resolve(null)
ipcMain.removeAllListeners(`rename${id}`)
windowManager.deleteById(windowId)
})
})
}
class Uploader {
private webContents: WebContents | null = null
constructor () {
this.init()
}
init () {
picgo.on('notification', message => {
const notification = new Notification(message)
notification.show()
})
picgo.on('uploadProgress', progress => {
this.webContents?.send('uploadProgress', progress)
})
picgo.on('beforeTransform', ctx => {
if (db.get('settings.uploadNotification')) {
const notification = new Notification({
title: '上传进度',
body: '正在上传'
})
notification.show()
}
})
picgo.helper.beforeUploadPlugins.register('renameFn', {
handle: async ctx => {
const rename = db.get('settings.rename')
const autoRename = db.get('settings.autoRename')
await Promise.all(ctx.output.map(async (item, index) => {
let name: undefined | string | null
let fileName: string | undefined
if (autoRename) {
fileName = dayjs().add(index, 'second').format('YYYYMMDDHHmmss') + item.extname
} else {
fileName = item.fileName
}
if (rename) {
const window = windowManager.create(IWindowList.RENAME_WINDOW)!
await waitForShow(window.webContents)
window.webContents.send('rename', fileName, window.webContents.id)
name = await waitForRename(window, window.webContents.id)
}
item.fileName = name || fileName
}))
}
})
}
setWebContents (webContents: WebContents) {
this.webContents = webContents
return this
}
upload (img?: IUploadOption): Promise<ImgInfo[]|false> {
picgo.upload(img)
return new Promise((resolve) => {
picgo.once('finished', ctx => {
if (ctx.output.every((item: ImgInfo) => item.imgUrl)) {
resolve(ctx.output)
} else {
resolve(false)
}
picgo.removeAllListeners('failed')
})
picgo.once('failed', ctx => {
setTimeout(() => {
const notification = new Notification({
title: '上传失败',
body: '请检查配置和上传的文件是否符合要求'
})
notification.show()
}, 500)
picgo.removeAllListeners('finished')
resolve(false)
})
})
}
}
export default new Uploader()
+27
View File
@@ -0,0 +1,27 @@
export enum IWindowList {
SETTING_WINDOW = 'SETTING_WINDOW',
TRAY_WINDOW = 'TRAY_WINDOW',
MINI_WINDOW = 'MINI_WINDOW',
RENAME_WINDOW = 'RENAME_WINDOW'
}
const isDevelopment = process.env.NODE_ENV !== 'production'
export const TRAY_WINDOW_URL = isDevelopment
? (process.env.WEBPACK_DEV_SERVER_URL as string)
: `picgo://./index.html`
export const SETTING_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#setting/upload`
: `picgo://./index.html#setting/upload`
export const MINI_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page`
: `picgo://./index.html#mini-page`
export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page`
: `picgo://./index.html#rename-page`
export const DELETE_WINDOW_EVENT = 'DELETE_WINDOW_EVENT'
export const CREATE_WINDOW_EVENT = 'CREATE_WINDOW_EVENT'
+181
View File
@@ -0,0 +1,181 @@
import {
IWindowList,
DELETE_WINDOW_EVENT,
CREATE_WINDOW_EVENT,
SETTING_WINDOW_URL,
TRAY_WINDOW_URL,
MINI_WINDOW_URL,
RENAME_WINDOW_URL
} from './constants'
import { IWindowListItem } from '#/types/electron'
import bus from '~/main/utils/eventBus'
import db from '#/datastore'
import { getWindowId } from '~/main/apis/bus'
import { BrowserWindow, app } from 'electron'
import { CREATE_APP_MENU } from '~/main/apis/bus/constants'
const windowList = new Map<IWindowList, IWindowListItem>()
windowList.set(IWindowList.TRAY_WINDOW, {
isValid: process.platform !== 'linux',
multiple: false,
options () {
return {
height: 350,
width: 196, // 196
show: false,
frame: false,
fullscreenable: false,
resizable: false,
transparent: true,
vibrancy: 'ultra-dark',
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
backgroundThrottling: false
}
}
},
callback (window) {
const id = window!.id
window!.loadURL(TRAY_WINDOW_URL)
window!.on('closed', () => {
bus.emit(DELETE_WINDOW_EVENT, id)
})
window!.on('blur', () => {
window!.hide()
})
}
})
windowList.set(IWindowList.SETTING_WINDOW, {
isValid: true,
multiple: false,
options () {
const options: IBrowserWindowOptions = {
height: 450,
width: 800,
show: false,
frame: true,
center: true,
fullscreenable: false,
resizable: false,
title: 'PicGo',
vibrancy: 'ultra-dark',
transparent: true,
titleBarStyle: 'hidden',
webPreferences: {
backgroundThrottling: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
webSecurity: false
}
}
if (process.platform !== 'darwin') {
options.show = false
options.frame = false
options.backgroundColor = '#3f3c37'
options.transparent = false
options.icon = `${__static}/logo.png`
}
return options
},
callback (window) {
const id = window!.id
window!.loadURL(SETTING_WINDOW_URL)
window!.on('closed', () => {
bus.emit('toggleShortKeyModifiedMode', false)
bus.emit(DELETE_WINDOW_EVENT, id)
if (process.platform === 'linux') {
process.nextTick(() => {
app.quit()
})
}
})
bus.emit(CREATE_APP_MENU)
bus.emit(CREATE_WINDOW_EVENT, IWindowList.MINI_WINDOW)
}
})
windowList.set(IWindowList.MINI_WINDOW, {
isValid: process.platform !== 'darwin',
multiple: false,
options () {
let obj: IBrowserWindowOptions = {
height: 64,
width: 64,
show: process.platform === 'linux',
frame: false,
fullscreenable: false,
skipTaskbar: true,
resizable: false,
transparent: process.platform !== 'linux',
icon: `${__static}/logo.png`,
webPreferences: {
backgroundThrottling: false,
nodeIntegration: true,
nodeIntegrationInWorker: true
}
}
if (db.get('settings.miniWindowOntop')) {
obj.alwaysOnTop = true
}
return obj
},
callback (window) {
const id = window!.id
window!.loadURL(MINI_WINDOW_URL)
window!.on('closed', () => {
bus.emit(DELETE_WINDOW_EVENT, id)
})
}
})
windowList.set(IWindowList.RENAME_WINDOW, {
isValid: true,
multiple: true,
options () {
let options: IBrowserWindowOptions = {
height: 175,
width: 300,
show: true,
fullscreenable: false,
resizable: false,
vibrancy: 'ultra-dark',
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
backgroundThrottling: false
}
}
if (process.platform !== 'darwin') {
options.show = true
options.backgroundColor = '#3f3c37'
options.autoHideMenuBar = true
options.transparent = false
}
return options
},
async callback (window) {
window!.loadURL(RENAME_WINDOW_URL)
const currentWindowId = await getWindowId()
const currentWindow = BrowserWindow.fromId(currentWindowId)
if (currentWindow && currentWindow.isVisible()) {
// bounds: { x: 821, y: 75, width: 800, height: 450 }
const bounds = currentWindow.getBounds()
const positionX = bounds.x + bounds.width / 2 - 150
let positionY
// if is the settingWindow
if (bounds.height > 400) {
positionY = bounds.y + bounds.height / 2 - 88
} else { // if is the miniWindow
positionY = bounds.y + bounds.height / 2
}
window!.setPosition(positionX, positionY, false)
}
}
})
export default windowList
+77
View File
@@ -0,0 +1,77 @@
import {
BrowserWindow
} from 'electron'
import { IWindowManager, IWindowListItem } from '#/types/electron'
import windowList from './windowList'
import {
IWindowList,
DELETE_WINDOW_EVENT,
CREATE_WINDOW_EVENT
} from './constants'
import bus from '~/main/utils/eventBus'
class WindowManager implements IWindowManager {
private windowMap: Map<IWindowList | string, BrowserWindow> = new Map()
private windowIdMap: Map<number, IWindowList | string> = new Map()
constructor () {
bus.on(DELETE_WINDOW_EVENT, this.deleteById)
bus.on(CREATE_WINDOW_EVENT, this.create)
}
create = (name: IWindowList) => {
const windowConfig: IWindowListItem = windowList.get(name)!
if (windowConfig.isValid) {
if (!windowConfig.multiple) {
if (this.windowMap.has(name)) return this.windowMap.get(name)!
}
const window = new BrowserWindow(windowConfig.options())
if (windowConfig.multiple) {
this.windowMap.set(`${name}_${window.id}`, window)
this.windowIdMap.set(window.id, `${name}_${window.id}`)
} else {
this.windowMap.set(name, window)
this.windowIdMap.set(window.id, name)
}
windowConfig.callback(window)
return window
} else {
return null
}
}
get (name: IWindowList) {
if (this.windowMap.has(name)) {
return this.windowMap.get(name)!
} else {
const window = this.create(name)
return window
}
}
has (name: IWindowList) {
return this.windowMap.has(name)
}
delete = (name: IWindowList) => {
const window = this.windowMap.get(name)
if (window) {
this.windowIdMap.delete(window.id)
this.windowMap.delete(name)
}
}
deleteById = (id: number) => {
const name = this.windowIdMap.get(id)
if (name) {
this.windowMap.delete(name)
this.windowIdMap.delete(id)
}
}
getAvailableWindow () {
const miniWindow = this.windowMap.get(IWindowList.MINI_WINDOW)
if (miniWindow && miniWindow.isVisible()) {
return miniWindow
} else {
const settingWindow = this.windowMap.get(IWindowList.SETTING_WINDOW)
const trayWindow = this.windowMap.get(IWindowList.TRAY_WINDOW)
return settingWindow || trayWindow || this.create(IWindowList.SETTING_WINDOW)!
}
}
}
export default new WindowManager()
-24
View File
@@ -1,24 +0,0 @@
/**
* This file is used specifically and only for development. It installs
* `electron-debug` & `vue-devtools`. There shouldn't be any need to
* modify this file, but it can be used to extend your development
* environment.
*/
/* eslint-disable */
// Install `electron-debug` with `devtron`
require('electron-debug')({ showDevTools: false })
// Install `vue-devtools`
require('electron').app.on('ready', () => {
let installExtension = require('electron-devtools-installer')
installExtension.default(installExtension.VUEJS_DEVTOOLS)
.then(() => {})
.catch(err => {
console.log('Unable to install `vue-devtools`: \n', err)
})
})
// Require `main` process to boot app
require('./index')
-598
View File
@@ -1,598 +0,0 @@
'use strict'
import Uploader from './utils/uploader.js'
import { app, BrowserWindow, Tray, Menu, Notification, clipboard, ipcMain, globalShortcut, dialog } from 'electron'
import db from '../datastore'
import beforeOpen from './utils/beforeOpen'
import pasteTemplate from './utils/pasteTemplate'
import updateChecker from './utils/updateChecker'
import { getPicBeds } from './utils/getPicBeds'
import pkg from '../../package.json'
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
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
*/
if (process.env.NODE_ENV !== 'development') {
global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
}
if (process.env.DEBUG_ENV === 'debug') {
global.__static = require('path').join(__dirname, '../../static').replace(/\\/g, '\\\\')
}
let window
let settingWindow
let miniWindow
let tray
let menu
let contextMenu
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080`
: `file://${__dirname}/index.html`
const settingWinURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/#setting/upload`
: `file://${__dirname}/index.html#setting/upload`
const miniWinURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/#mini-page`
: `file://${__dirname}/index.html#mini-page`
// fix the $PATH in macOS
fixPath()
function createContextMenu () {
const picBeds = getPicBeds(app)
const submenu = picBeds.map(item => {
return {
label: item.name,
type: 'radio',
checked: db.read().get('picBed.current').value() === item.type,
click () {
db.read().set('picBed.current', item.type).write()
if (settingWindow) {
settingWindow.webContents.send('syncPicBed')
}
}
}
})
contextMenu = Menu.buildFromTemplate([
{
label: '关于',
click () {
dialog.showMessageBox({
title: 'PicGo',
message: 'PicGo',
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
})
}
},
{
label: '打开详细窗口',
click () {
if (settingWindow === null) {
createSettingWindow()
settingWindow.show()
} else {
settingWindow.show()
settingWindow.focus()
}
if (miniWindow) {
miniWindow.hide()
}
}
},
{
label: '选择默认图床',
type: 'submenu',
submenu
},
{
label: '打开更新助手',
type: 'checkbox',
checked: db.get('settings.showUpdateTip').value(),
click () {
const value = db.read().get('settings.showUpdateTip').value()
db.read().set('settings.showUpdateTip', !value).write()
}
},
{
label: '重启应用',
click () {
app.relaunch()
app.exit(0)
}
},
{
role: 'quit',
label: '退出'
}
])
}
function createTray () {
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
tray = new Tray(menubarPic)
tray.on('right-click', () => {
if (window) {
window.hide()
}
createContextMenu()
tray.popUpContextMenu(contextMenu)
})
tray.on('click', (event, bounds) => {
if (process.platform === 'darwin') {
let img = clipboard.readImage()
let obj = []
if (!img.isEmpty()) {
// 从剪贴板来的图片默认转为png
const imgUrl = 'data:image/png;base64,' + Buffer.from(img.toPNG(), 'binary').toString('base64')
obj.push({
width: img.getSize().width,
height: img.getSize().height,
imgUrl
})
}
toggleWindow(bounds)
setTimeout(() => {
window.webContents.send('clipboardFiles', obj)
}, 0)
} else {
if (window) {
window.hide()
}
if (settingWindow === null) {
createSettingWindow()
settingWindow.show()
} else {
settingWindow.show()
settingWindow.focus()
}
if (miniWindow) {
miniWindow.hide()
}
}
})
tray.on('drag-enter', () => {
tray.setImage(`${__static}/upload.png`)
})
tray.on('drag-end', () => {
tray.setImage(`${__static}/menubar.png`)
})
tray.on('drop-files', async (event, files) => {
const pasteStyle = db.read().get('settings.pasteStyle').value() || 'markdown'
const imgs = await new Uploader(files, window.webContents).upload()
if (imgs !== false) {
for (let i in imgs) {
clipboard.writeText(pasteTemplate(pasteStyle, imgs[i]))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl,
icon: files[i]
})
setTimeout(() => {
notification.show()
}, i * 100)
db.read().get('uploaded').insert(imgs[i]).write()
}
window.webContents.send('dragFiles', imgs)
}
})
// toggleWindow()
}
const createWindow = () => {
if (process.platform !== 'darwin' && process.platform !== 'win32') {
return
}
window = new BrowserWindow({
height: 350,
width: 196, // 196
show: false,
frame: false,
fullscreenable: false,
resizable: false,
transparent: true,
vibrancy: 'ultra-dark',
webPreferences: {
backgroundThrottling: false
}
})
window.loadURL(winURL)
window.on('closed', () => {
window = null
})
window.on('blur', () => {
window.hide()
})
return window
}
const createMiniWidow = () => {
if (miniWindow) {
return false
}
let obj = {
height: 64,
width: 64,
show: process.platform === 'linux',
frame: false,
fullscreenable: false,
skipTaskbar: true,
resizable: false,
transparent: process.platform !== 'linux',
icon: `${__static}/logo.png`,
webPreferences: {
backgroundThrottling: false
}
}
if (db.read().get('settings.miniWindowOntop').value()) {
obj.alwaysOnTop = true
}
miniWindow = new BrowserWindow(obj)
miniWindow.loadURL(miniWinURL)
miniWindow.on('closed', () => {
miniWindow = null
})
return miniWindow
}
const createSettingWindow = () => {
const options = {
height: 450,
width: 800,
show: false,
frame: true,
center: true,
fullscreenable: false,
resizable: false,
title: 'PicGo',
vibrancy: 'ultra-dark',
transparent: true,
titleBarStyle: 'hidden',
webPreferences: {
backgroundThrottling: false,
webSecurity: false
}
}
if (process.platform !== 'darwin') {
options.show = false
options.frame = false
options.backgroundColor = '#3f3c37'
options.transparent = false
options.icon = `${__static}/logo.png`
}
settingWindow = new BrowserWindow(options)
settingWindow.loadURL(settingWinURL)
settingWindow.on('closed', () => {
settingWindow = null
if (process.platform === 'linux') {
app.quit()
}
})
createMenu()
createMiniWidow()
return settingWindow
}
const createMenu = () => {
if (process.env.NODE_ENV !== 'development') {
const template = [{
label: 'Edit',
submenu: [
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
{ label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
{ type: 'separator' },
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' },
{
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click () {
app.quit()
}
}
]
}]
menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
}
const toggleWindow = (bounds) => {
if (window.isVisible()) {
window.hide()
} else {
showWindow(bounds)
}
}
const showWindow = (bounds) => {
window.setPosition(bounds.x - 98 + 11, bounds.y, false)
window.webContents.send('updateFiles')
window.show()
window.focus()
}
const uploadClipboardFiles = async () => {
let win
if (miniWindow.isVisible()) {
win = miniWindow
} else {
win = settingWindow || window || createSettingWindow()
}
let img = await new Uploader(undefined, win.webContents).upload()
if (img !== false) {
if (img.length > 0) {
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: img[0].imgUrl
})
notification.show()
db.read().get('uploaded').insert(img[0]).write()
window.webContents.send('clipboardFiles', [])
window.webContents.send('uploadFiles', img)
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
} else {
const notification = new Notification({
title: '上传不成功',
body: '你剪贴板最新的一条记录不是图片哦'
})
notification.show()
}
}
}
const uploadChoosedFiles = async (webContents, files) => {
const input = files.map(item => item.path)
const imgs = await new Uploader(input, webContents).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: files[i].path
})
setTimeout(() => {
notification.show()
}, i * 100)
db.read().get('uploaded').insert(imgs[i]).write()
}
clipboard.writeText(pasteText)
window.webContents.send('uploadFiles', imgs)
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
}
}
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) => {
globalShortcut.unregisterAll()
for (let key in oldKey) {
globalShortcut.register(db.read().get('settings.shortKey').value()[key], () => {
return shortKeyHash[key]()
})
}
const notification = new Notification({
title: '操作成功',
body: '你的快捷键已经修改成功'
})
notification.show()
})
ipcMain.on('updateCustomLink', (evt, oldLink) => {
const notification = new Notification({
title: '操作成功',
body: '你的自定义链接格式已经修改成功'
})
notification.show()
})
ipcMain.on('autoStart', (evt, val) => {
app.setLoginItemSettings({
openAtLogin: val
})
})
ipcMain.on('openSettingWindow', (evt) => {
if (!settingWindow) {
createSettingWindow()
} else {
settingWindow.show()
}
miniWindow.hide()
})
ipcMain.on('openMiniWindow', (evt) => {
if (!miniWindow) {
createMiniWidow()
}
miniWindow.show()
miniWindow.focus()
settingWindow.hide()
})
// from mini window
ipcMain.on('syncPicBed', (evt) => {
if (settingWindow) {
settingWindow.webContents.send('syncPicBed')
}
})
ipcMain.on('getPicBeds', (evt) => {
const picBeds = getPicBeds(app)
evt.sender.send('getPicBeds', picBeds)
evt.returnValue = picBeds
})
const shortKeyHash = {
upload: uploadClipboardFiles
}
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
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') {
app.setAppUserModelId(pkg.build.appId)
}
if (process.env.XDG_CURRENT_DESKTOP && process.env.XDG_CURRENT_DESKTOP.includes('Unity')) {
process.env.XDG_CURRENT_DESKTOP = 'Unity'
}
app.on('ready', () => {
createWindow()
createSettingWindow()
if (process.platform === 'darwin' || process.platform === 'win32') {
createTray()
}
db.read().set('needReload', false).write()
updateChecker()
globalShortcut.register(db.read().get('settings.shortKey.upload').value(), () => {
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', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (window === null) {
createWindow()
}
if (settingWindow === null) {
createSettingWindow()
}
})
app.on('will-quit', () => {
globalShortcut.unregisterAll()
})
app.setLoginItemSettings({
openAtLogin: db.read().get('settings.autoStart').value() || false
})
/**
* Auto Updater
*
* Uncomment the following code below and install `electron-updater` to
* support auto updating. Code Signing with a valid certificate is required.
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
*/
// import { autoUpdater } from 'electron-updater'
// autoUpdater.on('update-downloaded', () => {
// autoUpdater.quitAndInstall()
// })
// app.on('ready', () => {
// if (process.env.NODE_ENV === 'production') {
// autoUpdater.checkForUpdates()
// }
// })
+26
View File
@@ -0,0 +1,26 @@
import DB from '#/datastore'
// from v2.1.2
const updateShortKeyFromVersion212 = (db: typeof DB, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
let needUpgrade = false
if (shortKeyConfig.upload) {
needUpgrade = true
// @ts-ignore
shortKeyConfig['picgo:upload'] = {
enable: true,
key: shortKeyConfig.upload,
name: 'upload',
label: '快捷上传'
}
delete shortKeyConfig.upload
}
if (needUpgrade) {
db.set('settings.shortKey', shortKeyConfig)
return shortKeyConfig
} else {
return false
}
}
export {
updateShortKeyFromVersion212
}
+107
View File
@@ -0,0 +1,107 @@
import http from 'http'
import routers from './routerManager'
import {
handleResponse
} from './utils'
import picgo from '~/main/apis/picgo'
import logger from '~/main/utils/logger'
class Server {
private httpServer: http.Server
private config: IServerConfig
constructor () {
let config = picgo.getConfig('settings.server')
const result = this.checkIfConfigIsValid(config)
if (result) {
this.config = config
} else {
config = {
port: 36677,
host: '127.0.0.1',
enable: true
}
this.config = config
picgo.saveConfig({
'settings.server': config
})
}
this.httpServer = http.createServer(this.handleRequest)
}
private checkIfConfigIsValid (config: IObj | undefined) {
if (config && config.port && config.host && (config.enable !== undefined)) {
return true
} else {
return false
}
}
private handleRequest = (request: http.IncomingMessage, response: http.ServerResponse) => {
if (request.method === 'POST') {
if (!routers.getHandler(request.url!)) {
handleResponse({
response,
statusCode: 404,
header: {},
body: {
success: false
}
})
} else {
let body: string = ''
let postObj: IObj
request.on('data', chunk => {
body += chunk
})
request.on('end', () => {
try {
postObj = (body === '') ? {} : JSON.parse(body)
} catch (err) {
return handleResponse({
response,
body: {
success: false,
message: 'Not sending data in JSON format'
}
})
}
const handler = routers.getHandler(request.url!)
handler!({
...postObj,
response
})
})
}
} else {
response.statusCode = 404
response.end()
}
}
private listen = (port: number) => {
logger.info(`[PicGo Server] is listening at ${port}`)
this.httpServer.listen(port, this.config.host).on('error', (err: ErrnoException) => {
if (err.errno === 'EADDRINUSE') {
logger.warn(`[PicGo Server] ${port} is busy, trying with port ${port + 1}`)
this.config.port += 1
picgo.saveConfig({
'settings.server': this.config
})
this.listen(this.config.port)
}
})
}
startup () {
if (this.config.enable) {
this.listen(this.config.port)
}
}
shutdown () {
this.httpServer.close()
logger.info('[PicGo Server] shutdown')
}
restart () {
this.config = picgo.getConfig('settings.server')
this.shutdown()
this.startup()
}
}
export default new Server()
+20
View File
@@ -0,0 +1,20 @@
class Router {
private router = new Map<string, routeHandler>()
get (url: string, callback: routeHandler): void {
this.router.set(url, callback)
}
post (url: string, callback: routeHandler): void {
this.router.set(url, callback)
}
getHandler (url: string) {
if (this.router.has(url)) {
return this.router.get(url)
} else {
return null
}
}
}
export default new Router()
+69
View File
@@ -0,0 +1,69 @@
import router from './router'
import {
uploadWithClipboardFiles,
uploadWithFiles
} from '~/main/apis/bus/index'
import {
handleResponse
} from './utils'
import logger from '../utils/logger'
router.get('/upload', async ({
response,
list = []
} : {
response: IHttpResponse,
list?: string[]
}): Promise<void> => {
try {
if (list.length === 0) {
// upload with clipboard
const res = await uploadWithClipboardFiles()
if (res.success) {
handleResponse({
response,
body: {
success: true,
result: res.result
}
})
} else {
handleResponse({
response
})
}
} else {
// upload with files
const pathList = list.map(item => {
return {
path: item
}
})
const res = await uploadWithFiles(pathList)
if (res.success) {
handleResponse({
response,
body: {
success: true,
result: res.result
}
})
} else {
handleResponse({
response
})
}
}
} catch (err) {
logger.error(err)
handleResponse({
response,
body: {
success: false,
message: err
}
})
}
})
export default router
+19
View File
@@ -0,0 +1,19 @@
export const handleResponse = ({
response,
statusCode = 200,
header = {
'Content-Type': 'application/json'
},
body = {
success: false
}
} : {
response: IHttpResponse,
statusCode?: number,
header?: IObj,
body?: any
}) => {
response.writeHead(statusCode, header)
response.write(JSON.stringify(body))
response.end()
}
-23
View File
@@ -1,23 +0,0 @@
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
+73
View File
@@ -0,0 +1,73 @@
import fs from 'fs-extra'
import path from 'path'
import os from 'os'
import { remote, app } from 'electron'
const APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData')
function beforeOpen () {
if (process.platform === 'darwin') {
resolveMacWorkFlow()
}
resolveClipboardImageGenerator()
}
/**
* macOS 右键菜单
*/
function resolveMacWorkFlow () {
const dest = `${os.homedir}/Library/Services/Upload pictures with PicGo.workflow`
if (fs.existsSync(dest)) {
return true
} else {
try {
fs.copySync(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
} catch (e) {
console.log(e)
}
}
}
/**
* 初始化剪贴板生成图片的脚本
*/
function resolveClipboardImageGenerator () {
let clipboardFiles = getClipboardFiles()
if (!fs.pathExistsSync(path.join(STORE_PATH, 'windows10.ps1'))) {
clipboardFiles.forEach(item => {
fs.copyFileSync(item.origin, item.dest)
})
} else {
clipboardFiles.forEach(item => {
diffFilesAndUpdate(item.origin, item.dest)
})
}
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
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',
'/windows10.ps1'
]
return files.map(item => {
return {
origin: path.join(__static, item),
dest: path.join(STORE_PATH, item)
}
})
}
}
export default beforeOpen
+5
View File
@@ -0,0 +1,5 @@
import { EventEmitter } from 'events'
const bus = new EventEmitter()
export default bus
-27
View File
@@ -1,27 +0,0 @@
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
}
+17
View File
@@ -0,0 +1,17 @@
import picgo from '../apis/picgo'
const getPicBeds = () => {
const picBedTypes = picgo.helper.uploader.getIdList()
const picBedFromDB = picgo.getConfig('picBed.list') || []
const picBeds = picBedTypes.map((item: string) => {
const visible = picBedFromDB.find((i: IPicBedType) => i.type === item) // object or undefined
return {
type: item,
name: picgo.helper.uploader.get(item).name || item,
visible: visible ? visible.visible : true
}
}) as IPicBedType[]
return picBeds
}
export default getPicBeds
-124
View File
@@ -1,124 +0,0 @@
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
@@ -1,5 +1,9 @@
import path from 'path' import path from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
type ClipboardFileObject = {
path: string
}
type Result = ClipboardFileObject[]
const getUploadFiles = (argv = process.argv, cwd = process.cwd()) => { const getUploadFiles = (argv = process.argv, cwd = process.cwd()) => {
let files = argv.slice(1) let files = argv.slice(1)
if (files.length > 0 && files[0] === 'upload') { if (files.length > 0 && files[0] === 'upload') {
@@ -7,7 +11,7 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd()) => {
return null // for uploading images in clipboard return null // for uploading images in clipboard
} else if (files.length > 1) { } else if (files.length > 1) {
files = argv.slice(1) files = argv.slice(1)
let result = [] let result: Result = []
if (files.length > 0) { if (files.length > 0) {
result = files.map(item => { result = files.map(item => {
if (path.isAbsolute(item)) { if (path.isAbsolute(item)) {
@@ -24,7 +28,7 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd()) => {
return null return null
} }
} }
}).filter(item => item !== null) }).filter(item => item !== null) as Result
} }
return result return result
} }
+76
View File
@@ -0,0 +1,76 @@
import chalk from 'chalk'
import dayjs from 'dayjs'
import fs from 'fs-extra'
import path from 'path'
import util from 'util'
import db from '#/datastore'
import { app } from 'electron'
import { IChalkType } from '#/types/enum'
const baseDir = app.getPath('userData')
class Logger {
private level = {
success: IChalkType.success,
info: IChalkType.info,
warn: IChalkType.warn,
error: IChalkType.error
}
protected handleLog (type: ILogType, msg: ILoggerType): ILoggerType {
// if configPath is invalid then this.ctx.config === undefined
// if not then check config.silent
const log = chalk[this.level[type]](`[PicGo ${type.toUpperCase()}]:`)
console.log(log, msg)
process.nextTick(() => {
this.handleWriteLog(type, msg)
})
return msg
}
protected handleWriteLog (type: string, msg: ILoggerType): void {
try {
const logLevel = db.get('settings.logLevel')
const logPath = db.get('settings.logPath') || path.join(baseDir, './picgo.log')
if (this.checkLogLevel(type, logLevel)) {
const picgoLog = fs.createWriteStream(logPath, { flags: 'a', encoding: 'utf8' })
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicGo ${type.toUpperCase()}] ${msg}`
const logger = new console.Console(picgoLog)
if (typeof msg === 'object' && type === 'error') {
log += `\n------Error Stack Begin------\n${util.format(msg.stack)}\n-------Error Stack End-------`
}
logger.log(log)
picgoLog.destroy()
}
} catch (e) {
console.log(e)
}
}
protected checkLogLevel (type: string, level: undefined | string | string[]): boolean {
if (level === undefined || level === 'all') {
return true
}
if (Array.isArray(level)) {
return level.some((item: string) => (item === type || item === 'all'))
} else {
return type === level
}
}
success (msg: ILoggerType): ILoggerType {
return this.handleLog('success', msg)
}
info (msg: ILoggerType): ILoggerType {
return this.handleLog('info', msg)
}
error (msg: ILoggerType): ILoggerType {
return this.handleLog('error', msg)
}
warn (msg: ILoggerType): ILoggerType {
return this.handleLog('warn', msg)
}
}
export default new Logger()
@@ -1,21 +1,30 @@
import { dialog, shell } from 'electron' import { dialog, shell } from 'electron'
import db from '../../datastore' import db from '#/datastore'
import axios from 'axios' import axios from 'axios'
import pkg from '../../../package.json' import pkg from 'root/package.json'
const version = pkg.version const version = pkg.version
const release = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest' const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
const releaseUrlBackup = 'https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@latest/package.json'
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest' const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
const checkVersion = async () => { const checkVersion = async () => {
let showTip = db.read().get('settings.showUpdateTip').value() let showTip = db.get('settings.showUpdateTip')
if (showTip === undefined) { if (showTip === undefined) {
db.read().set('settings.showUpdateTip', true).write() db.set('settings.showUpdateTip', true)
showTip = true showTip = true
} }
if (showTip) { if (showTip) {
const res = await axios.get(release) let res: any
try {
res = await axios.get(releaseUrl).catch(async () => {
const result = await axios.get(releaseUrlBackup)
return result
})
} catch (err) {
console.log(err)
}
if (res.status === 200) { if (res.status === 200) {
const latest = res.data.name const latest = res.data.version || res.data.name
const result = compareVersion2Update(version, latest) const result = compareVersion2Update(version, latest)
if (result) { if (result) {
dialog.showMessageBox({ dialog.showMessageBox({
@@ -25,11 +34,11 @@ const checkVersion = async () => {
message: `发现新版本${latest},更新了很多功能,是否去下载最新的版本?`, message: `发现新版本${latest},更新了很多功能,是否去下载最新的版本?`,
checkboxLabel: '以后不再提醒', checkboxLabel: '以后不再提醒',
checkboxChecked: false checkboxChecked: false
}, (res, checkboxChecked) => { }).then(res => {
if (res === 0) { // if selected yes if (res.response === 0) { // if selected yes
shell.openExternal(downloadUrl) shell.openExternal(downloadUrl)
} }
db.read().set('settings.showUpdateTip', !checkboxChecked).write() db.set('settings.showUpdateTip', !res.checkboxChecked)
}) })
} }
} else { } else {
@@ -41,7 +50,7 @@ const checkVersion = async () => {
} }
// if true -> update else return false // if true -> update else return false
const compareVersion2Update = (current, latest) => { const compareVersion2Update = (current: string, latest: string) => {
const currentVersion = current.split('.').map(item => parseInt(item)) const currentVersion = current.split('.').map(item => parseInt(item))
const latestVersion = latest.split('.').map(item => parseInt(item)) const latestVersion = latest.split('.').map(item => parseInt(item))
-156
View File
@@ -1,156 +0,0 @@
import {
app,
Notification,
BrowserWindow,
ipcMain
} from 'electron'
import path from 'path'
import dayjs from 'dayjs'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo')
const STORE_PATH = app.getPath('userData')
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
const renameURL = process.env.NODE_ENV === 'development' ? `http://localhost:9080/#rename-page` : `file://${__dirname}/index.html#rename-page`
const createRenameWindow = (win) => {
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)
// 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
}
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
+3 -3
View File
@@ -5,9 +5,9 @@
</template> </template>
<script> <script>
export default { export default {
name: 'picgo' name: 'picgo'
} }
</script> </script>
<style lang="stylus"> <style lang="stylus">
Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+5 -3
View File
@@ -21,17 +21,19 @@ export default {
} }
}, },
created () { created () {
if (this.type === this.$db.get('picBed.current').value()) { if (this.type === this.$db.get('picBed.current')) {
this.value = true this.value = true
} }
}, },
methods: { methods: {
choosePicBed (val) { choosePicBed (val) {
this.$db.set('picBed.current', this.type) this.letPicGoSaveData({
'picBed.current': this.type
})
this.$emit('update:choosed', this.type) this.$emit('update:choosed', this.type)
} }
} }
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
</style> </style>
+56 -61
View File
@@ -9,7 +9,7 @@
> >
<el-form-item <el-form-item
v-for="(item, index) in configList" v-for="(item, index) in configList"
:label="item.name" :label="item.alias || item.name"
:required="item.required" :required="item.required"
:prop="item.name" :prop="item.name"
:key="item.name + index" :key="item.name + index"
@@ -26,7 +26,7 @@
:placeholder="item.message || item.name" :placeholder="item.message || item.name"
> >
<el-option <el-option
v-for="(choice, idx) in item.choices" v-for="choice in item.choices"
:label="choice.name || choice.value || choice" :label="choice.name || choice.value || choice"
:key="choice.name || choice.value || choice" :key="choice.name || choice.value || choice"
:value="choice.value || choice" :value="choice.value || choice"
@@ -40,7 +40,7 @@
collapse-tags collapse-tags
> >
<el-option <el-option
v-for="(choice, idx) in item.choices" v-for="choice in item.choices"
:label="choice.name || choice.value || choice" :label="choice.name || choice.value || choice"
:key="choice.value || choice" :key="choice.value || choice"
:value="choice.value || choice" :value="choice.value || choice"
@@ -58,68 +58,63 @@
</el-form> </el-form>
</div> </div>
</template> </template>
<script> <script lang="ts">
import {
Component,
Vue,
Prop,
Watch
} from 'vue-property-decorator'
import { cloneDeep, union } from 'lodash' import { cloneDeep, union } from 'lodash'
export default {
name: 'config-form', @Component({
props: { name: 'config-form'
config: Array, })
type: String, export default class extends Vue {
id: { @Prop() private config!: any[]
type: String, @Prop() readonly type!: string
default: '' @Prop() readonly id!: string
} configList = []
}, ruleForm = {}
data () { @Watch('config', {
return { deep: true,
configList: [], immediate: true
ruleForm: {} })
} handleConfigChange (val: any) {
}, this.ruleForm = Object.assign({}, {})
created () { const config = this.$db.get(`picBed.${this.id}`)
}, if (val.length > 0) {
watch: { this.configList = cloneDeep(val).map((item: any) => {
config: { let defaultValue = item.default !== undefined
deep: true, ? item.default : item.type === 'checkbox'
handler (val) { ? [] : null
this.ruleForm = Object.assign({}, {}) if (item.type === 'checkbox') {
const config = this.$db.read().get(`picBed.${this.id}`).value() const defaults = item.choices.filter((i: any) => {
if (val.length > 0) { return i.checked
this.configList = cloneDeep(val).map(item => { }).map((i: any) => i.value)
let defaultValue = item.default !== undefined defaultValue = union(defaultValue, defaults)
? 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
})
} }
}, if (config && config[item.name] !== undefined) {
immediate: true defaultValue = config[item.name]
} }
}, this.$set(this.ruleForm, item.name, defaultValue)
methods: { return item
async validate () {
return new Promise((resolve, reject) => {
this.$refs.form.validate(valid => {
if (valid) {
resolve(this.ruleForm)
} else {
resolve(false)
return false
}
})
}) })
} }
} }
async validate () {
return new Promise((resolve, reject) => {
// @ts-ignore
this.$refs.form.validate((valid: boolean) => {
if (valid) {
resolve(this.ruleForm)
} else {
resolve(false)
return false
}
})
})
}
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
@@ -139,4 +134,4 @@ export default {
.el-switch__label .el-switch__label
&.is-active &.is-active
color #409EFF color #409EFF
</style> </style>
+139 -169
View File
@@ -91,29 +91,6 @@
</el-col> </el-col>
</el-row> </el-row>
</el-dialog> </el-dialog>
<el-dialog
title="修改快捷键"
:visible.sync="keyBindingVisible"
>
<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 <el-dialog
title="自定义链接格式" title="自定义链接格式"
:visible.sync="customLinkVisible" :visible.sync="customLinkVisible"
@@ -128,7 +105,7 @@
label="用占位符$url来表示url的位置" label="用占位符$url来表示url的位置"
prop="value" prop="value"
> >
<el-input <el-input
class="align-center" class="align-center"
v-model="customLink.value" v-model="customLink.value"
:autofocus="true" :autofocus="true"
@@ -159,167 +136,160 @@
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import pkg from 'root/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, ipcRenderer, IpcRendererEvent } from 'electron'
import db from '~/datastore' import db from '#/datastore'
import mixin from '@/utils/mixin' import mixin from '@/utils/mixin'
const { Menu, dialog, BrowserWindow } = remote const { Menu, dialog, BrowserWindow } = remote
export default { const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) => void) => {
if (!/\$url/.test(value)) {
return callback(new Error('必须含有$url'))
} else {
return callback()
}
}
@Component({
name: 'setting-page', name: 'setting-page',
mixins: [mixin], mixins: [mixin]
data () { })
const customLinkRule = (rule, value, callback) => { export default class extends Vue {
if (!/\$url/.test(value)) { version = process.env.NODE_ENV === 'production' ? pkg.version : 'Dev'
return callback(new Error('必须含有$url')) defaultActive = 'upload'
} else { menu: Electron.Menu | null = null
return callback() visible = false
} keyBindingVisible = false
} customLinkVisible = false
return { customLink = {
version: process.env.NODE_ENV === 'production' ? pkg.version : 'Dev', value: db.get('customLink') || '$url'
defaultActive: 'upload', }
menu: null, rules = {
visible: false, value: [
keyBindingVisible: false, { validator: customLinkRule, trigger: 'blur' }
customLinkVisible: false, ]
customLink: { }
value: db.read().get('customLink').value() || '$url' os = ''
}, shortKey: IShortKeyMap = {
rules: { upload: db.get('shortKey.upload')
value: [ }
{ validator: customLinkRule, trigger: 'blur' } picBed: IPicBedType[] = []
] // for showInputBox
}, showInputBoxVisible = false
os: '', inputBoxValue = ''
shortKey: { inputBoxOptions = {
upload: db.read().get('shortKey.upload').value() title: '',
}, placeholder: ''
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') ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds) ipcRenderer.on('getPicBeds', this.getPicBeds)
this.$electron.ipcRenderer.on('showInputBox', (evt, options) => { ipcRenderer.on('showInputBox', (evt: IpcRendererEvent, options: IShowInputBoxOption) => {
this.inputBoxValue = '' this.inputBoxValue = ''
this.inputBoxOptions.title = options.title || '' this.inputBoxOptions.title = options.title || ''
this.inputBoxOptions.placeholder = options.placeholder || '' this.inputBoxOptions.placeholder = options.placeholder || ''
this.showInputBoxVisible = true this.showInputBoxVisible = true
}) })
}, }
methods: { handleSelect (index: string) {
handleSelect (index) { const type = index.match(/picbeds-/)
const type = index.match(/picbeds-/) if (type === null) {
if (type === null) { this.$router.push({
name: index
})
} else {
const picBed = index.replace(/picbeds-/, '')
if (this.$builtInPicBed.includes(picBed)) {
this.$router.push({ this.$router.push({
name: index name: picBed
}) })
} else { } else {
const picBed = index.replace(/picbeds-/, '') this.$router.push({
if (this.$builtInPicBed.includes(picBed)) { name: 'others',
this.$router.push({ params: {
name: picBed type: picBed
}) }
} else { })
this.$router.push({ }
name: 'others', }
params: { }
type: picBed minimizeWindow () {
} const window = BrowserWindow.getFocusedWindow()
window!.minimize()
}
closeWindow () {
const window = BrowserWindow.getFocusedWindow()
window!.close()
}
buildMenu () {
const _this = this
const template = [
{
label: '关于',
click () {
dialog.showMessageBox({
title: 'PicGo',
message: 'PicGo',
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
}) })
} }
},
{
label: '赞助PicGo',
click () {
_this.visible = true
}
} }
}, ]
minimizeWindow () { this.menu = Menu.buildFromTemplate(template)
const window = BrowserWindow.getFocusedWindow() }
window.minimize() openDialog () {
}, // this.menu!.popup(remote.getCurrentWindow())
closeWindow () { this.menu!.popup()
const window = BrowserWindow.getFocusedWindow() }
window.close() keyDetect (type: string, event: KeyboardEvent) {
}, this.shortKey[type] = keyDetect(event).join('+')
buildMenu () { }
const _this = this cancelKeyBinding () {
const template = [ this.keyBindingVisible = false
{ this.shortKey = db.get('shortKey')
label: '关于', }
click () { cancelCustomLink () {
dialog.showMessageBox({ this.customLinkVisible = false
title: 'PicGo', this.customLink.value = db.get('customLink') || '$url'
message: 'PicGo', }
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo` confirmCustomLink () {
}) // @ts-ignore
} this.$refs.customLink.validate((valid: boolean) => {
}, if (valid) {
{ db.set('customLink', this.customLink.value)
label: '赞助PicGo', this.customLinkVisible = false
click () { ipcRenderer.send('updateCustomLink')
_this.visible = true } else {
} return false
} }
] })
this.menu = Menu.buildFromTemplate(template) }
}, openMiniWindow () {
openDialog () { ipcRenderer.send('openMiniWindow')
this.menu.popup(remote.getCurrentWindow()) }
}, getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
keyDetect (type, event) { this.picBed = picBeds
this.shortKey[type] = keyDetect(event).join('+') }
}, handleInputBoxClose () {
cancelKeyBinding () { ipcRenderer.send('showInputBox', this.inputBoxValue)
this.keyBindingVisible = false }
this.shortKey = db.read().get('shortKey').value() beforeRouteEnter (to: any, from: any, next: any) {
}, next((vm: this) => {
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
}
})
},
openMiniWindow () {
this.$electron.ipcRenderer.send('openMiniWindow')
},
getPicBeds (event, picBeds) {
this.picBed = picBeds
},
handleInputBoxClose () {
this.$electron.ipcRenderer.send('showInputBox', this.inputBoxValue)
}
},
beforeRouteEnter: (to, from, next) => {
next(vm => {
vm.defaultActive = to.name vm.defaultActive = to.name
}) })
}, }
beforeDestroy () { beforeDestroy () {
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds) ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
this.$electron.ipcRenderer.removeAllListeners('showInputBox') ipcRenderer.removeAllListeners('showInputBox')
} }
} }
</script> </script>
@@ -364,12 +334,12 @@ $darwinBg = transparentify(#172426, #000, 0.7)
position absolute position absolute
top 2px top 2px
right 4px right 4px
width 60px
z-index 10000 z-index 10000
-webkit-app-region no-drag -webkit-app-region no-drag
i i
cursor pointer cursor pointer
font-size 16px font-size 16px
margin-left 5px
.el-icon-minus .el-icon-minus
&:hover &:hover
color #409EFF color #409EFF
@@ -389,7 +359,7 @@ $darwinBg = transparentify(#172426, #000, 0.7)
overflow-y auto overflow-y auto
width 170px width 170px
.el-icon-info.setting-window .el-icon-info.setting-window
position fixed position fixed
bottom 4px bottom 4px
left 4px left 4px
cursor poiter cursor poiter
@@ -413,7 +383,7 @@ $darwinBg = transparentify(#172426, #000, 0.7)
&:before &:before
content '' content ''
position absolute position absolute
width 3px width 3px
height 20px height 20px
right 0 right 0
top 18px top 18px
@@ -435,7 +405,7 @@ $darwinBg = transparentify(#172426, #000, 0.7)
padding-top 22px padding-top 22px
position relative position relative
z-index 10 z-index 10
.el-dialog__body .el-dialog__body
padding 20px padding 20px
.support .support
text-align center text-align center
@@ -453,4 +423,4 @@ $darwinBg = transparentify(#172426, #000, 0.7)
background #6f6f6f background #6f6f6f
*::-webkit-scrollbar-track *::-webkit-scrollbar-track
background-color transparent background-color transparent
</style> </style>
+220 -215
View File
@@ -72,7 +72,7 @@
:options="options" :options="options"
></gallerys> ></gallerys>
<el-col :span="6" v-for="(item, index) in images" :key="item.id" class="gallery-list__img"> <el-col :span="6" v-for="(item, index) in images" :key="item.id" class="gallery-list__img">
<div <div
class="gallery-list__item" class="gallery-list__item"
@click="zoomImage(index)" @click="zoomImage(index)"
> >
@@ -83,7 +83,7 @@
<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>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
</el-col> </el-col>
@@ -102,258 +102,263 @@
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script> <script lang="ts">
// @ts-ignore
import gallerys from 'vue-gallery' import gallerys from 'vue-gallery'
import pasteStyle from '~/main/utils/pasteTemplate' import pasteStyle from '#/utils/pasteTemplate'
export default { import { Component, Vue } from 'vue-property-decorator'
import {
ipcRenderer,
clipboard,
IpcRendererEvent
} from 'electron'
@Component({
name: 'gallery', name: 'gallery',
components: { components: {
gallerys gallerys
}, }
data () { })
return { export default class extends Vue {
images: [], images: ImgInfo[] = []
idx: null, idx: null | number = null
options: { options = {
titleProperty: 'fileName', titleProperty: 'fileName',
urlProperty: 'imgUrl', urlProperty: 'imgUrl',
closeOnSlideClick: true closeOnSlideClick: true
}, }
dialogVisible: false, dialogVisible = false
imgInfo: { imgInfo = {
id: null, id: null,
imgUrl: '' imgUrl: ''
}, }
choosedList: {}, choosedList: IObjT<boolean> = {}
choosedPicBed: [], choosedPicBed: string[] = []
searchText: '', searchText = ''
handleBarActive: false, handleBarActive = false
pasteStyle: '', pasteStyle = ''
pasteStyleMap: { pasteStyleMap = {
Markdown: 'markdown', Markdown: 'markdown',
HTML: 'HTML', HTML: 'HTML',
URL: 'URL', URL: 'URL',
UBB: 'UBB', UBB: 'UBB',
Custom: 'Custom' Custom: 'Custom'
}, }
picBed: [] picBed: IPicBedType[] = []
} beforeRouteEnter (to: any, from: any, next: any) {
}, next((vm: any) => {
beforeRouteEnter (to, from, next) {
next(vm => {
vm.getGallery() vm.getGallery()
vm.getPasteStyle() vm.getPasteStyle()
vm.getPicBeds() vm.getPicBeds()
}) })
}, }
created () { created () {
this.$electron.ipcRenderer.on('updateGallery', (event) => { ipcRenderer.on('updateGallery', (event: IpcRendererEvent) => {
this.$nextTick(() => { this.$nextTick(() => {
this.filterList = this.getGallery() this.filterList = this.getGallery()
}) })
}) })
this.$electron.ipcRenderer.send('getPicBeds') ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds) ipcRenderer.on('getPicBeds', this.getPicBeds)
}, }
computed: { get filterList () {
filterList: { return this.getGallery()
get () { }
return this.getGallery() set filterList (val) {
}, this.images = val
set (val) { }
return this.val getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
this.picBed = picBeds
}
getGallery () {
if (this.choosedPicBed.length > 0) {
let arr: ImgInfo[] = []
this.choosedPicBed.forEach(item => {
let obj: IObj = {
type: item
}
if (this.searchText) {
obj.fileName = this.searchText
}
// @ts-ignore
arr = arr.concat(this.$db.read().get('uploaded').filter(obj => {
return obj.fileName.indexOf(this.searchText) !== -1 && obj.type === item
}).reverse().value())
})
this.images = arr
} else {
if (this.searchText) {
let data = this.$db.read().get('uploaded')
// @ts-ignore
.filter(item => {
return item.fileName.indexOf(this.searchText) !== -1
}).reverse().value()
this.images = data
} else {
// @ts-ignore
this.images = this.$db.read().get('uploaded').slice().reverse().value()
} }
} }
}, return this.images
methods: { }
getPicBeds (event, picBeds) { zoomImage (index: number) {
this.picBed = picBeds this.idx = index
}, this.changeZIndexForGallery(true)
getGallery () { }
if (this.choosedPicBed.length > 0) { changeZIndexForGallery (isOpen: boolean) {
let arr = [] if (isOpen) {
this.choosedPicBed.forEach(item => { // @ts-ignore
let obj = { document.querySelector('.main-content.el-row').style.zIndex = 101
type: item } else {
} // @ts-ignore
if (this.searchText) { document.querySelector('.main-content.el-row').style.zIndex = 10
obj.fileName = this.searchText }
} }
arr = arr.concat(this.$db.read().get('uploaded').filter(obj => { handleClose () {
return obj.fileName.indexOf(this.searchText) !== -1 && obj.type === item this.idx = null
}).reverse().value()) this.changeZIndexForGallery(false)
}) }
this.images = arr copy (item: ImgInfo) {
} else { const style = this.$db.get('settings.pasteStyle') || 'markdown'
if (this.searchText) { const copyLink = pasteStyle(style, item)
let data = this.$db.read().get('uploaded') const obj = {
.filter(item => { title: '复制链接成功',
return item.fileName.indexOf(this.searchText) !== -1 body: copyLink,
}).reverse().value() icon: item.url || item.imgUrl
this.images = data }
} else { const myNotification = new Notification(obj.title, obj)
this.images = this.$db.read().get('uploaded').slice().reverse().value() clipboard.writeText(copyLink)
} myNotification.onclick = () => {
} return true
return this.images }
}, }
zoomImage (index) { remove (id: string) {
this.idx = index this.$confirm('此操作将把该图片移出相册, 是否继续?', '提示', {
this.changeZIndexForGallery(true) confirmButtonText: '确定',
}, cancelButtonText: '取消',
changeZIndexForGallery (isOpen) { type: 'warning'
if (isOpen) { }).then(() => {
document.querySelector('.main-content.el-row').style.zIndex = 101 const file = this.$db.getById('uploaded', id)
} else { this.$db.removeById('uploaded', id)
document.querySelector('.main-content.el-row').style.zIndex = 10 ipcRenderer.send('removeFiles', [file])
}
},
handleClose () {
this.idx = null
this.changeZIndexForGallery(false)
},
copy (item) {
const style = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
const copyLink = pasteStyle(style, item)
const obj = { const obj = {
title: '复制链接成功', title: '操作结果',
body: copyLink, body: '删除成功'
icon: item.url || item.imgUrl
} }
const myNotification = new window.Notification(obj.title, obj) const myNotification = new Notification(obj.title, obj)
this.$electron.clipboard.writeText(copyLink)
myNotification.onclick = () => { myNotification.onclick = () => {
return true return true
} }
}, this.getGallery()
remove (id) { }).catch((e) => {
this.$confirm('此操作将把该图片移出相册, 是否继续?', '提示', { console.log(e)
return true
})
}
openDialog (item: ImgInfo) {
this.imgInfo.id = item.id
this.imgInfo.imgUrl = item.imgUrl as string
this.dialogVisible = true
}
confirmModify () {
this.$db.read().get('uploaded')
// @ts-ignore
.getById(this.imgInfo.id)
.assign({ imgUrl: this.imgInfo.imgUrl })
.write()
const obj = {
title: '修改图片URL成功',
body: this.imgInfo.imgUrl,
icon: this.imgInfo.imgUrl
}
const myNotification = new Notification(obj.title, obj)
myNotification.onclick = () => {
return true
}
this.dialogVisible = false
this.getGallery()
}
choosePicBed (type: string) {
let idx = this.choosedPicBed.indexOf(type)
if (idx !== -1) {
this.choosedPicBed.splice(idx, 1)
} else {
this.choosedPicBed.push(type)
}
}
cleanSearch () {
this.searchText = ''
}
isMultiple (obj: IObj) {
return Object.values(obj).some(item => item)
}
multiRemove () {
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
if (Object.values(this.choosedList).some(item => item)) {
this.$confirm('将删除刚才选中的图片,是否继续?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
const file = this.$db.read().get('uploaded').getById(id).value() let files: ImgInfo[] = []
this.$db.read().get('uploaded').removeById(id).write() Object.keys(this.choosedList).forEach(key => {
this.$electron.ipcRenderer.send('removeFiles', [file]) if (this.choosedList[key]) {
const file = this.$db.getById('uploaded', key)
files.push(file)
this.$db.removeById('uploaded', key)
}
})
this.choosedList = {}
this.getGallery()
const obj = { const obj = {
title: '操作结果', title: '操作结果',
body: '删除成功' body: '删除成功'
} }
const myNotification = new window.Notification(obj.title, obj) ipcRenderer.send('removeFiles', files)
const myNotification = new Notification(obj.title, obj)
myNotification.onclick = () => { myNotification.onclick = () => {
return true return true
} }
this.getGallery()
}).catch(() => { }).catch(() => {
return true return true
}) })
}, }
openDialog (item) { }
this.imgInfo.id = item.id multiCopy () {
this.imgInfo.imgUrl = item.imgUrl if (Object.values(this.choosedList).some(item => item)) {
this.dialogVisible = true let copyString = ''
}, const style = this.$db.get('settings.pasteStyle') || 'markdown'
confirmModify () { // choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
this.$db.read().get('uploaded') Object.keys(this.choosedList).forEach(key => {
.getById(this.imgInfo.id) if (this.choosedList[key]) {
.assign({imgUrl: this.imgInfo.imgUrl}) const item = this.$db.getById('uploaded', key)
.write() copyString += pasteStyle(style, item) + '\n'
this.choosedList[key] = false
}
})
const obj = { const obj = {
title: '修改图片URL成功', title: '批量复制链接成功',
body: this.imgInfo.imgUrl, body: copyString
icon: this.imgInfo.imgUrl
} }
const myNotification = new window.Notification(obj.title, obj) const myNotification = new Notification(obj.title, obj)
clipboard.writeText(copyString)
myNotification.onclick = () => { myNotification.onclick = () => {
return true return true
} }
this.dialogVisible = false
this.getGallery()
},
choosePicBed (type) {
let idx = this.choosedPicBed.indexOf(type)
if (idx !== -1) {
this.choosedPicBed.splice(idx, 1)
} else {
this.choosedPicBed.push(type)
}
},
cleanSearch () {
this.searchText = ''
},
isMultiple (obj) {
return Object.values(obj).some(item => item)
},
multiRemove () {
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
if (Object.values(this.choosedList).some(item => item)) {
this.$confirm('将删除刚才选中的图片,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let files = []
Object.keys(this.choosedList).forEach(key => {
if (this.choosedList[key]) {
const file = this.$db.read().get('uploaded').getById(key).value()
files.push(file)
this.$db.read().get('uploaded').removeById(key).write()
}
})
this.choosedList = {}
this.getGallery()
const obj = {
title: '操作结果',
body: '删除成功'
}
this.$electron.ipcRenderer.send('removeFiles', files)
const myNotification = new window.Notification(obj.title, obj)
myNotification.onclick = () => {
return true
}
}).catch(() => {
return true
})
}
},
multiCopy () {
if (Object.values(this.choosedList).some(item => item)) {
let copyString = ''
const style = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
Object.keys(this.choosedList).forEach(key => {
if (this.choosedList[key]) {
const item = this.$db.read().get('uploaded').getById(key).value()
copyString += pasteStyle(style, item) + '\n'
this.choosedList[key] = false
}
})
const obj = {
title: '批量复制链接成功',
body: copyString
}
const myNotification = new window.Notification(obj.title, obj)
this.$electron.clipboard.writeText(copyString)
myNotification.onclick = () => {
return true
}
}
},
toggleHandleBar () {
this.handleBarActive = !this.handleBarActive
},
getPasteStyle () {
this.pasteStyle = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
},
handlePasteStyleChange (val) {
this.$db.read().set('settings.pasteStyle', val)
.write()
this.pasteStyle = val
} }
}, }
toggleHandleBar () {
this.handleBarActive = !this.handleBarActive
}
getPasteStyle () {
this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
}
handlePasteStyleChange (val: string) {
this.$db.set('settings.pasteStyle', val)
this.pasteStyle = val
}
beforeDestroy () { beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('updateGallery') ipcRenderer.removeAllListeners('updateGallery')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds) ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
} }
} }
</script> </script>
@@ -429,7 +434,7 @@ export default {
&-fake &-fake
position absolute position absolute
top 0 top 0
left 0 left 0
opacity 0 opacity 0
width 100% width 100%
z-index -1 z-index -1
@@ -457,4 +462,4 @@ export default {
margin-bottom 10px margin-bottom 10px
.el-input__inner .el-input__inner
border-radius 14px border-radius 14px
</style> </style>
+155 -145
View File
@@ -15,31 +15,36 @@
</div> </div>
</div> </div>
</template> </template>
<script> <script lang="ts">
import mixin from '@/utils/mixin' import mixin from '@/utils/mixin'
export default { import { Component, Vue, Watch } from 'vue-property-decorator'
import {
ipcRenderer,
IpcRendererEvent,
remote
} from 'electron'
import path from 'path'
@Component({
name: 'mini-page', name: 'mini-page',
mixins: [mixin], mixins: [mixin]
data () { })
return { export default class extends Vue {
logo: 'static/squareLogo.png', logo = require('../assets/squareLogo.png')
dragover: false, dragover = false
progress: 0, progress = 0
showProgress: false, showProgress = false
showError: false, showError = false
dragging: false, dragging = false
wX: '', wX: number = -1
wY: '', wY: number = -1
screenX: '', screenX: number = -1
screenY: '', screenY: number = -1
menu: null, menu: Electron.Menu | null = null
os: '', os = ''
picBed: [] picBed: IPicBedType[] = []
}
},
created () { created () {
this.os = process.platform this.os = process.platform
this.$electron.ipcRenderer.on('uploadProgress', (event, progress) => { ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, progress: number) => {
if (progress !== -1) { if (progress !== -1) {
this.showProgress = true this.showProgress = true
this.progress = progress this.progress = progress
@@ -49,140 +54,145 @@ export default {
} }
}) })
this.getPicBeds() this.getPicBeds()
}, }
mounted () { 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)
}, }
watch: {
progress (val) { @Watch('progress')
if (val === 100) { onProgressChange (val: number) {
setTimeout(() => { if (val === 100) {
this.showProgress = false setTimeout(() => {
this.showError = false this.showProgress = false
}, 1000) this.showError = false
setTimeout(() => { }, 1000)
this.progress = 0 setTimeout(() => {
}, 1200) this.progress = 0
}, 1200)
}
}
getPicBeds () {
this.picBed = ipcRenderer.sendSync('getPicBeds')
this.buildMenu()
}
onDrop (e: DragEvent) {
this.dragover = false
this.ipcSendFiles(e.dataTransfer!.files)
}
openUploadWindow () {
// @ts-ignore
document.getElementById('file-uploader').click()
}
onChange (e: any) {
this.ipcSendFiles(e.target.files)
// @ts-ignore
document.getElementById('file-uploader').value = ''
}
ipcSendFiles (files: FileList) {
let sendFiles: IFileWithPath[] = []
Array.from(files).forEach((item, index) => {
let obj = {
name: item.name,
path: item.path
}
sendFiles.push(obj)
})
ipcRenderer.send('uploadChoosedFiles', sendFiles)
}
handleMouseDown (e: MouseEvent) {
this.dragging = true
this.wX = e.pageX
this.wY = e.pageY
this.screenX = e.screenX
this.screenY = e.screenY
}
handleMouseMove (e: MouseEvent) {
e.preventDefault()
e.stopPropagation()
if (this.dragging) {
const xLoc = e.screenX - this.wX
const yLoc = e.screenY - this.wY
remote.BrowserWindow.getFocusedWindow()!.setBounds({
x: xLoc,
y: yLoc,
width: 64,
height: 64
})
}
}
handleMouseUp (e: MouseEvent) {
this.dragging = false
if (this.screenX === e.screenX && this.screenY === e.screenY) {
if (e.button === 0) { // left mouse
this.openUploadWindow()
} else {
this.getPicBeds()
this.openContextMenu()
} }
} }
}, }
methods: { openContextMenu () {
getPicBeds () { this.menu!.popup()
this.picBed = this.$electron.ipcRenderer.sendSync('getPicBeds') }
this.buildMenu() buildMenu () {
}, const _this = this
onDrop (e) { const submenu = this.picBed.filter(item => item.visible).map(item => {
this.dragover = false return {
this.ipcSendFiles(e.dataTransfer.files) label: item.name,
}, type: 'radio',
openUploadWindow () { checked: this.$db.get('picBed.current') === item.type,
document.getElementById('file-uploader').click() click () {
}, _this.letPicGoSaveData({
onChange (e) { 'picBed.current': item.type
this.ipcSendFiles(e.target.files) })
document.getElementById('file-uploader').value = '' ipcRenderer.send('syncPicBed')
},
ipcSendFiles (files) {
let sendFiles = []
Array.from(files).forEach((item, index) => {
let obj = {
name: item.name,
path: item.path
}
sendFiles.push(obj)
})
this.$electron.ipcRenderer.send('uploadChoosedFiles', sendFiles)
},
handleMouseDown (e) {
this.dragging = true
this.wX = e.pageX
this.wY = e.pageY
this.screenX = e.screenX
this.screenY = e.screenY
},
handleMouseMove (e) {
e.preventDefault()
e.stopPropagation()
if (this.dragging) {
const xLoc = e.screenX - this.wX
const yLoc = e.screenY - this.wY
this.$electron.remote.BrowserWindow.getFocusedWindow().setBounds({
x: xLoc,
y: yLoc,
width: 64,
height: 64
})
}
},
handleMouseUp (e) {
this.dragging = false
if (this.screenX === e.screenX && this.screenY === e.screenY) {
if (e.button === 0) { // left mouse
this.openUploadWindow()
} else {
this.getPicBeds()
this.openContextMenu()
} }
} }
}, })
openContextMenu () { const template = [
this.menu.popup(this.$electron.remote.getCurrentWindow()) {
}, label: '打开详细窗口',
buildMenu () { click () {
const _this = this ipcRenderer.send('openSettingWindow')
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')
}
} }
}) },
const template = [ {
{ label: '选择默认图床',
label: '打开详细窗口', type: 'submenu',
click () { submenu
_this.$electron.ipcRenderer.send('openSettingWindow') },
} {
}, label: '剪贴板图片上传',
{ click () {
label: '选择默认图床', ipcRenderer.send('uploadClipboardFilesFromUploadPage')
type: 'submenu',
submenu
},
{
label: '剪贴板图片上传',
click () {
_this.$electron.ipcRenderer.send('uploadClipboardFilesFromUploadPage')
}
},
{
label: '最小化窗口',
role: 'minimize'
},
{
label: '重启应用',
click () {
_this.$electron.remote.app.relaunch()
_this.$electron.remote.app.exit(0)
}
},
{
role: 'quit',
label: '退出'
} }
] },
this.menu = this.$electron.remote.Menu.buildFromTemplate(template) {
} label: '隐藏窗口',
}, click () {
remote.BrowserWindow.getFocusedWindow()!.hide()
}
},
{
label: '重启应用',
click () {
remote.app.relaunch()
remote.app.exit(0)
}
},
{
role: 'quit',
label: '退出'
}
]
// @ts-ignore
this.menu = remote.Menu.buildFromTemplate(template)
}
beforeDestroy () { beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('uploadProgress') ipcRenderer.removeAllListeners('uploadProgress')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds) 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)
@@ -225,4 +235,4 @@ export default {
background rgba(0,0,0,0.3) background rgba(0,0,0,0.3)
#file-uploader #file-uploader
display none display none
</style> </style>
+337 -261
View File
@@ -22,9 +22,9 @@
<el-button type="primary" round size="mini" @click="openLogSetting">点击设置</el-button> <el-button type="primary" round size="mini" @click="openLogSetting">点击设置</el-button>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label="修改上传快捷键" label="修改快捷键"
> >
<el-button type="primary" round size="mini" @click="keyBindingVisible = true">点击设置</el-button> <el-button type="primary" round size="mini" @click="goShortCutPage">点击设置</el-button>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label="自定义链接格式" label="自定义链接格式"
@@ -36,6 +36,11 @@
> >
<el-button type="primary" round size="mini" @click="proxyVisible = true">点击设置</el-button> <el-button type="primary" round size="mini" @click="proxyVisible = true">点击设置</el-button>
</el-form-item> </el-form-item>
<el-form-item
label="设置Server"
>
<el-button type="primary" round size="mini" @click="serverVisible = true">点击设置</el-button>
</el-form-item>
<el-form-item <el-form-item
label="检查更新" label="检查更新"
> >
@@ -120,30 +125,6 @@
</el-row> </el-row>
</el-col> </el-col>
</el-row> </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 <el-dialog
title="自定义链接格式" title="自定义链接格式"
:visible.sync="customLinkVisible" :visible.sync="customLinkVisible"
@@ -165,7 +146,7 @@
<div class="custom-title"> <div class="custom-title">
用占位符 <b>$fileName</b> 来表示文件名的位置 用占位符 <b>$fileName</b> 来表示文件名的位置
</div> </div>
<el-input <el-input
class="align-center" class="align-center"
v-model="customLink.value" v-model="customLink.value"
:autofocus="true" :autofocus="true"
@@ -195,7 +176,7 @@
<el-form-item <el-form-item
label="代理地址" label="代理地址"
> >
<el-input <el-input
v-model="proxy" v-model="proxy"
:autofocus="true" :autofocus="true"
placeholder="例如:http://127.0.0.1:1080" placeholder="例如:http://127.0.0.1:1080"
@@ -263,34 +244,293 @@
<el-button type="primary" @click="confirmLogLevelSetting" round>确定</el-button> <el-button type="primary" @click="confirmLogLevelSetting" round>确定</el-button>
</span> </span>
</el-dialog> </el-dialog>
<el-dialog
class="server-dialog"
width="60%"
title="设置PicGo-Server"
:visible.sync="serverVisible"
:modal-append-to-body="false"
>
<div class="notice-text">
如果你不知道Server的作用请阅读文档或者不用修改配置
</div>
<el-form
label-position="right"
label-width="120px"
>
<el-form-item
label="是否开启Server"
>
<el-switch
v-model="server.enable"
active-text=""
inactive-text=""
></el-switch>
</el-form-item>
<template v-if="server.enable">
<el-form-item
label="设置监听地址"
>
<el-input
type="input"
v-model="server.host"
placeholder="推荐默认地址:127.0.0.1"
></el-input>
</el-form-item>
<el-form-item
label="设置监听端口"
>
<el-input
type="number"
v-model="server.port"
placeholder="推荐默认端口:36677"
></el-input>
</el-form-item>
</template>
</el-form>
<span slot="footer">
<el-button @click="cancelServerSetting" round>取消</el-button>
<el-button type="primary" @click="confirmServerSetting" round>确定</el-button>
</span>
</el-dialog>
</div> </div>
</template> </template>
<script> <script lang="ts">
import keyDetect from 'utils/key-binding' import keyDetect from '@/utils/key-binding'
import pkg from 'root/package.json' import pkg from 'root/package.json'
import path from 'path' import path from 'path'
const release = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest' import {
ipcRenderer,
remote
} from 'electron'
import { Component, Vue } from 'vue-property-decorator'
import db from '#/datastore'
const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
const releaseUrlBackup = 'https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@latest/package.json'
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest' const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
export default { const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) => void) => {
name: 'picgo-setting', if (!/\$url/.test(value)) {
computed: { return callback(new Error('必须含有$url'))
needUpdate () { } else {
if (this.latestVersion) { return callback()
return this.compareVersion2Update(this.version, this.latestVersion) }
}
let logLevel = db.get('settings.logLevel')
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
@Component({
name: 'picgo-setting'
})
export default class extends Vue {
form: ISettingForm = {
updateHelper: db.get('settings.showUpdateTip'),
showPicBedList: [],
autoStart: db.get('settings.autoStart') || false,
rename: db.get('settings.rename') || false,
autoRename: db.get('settings.autoRename') || false,
uploadNotification: db.get('settings.uploadNotification') || false,
miniWindowOntop: db.get('settings.miniWindowOntop') || false,
logLevel
}
picBed: IPicBedType[] = []
logFileVisible = false
keyBindingVisible = false
customLinkVisible = false
checkUpdateVisible = false
serverVisible = false
proxyVisible = false
customLink = {
value: db.get('settings.customLink') || '$url'
}
shortKey: IShortKeyMap = {
upload: db.get('settings.shortKey.upload')
}
proxy = db.get('picBed.proxy') || ''
rules = {
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
}
logLevel = {
all: '全部-All',
success: '成功-Success',
error: '错误-Error',
info: '普通-Info',
warn: '提醒-Warn',
none: '不记录日志-None'
}
server = db.get('settings.server') || {
port: 36677,
host: '127.0.0.1',
enable: true
}
version = pkg.version
latestVersion = ''
os = ''
get needUpdate () {
if (this.latestVersion) {
return this.compareVersion2Update(this.version, this.latestVersion)
} else {
return false
}
}
created () {
this.os = process.platform
ipcRenderer.send('getPicBeds')
ipcRenderer.on('getPicBeds', this.getPicBeds)
}
getPicBeds (event: Event, picBeds: IPicBedType[]) {
this.picBed = picBeds
this.form.showPicBedList = this.picBed.map(item => {
if (item.visible) {
return item.name
}
}) as string[]
}
openFile (file: string) {
const { app, shell } = remote
const STORE_PATH = app.getPath('userData')
const FILE = path.join(STORE_PATH, `/${file}`)
shell.openItem(FILE)
}
openLogSetting () {
this.logFileVisible = true
}
keyDetect (type: string, event: KeyboardEvent) {
this.shortKey[type] = keyDetect(event).join('+')
}
cancelCustomLink () {
this.customLinkVisible = false
this.customLink.value = db.get('settings.customLink') || '$url'
}
confirmCustomLink () {
// @ts-ignore
this.$refs.customLink.validate((valid: boolean) => {
if (valid) {
db.set('settings.customLink', this.customLink.value)
this.customLinkVisible = false
ipcRenderer.send('updateCustomLink')
} else { } else {
return false return false
} }
})
}
cancelProxy () {
this.proxyVisible = false
this.proxy = db.get('picBed.proxy') || undefined
}
confirmProxy () {
this.proxyVisible = false
this.letPicGoSaveData({
'picBed.proxy': this.proxy
})
const successNotification = new Notification('设置代理', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
} }
}, }
data () { updateHelperChange (val: boolean) {
const customLinkRule = (rule, value, callback) => { db.set('settings.showUpdateTip', val)
if (!/\$url/.test(value)) { }
return callback(new Error('必须含有$url')) handleShowPicBedListChange (val: string[]) {
const list = this.picBed.map(item => {
if (!val.includes(item.name)) {
item.visible = false
} else { } else {
return callback() item.visible = true
}
return item
})
this.letPicGoSaveData({
'picBed.list': list
})
ipcRenderer.send('getPicBeds')
}
handleAutoStartChange (val: boolean) {
db.set('settings.autoStart', val)
ipcRenderer.send('autoStart', val)
}
handleRename (val: boolean) {
this.letPicGoSaveData({
'settings.rename': val
})
}
handleAutoRename (val: boolean) {
this.letPicGoSaveData({
'settings.autoRename': val
})
}
compareVersion2Update (current: string, latest: string) {
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
} }
} }
let logLevel = this.$db.read().get('settings.logLevel').value() return false
}
checkUpdate () {
this.checkUpdateVisible = true
this.$http.get(releaseUrl)
.then(res => {
this.latestVersion = res.data.name
}).catch(async () => {
this.$http.get(releaseUrlBackup)
.then(res => {
this.latestVersion = res.data.version
}).catch(() => {
this.latestVersion = '网络错误暂时无法获取'
})
})
}
confirmCheckVersion () {
if (this.needUpdate) {
remote.shell.openExternal(downloadUrl)
}
this.checkUpdateVisible = false
}
cancelCheckVersion () {
this.checkUpdateVisible = false
}
handleUploadNotification (val: boolean) {
db.set('settings.uploadNotification', val)
}
handleMiniWindowOntop (val: boolean) {
db.set('settings.miniWindowOntop', val)
this.$message.info('需要重启生效')
}
confirmLogLevelSetting () {
if (this.form.logLevel.length === 0) {
return this.$message.error('请选择日志记录等级')
}
this.letPicGoSaveData({
'settings.logLevel': this.form.logLevel
})
const successNotification = new Notification('设置日志', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
this.logFileVisible = false
}
cancelLogLevelSetting () {
this.logFileVisible = false
let logLevel = db.get('settings.logLevel')
if (!Array.isArray(logLevel)) { if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) { if (logLevel && logLevel.length > 0) {
logLevel = [logLevel] logLevel = [logLevel]
@@ -298,228 +538,57 @@ export default {
logLevel = ['all'] logLevel = ['all']
} }
} }
return { this.form.logLevel = logLevel
form: { }
updateHelper: this.$db.read().get('settings.showUpdateTip').value(), confirmServerSetting () {
showPicBedList: [], this.letPicGoSaveData({
autoStart: this.$db.read().get('settings.autoStart').value() || false, 'settings.server': this.server
rename: this.$db.read().get('settings.rename').value() || false, })
autoRename: this.$db.read().get('settings.autoRename').value() || false, const successNotification = new Notification('设置PicGo-Server', {
uploadNotification: this.$db.read().get('settings.uploadNotification').value() || false, body: '设置成功'
miniWindowOntop: this.$db.read().get('settings.miniWindowOntop').value() || false, })
logLevel successNotification.onclick = () => {
}, return true
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: ''
} }
}, this.serverVisible = false
created () { ipcRenderer.send('updateServer')
this.os = process.platform }
this.$electron.ipcRenderer.send('getPicBeds') cancelServerSetting () {
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds) this.serverVisible = false
}, this.server = db.get('settings.server') || {
methods: { port: 36677,
getPicBeds (event, picBeds) { host: '127.0.0.1',
this.picBed = picBeds enable: true
this.form.showPicBedList = this.picBed.map(item => { }
if (item.visible) { }
return item.name handleLevelDisabled (val: string) {
} let currentLevel = val
}) let flagLevel
}, let result = this.form.logLevel.some(item => {
openFile (file) { if (item === 'all' || item === 'none') {
const { app, shell } = this.$electron.remote flagLevel = item
const STORE_PATH = app.getPath('userData') }
const FILE = path.join(STORE_PATH, `/${file}`) return (item === 'all' || item === 'none')
shell.openItem(FILE) })
}, if (result) {
openLogSetting () { if (currentLevel !== flagLevel) {
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 return true
} }
}, } else if (this.form.logLevel.length > 0) {
updateHelperChange (val) { if (val === 'all' || val === 'none') {
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 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设置')
} }
}, return false
}
goConfigPage () {
remote.shell.openExternal('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
}
goShortCutPage () {
this.$router.push('shortKey')
}
beforeDestroy () { beforeDestroy () {
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds) ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
} }
} }
</script> </script>
@@ -542,7 +611,7 @@ export default {
overflow-x hidden overflow-x hidden
.setting-list .setting-list
.el-form .el-form
label label
line-height 32px line-height 32px
padding-bottom 0 padding-bottom 0
color #eee color #eee
@@ -576,4 +645,11 @@ export default {
margin-left 0 margin-left 0
.confirm-button .confirm-button
width 100% width 100%
</style> .server-dialog
.notice-text
color: #49B1F5
.el-dialog__body
padding-top: 0
.el-form-item
margin-bottom: 10px
</style>
+297 -254
View File
@@ -17,7 +17,7 @@
<div class="plugin-item" :class="{ 'darwin': os === 'darwin' }"> <div class="plugin-item" :class="{ 'darwin': os === 'darwin' }">
<div class="cli-only-badge" v-if="!item.gui" title="CLI only">CLI</div> <div class="cli-only-badge" v-if="!item.gui" title="CLI only">CLI</div>
<img class="plugin-item__logo" :src="item.logo" <img class="plugin-item__logo" :src="item.logo"
onerror="this.src='static/roundLogo.png'" :onerror="defaultLogo"
> >
<div <div
class="plugin-item__content" class="plugin-item__content"
@@ -93,58 +93,76 @@
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script> <script lang="ts">
import ConfigForm from '@/components/ConfigForm' import {
Component,
Vue,
Watch
} from 'vue-property-decorator'
import ConfigForm from '@/components/ConfigForm.vue'
import { debounce } from 'lodash' import { debounce } from 'lodash'
export default { import {
ipcRenderer,
remote,
IpcRendererEvent
} from 'electron'
const { Menu } = remote
@Component({
name: 'plugin', name: 'plugin',
components: { components: {
ConfigForm ConfigForm
}, }
data () { })
return { export default class extends Vue {
searchText: '', searchText = ''
pluginList: [], pluginList: IPicGoPlugin[] = []
menu: null, menu: Electron.Menu | null = null
config: [], config: any[] = []
currentType: '', currentType = ''
configName: '', configName = ''
dialogVisible: false, dialogVisible = false
pluginNameList: [], pluginNameList: string[] = []
loading: true, loading = true
needReload: false, needReload = false
id: '', id = ''
os: '' os = ''
defaultLogo: string = 'this.src="https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@dev/public/roundLogo.png"'
get npmSearchText () {
return this.searchText.match('picgo-plugin-')
? this.searchText
: this.searchText !== ''
? `picgo-plugin-${this.searchText}`
: this.searchText
}
@Watch('npmSearchText')
onNpmSearchTextChange (val: string) {
if (val) {
this.loading = true
this.pluginList = []
this.getSearchResult(val)
} else {
this.getPluginList()
} }
}, }
computed: { @Watch('dialogVisible')
npmSearchText () { onDialogVisible (val: boolean) {
return this.searchText.match('picgo-plugin-') if (val) {
? this.searchText // @ts-ignore
: this.searchText !== '' document.querySelector('.main-content.el-row').style.zIndex = 101
? `picgo-plugin-${this.searchText}` } else {
: this.searchText // @ts-ignore
document.querySelector('.main-content.el-row').style.zIndex = 10
} }
}, }
watch: {
npmSearchText (val) {
if (val) {
this.loading = true
this.pluginList = []
this.getSearchResult(val)
} else {
this.getPluginList()
}
}
},
created () { created () {
this.os = process.platform this.os = process.platform
this.$electron.ipcRenderer.on('pluginList', (evt, list) => { ipcRenderer.on('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => {
this.pluginList = list this.pluginList = list
this.pluginNameList = list.map(item => item.name) this.pluginNameList = list.map(item => item.name)
this.loading = false this.loading = false
}) })
this.$electron.ipcRenderer.on('installSuccess', (evt, plugin) => { ipcRenderer.on('installSuccess', (evt: IpcRendererEvent, plugin: string) => {
this.loading = false this.loading = false
this.pluginList.forEach(item => { this.pluginList.forEach(item => {
if (item.name === plugin) { if (item.name === plugin) {
@@ -153,7 +171,7 @@ export default {
} }
}) })
}) })
this.$electron.ipcRenderer.on('updateSuccess', (evt, plugin) => { ipcRenderer.on('updateSuccess', (evt: IpcRendererEvent, plugin: string) => {
this.loading = false this.loading = false
this.pluginList.forEach(item => { this.pluginList.forEach(item => {
if (item.name === plugin) { if (item.name === plugin) {
@@ -165,7 +183,7 @@ export default {
this.handleReload() this.handleReload()
this.getPluginList() this.getPluginList()
}) })
this.$electron.ipcRenderer.on('uninstallSuccess', (evt, plugin) => { ipcRenderer.on('uninstallSuccess', (evt: IpcRendererEvent, plugin: string) => {
this.loading = false this.loading = false
this.pluginList = this.pluginList.filter(item => { this.pluginList = this.pluginList.filter(item => {
if (item.name === plugin) { // restore Uploader & Transformer after uninstalling if (item.name === plugin) { // restore Uploader & Transformer after uninstalling
@@ -183,242 +201,267 @@ export default {
}) })
this.getPluginList() this.getPluginList()
this.getSearchResult = debounce(this.getSearchResult, 50) this.getSearchResult = debounce(this.getSearchResult, 50)
this.needReload = this.$db.read().get('needReload').value() this.needReload = this.$db.get('needReload')
}, }
methods: { buildContextMenu (plugin: IPicGoPlugin) {
buildContextMenu (plugin) { const _this = this
const _this = this let menu = [{
let menu = [{ label: '启用插件',
label: '启用插件', enabled: !plugin.enabled,
enabled: !plugin.enabled, click () {
click () { _this.letPicGoSaveData({
_this.$db.read().set(`picgoPlugins.picgo-plugin-${plugin.name}`, true).write() [`picgoPlugins.picgo-plugin-${plugin.name}`]: true
plugin.enabled = true })
_this.getPicBeds() plugin.enabled = true
_this.getPicBeds()
}
}, {
label: '禁用插件',
enabled: plugin.enabled,
click () {
_this.letPicGoSaveData({
[`picgoPlugins.picgo-plugin-${plugin.name}`]: false
})
plugin.enabled = false
_this.getPicBeds()
if (plugin.config.transformer.name) {
_this.handleRestoreState('transformer', plugin.config.transformer.name)
} }
}, { if (plugin.config.uploader.name) {
label: '禁用插件', _this.handleRestoreState('uploader', plugin.config.uploader.name)
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 label: '卸载插件',
if (plugin.config.transformer.name) { click () {
let currentTransformer = this.$db.read().get('picBed.transformer').value() || 'path' _this.uninstallPlugin(plugin.name)
let pluginTransformer = plugin.config.transformer.name }
}, {
label: '更新插件',
click () {
_this.updatePlugin(plugin.name)
}
}]
for (let i in plugin.config) {
if (plugin.config[i].config.length > 0) {
const obj = { const obj = {
label: `${currentTransformer === pluginTransformer ? '禁用' : '启用'}transformer - ${plugin.config.transformer.name}`, label: `配置${i} - ${plugin.config[i].name}`,
click () { click () {
_this.toggleTransformer(plugin.config.transformer.name) _this.currentType = i
_this.configName = plugin.config[i].name
_this.dialogVisible = true
_this.config = plugin.config[i].config
} }
} }
menu.push(obj) menu.push(obj)
} }
}
// plugin custom menus // handle transformer
if (plugin.guiMenu) { if (plugin.config.transformer.name) {
let currentTransformer = this.$db.get('picBed.transformer') || 'path'
let pluginTransformer = plugin.config.transformer.name
const obj = {
label: `${currentTransformer === pluginTransformer ? '禁用' : '启用'}transformer - ${plugin.config.transformer.name}`,
click () {
_this.toggleTransformer(plugin.config.transformer.name)
}
}
menu.push(obj)
}
// plugin custom menus
if (plugin.guiMenu) {
menu.push({
// @ts-ignore
type: 'separator'
})
for (let i of plugin.guiMenu) {
menu.push({ menu.push({
type: 'separator' label: i.label,
click () {
ipcRenderer.send('pluginActions', plugin.name, i.label)
}
}) })
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 = Menu.buildFromTemplate(menu)
this.menu.popup(this.$electron.remote.getCurrentWindow()) this.menu.popup()
}, }
getPluginList () { getPluginList () {
this.$electron.ipcRenderer.send('getPluginList') ipcRenderer.send('getPluginList')
}, }
getPicBeds () { getPicBeds () {
this.$electron.ipcRenderer.send('getPicBeds') ipcRenderer.send('getPicBeds')
}, }
installPlugin (item) { installPlugin (item: IPicGoPlugin) {
if (!item.gui) { if (!item.gui) {
this.$confirm('该插件未对可视化界面进行优化, 是否继续安装?', '提示', { this.$confirm('该插件未对可视化界面进行优化, 是否继续安装?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
item.ing = true item.ing = true
this.$electron.ipcRenderer.send('installPlugin', item.name) ipcRenderer.send('installPlugin', item.name)
}).catch(() => { }).catch(() => {
console.log('Install canceled') console.log('Install canceled')
}) })
} else { } else {
item.ing = true
ipcRenderer.send('installPlugin', item.name)
}
}
uninstallPlugin (val: string) {
this.pluginList.forEach(item => {
if (item.name === val) {
item.ing = true item.ing = true
this.$electron.ipcRenderer.send('installPlugin', item.name)
} }
}, })
uninstallPlugin (val) { ipcRenderer.send('uninstallPlugin', val)
this.pluginList.forEach(item => { }
if (item.name === val) { updatePlugin (val: string) {
item.ing = true this.pluginList.forEach(item => {
} if (item.name === val) {
item.ing = true
}
})
ipcRenderer.send('updatePlugin', val)
}
reloadApp () {
remote.app.relaunch()
remote.app.exit(0)
}
handleReload () {
this.$db.set('needReload', true)
this.needReload = true
const successNotification = new Notification('更新成功', {
body: '请点击此通知重启应用以生效'
})
successNotification.onclick = () => {
this.reloadApp()
}
}
cleanSearch () {
this.searchText = ''
}
toggleTransformer (transformer: string) {
let currentTransformer = this.$db.get('picBed.transformer') || 'path'
if (currentTransformer === transformer) {
this.letPicGoSaveData({
'picBed.transformer': 'path'
}) })
this.$electron.ipcRenderer.send('uninstallPlugin', val) } else {
}, this.letPicGoSaveData({
updatePlugin (val) { 'picBed.transformer': transformer
this.pluginList.forEach(item => {
if (item.name === val) {
item.ing = true
}
}) })
this.$electron.ipcRenderer.send('updatePlugin', val) }
}, }
reloadApp () { async handleConfirmConfig () {
this.$electron.remote.app.relaunch() // @ts-ignore
this.$electron.remote.app.exit(0) const result = await this.$refs.configForm.validate()
}, if (result !== false) {
handleReload () { switch (this.currentType) {
this.$db.read().set('needReload', true).write() case 'plugin':
this.needReload = true this.letPicGoSaveData({
const successNotification = new window.Notification('更新成功', { [`picgo-plugin-${this.configName}`]: result
body: '请点击此通知重启应用以生效' })
break
case 'uploader':
this.letPicGoSaveData({
[`picBed.${this.configName}`]: result
})
break
case 'transformer':
this.letPicGoSaveData({
[`transformer.${this.configName}`]: result
})
break
}
const successNotification = new Notification('设置结果', {
body: '设置成功'
}) })
successNotification.onclick = () => { successNotification.onclick = () => {
this.reloadApp() return true
} }
}, this.dialogVisible = false
cleanSearch () { this.getPluginList()
this.searchText = '' }
}, }
toggleTransformer (transformer) { getSearchResult (val: string) {
let currentTransformer = this.$db.read().get('picBed.transformer').value() || 'path' // this.$http.get(`https://api.npms.io/v2/search?q=${val}`)
if (currentTransformer === transformer) { this.$http.get(`https://registry.npmjs.com/-/v1/search?text=${val}`)
this.$db.read().set('picBed.transformer', 'path').write() .then((res: INPMSearchResult) => {
} else { this.pluginList = res.data.objects
this.$db.read().set('picBed.transformer', transformer).write() .filter((item:INPMSearchResultObject) => {
} return item.package.name.includes('picgo-plugin-')
}, })
async handleConfirmConfig () { .map((item: INPMSearchResultObject) => {
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) return this.handleSearchResult(item)
}) })
this.loading = false this.loading = false
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err)
this.loading = false this.loading = false
}) })
}, }
handleSearchResult (item) { handleSearchResult (item: INPMSearchResultObject) {
const name = item.package.name.replace(/picgo-plugin-/, '') const name = item.package.name.replace(/picgo-plugin-/, '')
let gui = false let gui = false
if (item.package.keywords && item.package.keywords.length > 0) { if (item.package.keywords && item.package.keywords.length > 0) {
if (item.package.keywords.includes('picgo-gui-plugin')) { if (item.package.keywords.includes('picgo-gui-plugin')) {
gui = true 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')
} }
}, 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: string, name: string) {
if (item === 'uploader') {
const current = this.$db.get('picBed.current')
if (current === name) {
this.letPicGoSaveData({
'picBed.current': 'smms'
})
}
}
if (item === 'transformer') {
const current = this.$db.get('picBed.transformer')
if (current === name) {
this.letPicGoSaveData({
'picBed.transformer': 'path'
})
}
}
}
openHomepage (url: string) {
if (url) {
remote.shell.openExternal(url)
}
}
goAwesomeList () {
remote.shell.openExternal('https://github.com/PicGo/Awesome-PicGo')
}
letPicGoSaveData (data: IObj) {
ipcRenderer.send('picgoSaveData', data)
}
beforeDestroy () { beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('pluginList') ipcRenderer.removeAllListeners('pluginList')
this.$electron.ipcRenderer.removeAllListeners('installSuccess') ipcRenderer.removeAllListeners('installSuccess')
this.$electron.ipcRenderer.removeAllListeners('uninstallSuccess') ipcRenderer.removeAllListeners('uninstallSuccess')
this.$electron.ipcRenderer.removeAllListeners('updateSuccess') ipcRenderer.removeAllListeners('updateSuccess')
} }
} }
</script> </script>
@@ -562,4 +605,4 @@ $darwinBg = #172426
padding 10px 0 padding 10px 0
&.cut-width &.cut-width
width calc(100% - 48px) width calc(100% - 48px)
</style> </style>
+23 -22
View File
@@ -6,7 +6,7 @@
<el-form-item <el-form-item
label="文件改名" label="文件改名"
> >
<el-input <el-input
v-model="fileName" v-model="fileName"
size="small" size="small"
@keyup.enter.native="confirmName" @keyup.enter.native="confirmName"
@@ -21,33 +21,34 @@
</el-row> </el-row>
</div> </div>
</template> </template>
<script> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/mixin' import mixin from '@/utils/mixin'
export default { import {
ipcRenderer,
IpcRendererEvent
} from 'electron'
@Component({
name: 'rename-page', name: 'rename-page',
mixins: [mixin], mixins: [mixin]
data () { })
return { export default class extends Vue {
fileName: '', fileName: string = ''
id: null id: string | null = null
}
},
created () { created () {
this.$electron.ipcRenderer.on('rename', (event, name, id) => { ipcRenderer.on('rename', (event: IpcRendererEvent, name: string, id: string) => {
this.fileName = name this.fileName = name
this.id = id this.id = id
}) })
}, }
methods: { confirmName () {
confirmName () { ipcRenderer.send(`rename${this.id}`, this.fileName)
this.$electron.ipcRenderer.send(`rename${this.id}`, this.fileName) }
}, cancel () {
cancel () { ipcRenderer.send(`rename${this.id}`, null)
this.$electron.ipcRenderer.send(`rename${this.id}`, null) }
}
},
beforeDestroy () { beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('rename') ipcRenderer.removeAllListeners('rename')
} }
} }
</script> </script>
@@ -58,4 +59,4 @@ export default {
float right float right
.el-form-item__label .el-form-item__label
color #ddd color #ddd
</style> </style>
+196
View File
@@ -0,0 +1,196 @@
<template>
<div id="shortcut-page">
<div class="view-title">
快捷键设置
</div>
<el-row>
<el-col :span="20" :offset="2">
<el-table
:data="list"
size="mini"
>
<el-table-column
label="快捷键名称"
>
<template slot-scope="scope">
{{ scope.row.label ? scope.row.label : scope.row.name }}
</template>
</el-table-column>
<el-table-column
width="160px"
label="快捷键绑定"
prop="key"
>
</el-table-column>
<el-table-column
label="状态"
>
<template slot-scope="scope">
<el-tag
size="mini"
:type="scope.row.enable ? 'success' : 'danger'"
>
{{ scope.row.enable ? '已启用' : '已禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column
label="来源"
width="100px"
>
<template slot-scope="scope">
{{ calcOriginShowName(scope.row.from) }}
</template>
</el-table-column>
<el-table-column
label="操作"
>
<template slot-scope="scope">
<el-button
@click="toggleEnable(scope.row)"
size="mini"
:class="{
disabled: scope.row.enable
}"
type="text">
{{ scope.row.enable ? '禁用' : '启用' }}
</el-button>
<el-button
class="edit"
size="mini"
@click="openKeyBindingDialog(scope.row, scope.$index)"
type="text">
编辑
</el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
<el-dialog
title="修改上传快捷键"
:visible.sync="keyBindingVisible"
:modal-append-to-body="false"
>
<el-form
label-position="top"
label-width="80px"
>
<el-form-item
label="快捷上传"
>
<el-input
class="align-center"
@keydown.native.prevent="keyDetect($event)"
v-model="shortKey"
: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>
</div>
</template>
<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'
import keyDetect from '@/utils/key-binding'
import { ipcRenderer, IpcRendererEvent } from 'electron'
@Component({
name: 'shortkey-page'
})
export default class extends Vue {
list: IShortKeyConfig[] = []
keyBindingVisible = false
command = ''
shortKey = ''
currentIndex = 0
created () {
const shortKeyConfig = this.$db.get('settings.shortKey') as IShortKeyConfigs
this.list = Object.keys(shortKeyConfig).map(item => {
return {
...shortKeyConfig[item],
from: this.calcOrigin(item)
}
})
}
@Watch('keyBindingVisible')
onKeyBindingVisibleChange (val: boolean) {
ipcRenderer.send('toggleShortKeyModifiedMode', val)
}
calcOrigin (item: string) {
const [origin] = item.split(':')
return origin
}
calcOriginShowName (item: string) {
return item.replace('picgo-plugin-', '')
}
toggleEnable (item: IShortKeyConfig) {
const status = !item.enable
item.enable = status
// this.$db.set(`settings.shortKey.${item.name}.enable`, status)
ipcRenderer.send('bindOrUnbindShortKey', item, item.from)
}
keyDetect (event: KeyboardEvent) {
this.shortKey = keyDetect(event).join('+')
}
openKeyBindingDialog (config: IShortKeyConfig, index: number) {
this.command = `${config.from}:${config.name}`
this.shortKey = this.$db.get(`settings.shortKey.${this.command}.key`)
this.currentIndex = index
this.keyBindingVisible = true
}
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = this.$db.get(`settings.shortKey.${this.command}.key`)
}
confirmKeyBinding () {
const oldKey = this.$db.get(`settings.shortKey.${this.command}.key`)
// this.$db.set(`settings.shortKey.${this.command}.key`, this.shortKey)
// const newKey = this.$db.get(`settings.shortKey.${this.command}`)
const config = Object.assign({}, this.list[this.currentIndex])
config.key = this.shortKey
ipcRenderer.send('updateShortKey', config, oldKey, config.from)
ipcRenderer.once('updateShortKeyResponse', (evt: IpcRendererEvent, result) => {
if (result) {
this.keyBindingVisible = false
this.list[this.currentIndex].key = this.shortKey
}
})
}
beforeDestroy () {
ipcRenderer.send('toggleShortKeyModifiedMode', false)
}
}
</script>
<style lang='stylus'>
#shortcut-page
.el-dialog__body
padding 10px 20px
.el-form-item
margin-bottom 0
.el-button
&.disabled
color: #F56C6C
&.edit
color: #67C23A
.el-table
background-color: transparent
color #ddd
thead
color #bbb
th,tr
background-color: transparent
&__body
tr.el-table__row--striped
td
background transparent
&--enable-row-hover
.el-table__body
tr:hover
&>td
background #333
</style>
+83 -84
View File
@@ -25,92 +25,91 @@
</div> </div>
</template> </template>
<script> <script lang="ts">
import mixin from '@/utils/mixin' import { Component, Vue } from 'vue-property-decorator'
import pasteTemplate from '~/main/utils/pasteTemplate' import mixin from '@/utils/mixin'
export default { import pasteTemplate from '#/utils/pasteTemplate'
name: 'tray-page', import { ipcRenderer, clipboard } from 'electron'
mixins: [mixin], @Component({
data () { name: 'tray-page',
return { mixins: [mixin]
files: [], })
notification: { export default class extends Vue {
title: '复制链接成功', files = []
body: '', notification = {
icon: '' title: '复制链接成功',
}, body: '',
clipboardFiles: [], icon: ''
uploadFlag: false }
} clipboardFiles: ImgInfo[] = []
}, uploadFlag = false
computed: { get reverseList () {
reverseList () { return this.files.slice().reverse()
return this.files.slice().reverse() }
} getData () {
}, // @ts-ignore
mounted () { this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
this.disableDragFile() }
this.getData() copyTheLink (item: ImgInfo) {
this.$electron.ipcRenderer.on('dragFiles', (event, files) => { this.notification.body = item.imgUrl!
files.forEach(item => { this.notification.icon = item.imgUrl!
this.$db.read().get('uploaded').insert(item).write() const myNotification = new Notification(this.notification.title, this.notification)
}) const pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value() clipboard.writeText(pasteTemplate(pasteStyle, item))
}) myNotification.onclick = () => {
this.$electron.ipcRenderer.on('clipboardFiles', (event, files) => { return true
this.clipboardFiles = files
})
this.$electron.ipcRenderer.on('uploadFiles', (event) => {
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.getData()
})
},
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('dragFiles')
this.$electron.ipcRenderer.removeAllListeners('clipboardFiles')
this.$electron.ipcRenderer.removeAllListeners('uploadClipboardFiles')
this.$electron.ipcRenderer.removeAllListeners('updateFiles')
},
methods: {
getData () {
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
},
copyTheLink (item) {
this.notification.body = item.imgUrl
this.notification.icon = item.imgUrl
const myNotification = new window.Notification(this.notification.title, this.notification)
const pasteStyle = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
this.$electron.clipboard.writeText(pasteTemplate(pasteStyle, item))
myNotification.onclick = () => {
return true
}
},
calcHeight (width, height) {
return height * 160 / width
},
disableDragFile () {
window.addEventListener('dragover', (e) => {
e = e || event
e.preventDefault()
}, false)
window.addEventListener('drop', (e) => {
e = e || event
e.preventDefault()
}, false)
},
uploadClipboardFiles () {
if (this.uploadFlag) {
return
}
this.uploadFlag = true
this.$electron.ipcRenderer.send('uploadClipboardFiles')
}
} }
} }
calcHeight (width: number, height: number): number {
return height * 160 / width
}
disableDragFile () {
window.addEventListener('dragover', (e) => {
e = e || event
e.preventDefault()
}, false)
window.addEventListener('drop', (e) => {
e = e || event
e.preventDefault()
}, false)
}
uploadClipboardFiles () {
if (this.uploadFlag) {
return
}
this.uploadFlag = true
ipcRenderer.send('uploadClipboardFiles')
}
mounted () {
this.disableDragFile()
this.getData()
ipcRenderer.on('dragFiles', (event: Event, files: string[]) => {
files.forEach(item => {
this.$db.insert('uploaded', item)
})
// @ts-ignore
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
})
ipcRenderer.on('clipboardFiles', (event: Event, files: ImgInfo[]) => {
this.clipboardFiles = files
})
ipcRenderer.on('uploadFiles', (event: Event) => {
// @ts-ignore
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
console.log(this.files)
this.uploadFlag = false
})
ipcRenderer.on('updateFiles', (event: Event) => {
this.getData()
})
}
beforeDestroy () {
ipcRenderer.removeAllListeners('dragFiles')
ipcRenderer.removeAllListeners('clipboardFiles')
ipcRenderer.removeAllListeners('uploadClipboardFiles')
ipcRenderer.removeAllListeners('updateFiles')
}
}
</script> </script>
<style lang="stylus"> <style lang="stylus">
+111 -104
View File
@@ -20,12 +20,12 @@
<input type="file" id="file-uploader" @change="onChange" multiple> <input type="file" id="file-uploader" @change="onChange" multiple>
</div> </div>
</div> </div>
<el-progress <el-progress
:percentage="progress" :percentage="progress"
:show-text="false" :show-text="false"
class="upload-progress" class="upload-progress"
:class="{ 'show': showProgress }" :class="{ 'show': showProgress }"
:status="showError ? 'exception' : 'text'" :status="showError ? 'exception' : undefined"
></el-progress> ></el-progress>
<div class="paste-style"> <div class="paste-style">
<div class="el-col-16"> <div class="el-col-16">
@@ -55,23 +55,28 @@
</el-row> </el-row>
</div> </div>
</template> </template>
<script> <script lang="ts">
export default { import { Component, Vue, Watch } from 'vue-property-decorator'
name: 'upload', import {
data () { ipcRenderer,
return { IpcRendererEvent,
dragover: false, remote
progress: 0, } from 'electron'
showProgress: false, const { Menu } = remote
showError: false, @Component({
pasteStyle: '', name: 'upload'
picBed: [], })
picBedName: '', export default class extends Vue {
menu: null dragover = false
} progress = 0
}, showProgress = false
showError = false
pasteStyle = ''
picBed: IPicBedType[] = []
picBedName = ''
menu: Electron.Menu | null= null
mounted () { mounted () {
this.$electron.ipcRenderer.on('uploadProgress', (event, progress) => { ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, progress: number) => {
if (progress !== -1) { if (progress !== -1) {
this.showProgress = true this.showProgress = true
this.progress = progress this.progress = progress
@@ -82,94 +87,94 @@ export default {
}) })
this.getPasteStyle() this.getPasteStyle()
this.getDefaultPicBed() this.getDefaultPicBed()
this.$electron.ipcRenderer.on('syncPicBed', () => { ipcRenderer.on('syncPicBed', () => {
this.getDefaultPicBed() this.getDefaultPicBed()
}) })
this.$electron.ipcRenderer.send('getPicBeds') ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds) ipcRenderer.on('getPicBeds', this.getPicBeds)
}, }
watch: { @Watch('progress')
progress (val) { onProgressChange (val: number) {
if (val === 100) { if (val === 100) {
setTimeout(() => { setTimeout(() => {
this.showProgress = false this.showProgress = false
this.showError = false this.showError = false
}, 1000) }, 1000)
setTimeout(() => { setTimeout(() => {
this.progress = 0 this.progress = 0
}, 1200) }, 1200)
}
} }
}, }
beforeDestroy () { beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('uploadProgress') ipcRenderer.removeAllListeners('uploadProgress')
this.$electron.ipcRenderer.removeAllListeners('syncPicBed') ipcRenderer.removeAllListeners('syncPicBed')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds) ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}, }
methods: { onDrop (e: DragEvent) {
onDrop (e) { this.dragover = false
this.dragover = false this.ipcSendFiles(e.dataTransfer!.files)
this.ipcSendFiles(e.dataTransfer.files) }
}, openUplodWindow () {
openUplodWindow () { document.getElementById('file-uploader')!.click()
document.getElementById('file-uploader').click() }
}, onChange (e: any) {
onChange (e) { this.ipcSendFiles(e.target.files);
this.ipcSendFiles(e.target.files) (document.getElementById('file-uploader') as HTMLInputElement).value = ''
document.getElementById('file-uploader').value = '' }
}, ipcSendFiles (files: FileList) {
ipcSendFiles (files) { let sendFiles: IFileWithPath[] = []
let sendFiles = [] Array.from(files).forEach((item, index) => {
Array.from(files).forEach((item, index) => { let obj = {
let obj = { name: item.name,
name: item.name, path: item.path
path: item.path }
sendFiles.push(obj)
})
ipcRenderer.send('uploadChoosedFiles', sendFiles)
}
getPasteStyle () {
this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
}
handlePasteStyleChange (val: string) {
this.$db.set('settings.pasteStyle', val)
}
uploadClipboardFiles () {
ipcRenderer.send('uploadClipboardFilesFromUploadPage')
}
getDefaultPicBed () {
const current: string = this.$db.get('picBed.current')
this.picBed.forEach(item => {
if (item.type === current) {
this.picBedName = item.name
}
})
}
getPicBeds (event: Event, picBeds: IPicBedType[]) {
this.picBed = picBeds
this.getDefaultPicBed()
}
handleChangePicBed () {
this.buildMenu()
// this.menu.popup(remote.getCurrentWindow())
this.menu!.popup()
}
buildMenu () {
const _this = this
const submenu = this.picBed.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: this.$db.get('picBed.current') === item.type,
click () {
_this.letPicGoSaveData({
'picBed.current': item.type
})
ipcRenderer.send('syncPicBed')
} }
sendFiles.push(obj) }
}) })
this.$electron.ipcRenderer.send('uploadChoosedFiles', sendFiles) // @ts-ignore
}, this.menu = Menu.buildFromTemplate(submenu)
getPasteStyle () {
this.pasteStyle = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
},
handlePasteStyleChange (val) {
this.$db.read().set('settings.pasteStyle', val)
.write()
},
uploadClipboardFiles () {
this.$electron.ipcRenderer.send('uploadClipboardFilesFromUploadPage')
},
getDefaultPicBed () {
const current = this.$db.read().get('picBed.current').value()
this.picBed.forEach(item => {
if (item.type === current) {
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)
}
} }
} }
</script> </script>
@@ -236,4 +241,6 @@ export default {
border-radius 0 14px 14px 0 border-radius 0 14px 14px 0
.paste-upload .paste-upload
width 100% width 100%
</style> .el-icon-caret-bottom
cursor pointer
</style>
+43 -39
View File
@@ -5,7 +5,7 @@
<div class="view-title"> <div class="view-title">
阿里云OSS设置 阿里云OSS设置
</div> </div>
<el-form <el-form
ref="aliyun" ref="aliyun"
label-position="right" label-position="right"
label-width="120px" label-width="120px"
@@ -48,6 +48,11 @@
> >
<el-input v-model="form.path" @keyup.native.enter="confirm" placeholder="例如img/"></el-input> <el-input v-model="form.path" @keyup.native.enter="confirm" placeholder="例如img/"></el-input>
</el-form-item> </el-form-item>
<el-form-item
label="设定网址后缀"
>
<el-input v-model="form.options" @keyup.native.enter="confirm" placeholder="例如?x-oss-process=xxx"></el-input>
</el-form-item>
<el-form-item <el-form-item
label="设定自定义域名" label="设定自定义域名"
> >
@@ -64,54 +69,53 @@
</el-row> </el-row>
</div> </div>
</template> </template>
<script> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/ConfirmButtonMixin' import mixin from '@/utils/ConfirmButtonMixin'
export default { @Component({
mixins: [mixin],
name: 'aliyun', name: 'aliyun',
data () { mixins: [mixin]
return { })
form: { export default class extends Vue {
accessKeyId: '', form: IAliYunConfig = {
accessKeySecret: '', accessKeyId: '',
bucket: '', accessKeySecret: '',
area: '', bucket: '',
path: '', area: '',
customUrl: '' path: '',
} customUrl: '',
} options: ''
}, }
created () { created () {
const config = this.$db.get('picBed.aliyun').value() const config = this.$db.get('picBed.aliyun') as IAliYunConfig
if (config) { if (config) {
for (let i in config) { this.form = Object.assign({}, config)
this.form[i] = config[i]
}
} }
}, }
methods: { confirm () {
confirm () { // @ts-ignore
this.$refs.aliyun.validate((valid) => { this.$refs.aliyun.validate((valid) => {
if (valid) { if (valid) {
this.$db.set('picBed.aliyun', this.form).write() this.letPicGoSaveData({
const successNotification = new window.Notification('设置结果', { 'picBed.aliyun': this.form
body: '设置成功' })
}) const successNotification = new window.Notification('设置结果', {
successNotification.onclick = () => { body: '设置成功'
return true })
} successNotification.onclick = () => {
} else { return true
return false
} }
}) } else {
} return false
}
})
} }
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
#aliyun-view #aliyun-view
.el-form .el-form
label label
line-height 22px line-height 22px
padding-bottom 0 padding-bottom 0
color #eee color #eee
@@ -122,7 +126,7 @@ export default {
.el-radio-group .el-radio-group
width 100% width 100%
label label
width 25% width 25%
.el-radio-button__inner .el-radio-button__inner
width 100% width 100%
.el-radio-button:first-child .el-radio-button:first-child
@@ -146,4 +150,4 @@ export default {
transition .2s color ease-in-out transition .2s color ease-in-out
&:hover &:hover
color #409EFF color #409EFF
</style> </style>
+35 -37
View File
@@ -5,7 +5,7 @@
<div class="view-title"> <div class="view-title">
GitHub设置 GitHub设置
</div> </div>
<el-form <el-form
ref="github" ref="github"
label-position="right" label-position="right"
label-width="120px" label-width="120px"
@@ -56,53 +56,51 @@
</el-row> </el-row>
</div> </div>
</template> </template>
<script> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/ConfirmButtonMixin' import mixin from '@/utils/ConfirmButtonMixin'
export default { @Component({
name: 'github', name: 'github',
mixins: [mixin], mixins: [mixin]
data () { })
return { export default class extends Vue {
form: { form: IGitHubConfig = {
repo: '', repo: '',
token: '', token: '',
path: '', path: '',
customUrl: '', customUrl: '',
branch: '' branch: ''
} }
}
},
created () { created () {
const config = this.$db.get('picBed.github').value() const config = this.$db.get('picBed.github') as IGitHubConfig
if (config) { if (config) {
for (let i in config) { this.form = Object.assign({}, config)
this.form[i] = config[i]
}
} }
}, }
methods: { confirm () {
confirm () { // @ts-ignore
this.$refs.github.validate((valid) => { this.$refs.github.validate((valid) => {
if (valid) { if (valid) {
this.$db.set('picBed.github', this.form).write() this.letPicGoSaveData({
const successNotification = new window.Notification('设置结果', { 'picBed.github': this.form
body: '设置成功' })
}) const successNotification = new Notification('设置结果', {
successNotification.onclick = () => { body: '设置成功'
return true })
} successNotification.onclick = () => {
} else { return true
return false
} }
}) } else {
} return false
}
})
} }
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
#github-view #github-view
.el-form .el-form
label label
line-height 22px line-height 22px
padding-bottom 0 padding-bottom 0
color #eee color #eee
@@ -111,7 +109,7 @@ export default {
.el-radio-group .el-radio-group
width 100% width 100%
label label
width 25% width 25%
.el-radio-button__inner .el-radio-button__inner
width 100% width 100%
.el-radio-button:first-child .el-radio-button:first-child
+33 -35
View File
@@ -5,7 +5,7 @@
<div class="view-title"> <div class="view-title">
Imgur图床设置 Imgur图床设置
</div> </div>
<el-form <el-form
ref="imgur" ref="imgur"
label-position="right" label-position="right"
label-width="120px" label-width="120px"
@@ -36,50 +36,48 @@
</el-row> </el-row>
</div> </div>
</template> </template>
<script> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/ConfirmButtonMixin' import mixin from '@/utils/ConfirmButtonMixin'
export default { @Component({
name: 'imgur', name: 'imgur',
mixins: [mixin], mixins: [mixin]
data () { })
return { export default class extends Vue {
form: { form: IImgurConfig = {
clientId: '', clientId: '',
proxy: '' proxy: ''
} }
}
},
created () { created () {
const config = this.$db.get('picBed.imgur').value() const config = this.$db.get('picBed.imgur') as IImgurConfig
if (config) { if (config) {
for (let i in config) { this.form = Object.assign({}, config)
this.form[i] = config[i]
}
} }
}, }
methods: { confirm () {
confirm () { // @ts-ignore
this.$refs.imgur.validate((valid) => { this.$refs.imgur.validate((valid) => {
if (valid) { if (valid) {
this.$db.set('picBed.imgur', this.form).write() this.letPicGoSaveData({
const successNotification = new window.Notification('设置结果', { 'picBed.imgur': this.form
body: '设置成功' })
}) const successNotification = new Notification('设置结果', {
successNotification.onclick = () => { body: '设置成功'
return true })
} successNotification.onclick = () => {
} else { return true
return false
} }
}) } else {
} return false
}
})
} }
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
#imgur-view #imgur-view
.el-form .el-form
label label
line-height 22px line-height 22px
padding-bottom 0 padding-bottom 0
color #eee color #eee
@@ -90,7 +88,7 @@ export default {
.el-radio-group .el-radio-group
width 100% width 100%
label label
width 25% width 25%
.el-radio-button__inner .el-radio-button__inner
width 100% width 100%
.el-radio-button:first-child .el-radio-button:first-child
@@ -114,4 +112,4 @@ export default {
transition .2s color ease-in-out transition .2s color ease-in-out
&:hover &:hover
color #409EFF color #409EFF
</style> </style>

Some files were not shown because too many files have changed in this diff Show More