Compare commits

..
78 Commits
Author SHA1 Message Date
PiEgg 9274b3f8be 📦 Chore(fuck): electron-builder publish option 2020-01-01 19:38:31 +08:00
PiEgg 7809b626d3 🐛 Fix: cI build error in linux 2020-01-01 19:29:37 +08:00
PiEgg a7995c037c 📦 Chore: fix CI script 2020-01-01 19:01:47 +08:00
PiEgg 85f4c67e0b 🎉 Release: v2.2.0 2020-01-01 18:18:51 +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
Molunerfinn 50f31bd438 🎉 Release: v2.1.2 2019-04-19 13:26:04 +08:00
Molunerfinn 5b0d596c6d 🔨 Refactor: improve picgo-setting page's thing
Add $fileName info
Add settings documentation url icon button
2019-04-19 11:00:51 +08:00
Molunerfinn c59e2bcc97 Feature: add file-name for customurl
add $fileName arg for customUrl

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

ISSUES CLOSED: #237
2019-04-17 09:28:36 +08:00
Molunerfinn 748a7fd07e 🎉 Release: v2.1.1 2019-04-16 10:45:21 +08:00
Molunerfinn 4000cea7c8 🐛 Fix: upload-area can't upload images 2019-04-16 10:45:09 +08:00
Molunerfinn 8978166328 🎉 Release: v2.1.0 2019-04-15 15:26:42 +08:00
Molunerfinn 7f1769733b Feature: add workflow for mac
for uploading images from context menu in macOS
2019-04-15 15:25:33 +08:00
Molunerfinn e5fbe750ae Feature: add windows context menu
for uploading pictures with picgo.exe
2019-04-15 14:10:01 +08:00
Molunerfinn ed539cf206 ⬆️ Upgrade: picgo-core -> 1.3.5 2019-04-15 10:28:40 +08:00
Molunerfinn 74c7016d2c Feature: support commandline -> upload images in clipboard 2019-04-15 10:28:11 +08:00
Molunerfinn 407b821aca Feature: add gui-api for remove event
ISSUES CLOSED: #201
2019-04-14 22:07:33 +08:00
PiEgg 6db86ec359 Feature: add commandline argvs support for picgo app
for example: picgo.exe ./xxx.jpg
2019-04-14 15:41:48 +08:00
PiEgg c64959a139 🐛 Fix: qiniu area option from select group -> input
ISSUES CLOSED: #230
2019-04-13 11:44:21 +08:00
Molunerfinn 6122e17d75 📝 Docs: add brew cask source
Thanks to @womeimingzi11!
2019-04-12 10:58:10 +08:00
Molunerfinn 37f1d34abc 📦 Chore: add picgo bump-version 2019-04-10 10:48:22 +08:00
Molunerfinn 3dd20857aa Fixed: #223 || change github token input type -> password 2019-04-04 17:09:42 +08:00
Molunerfinn 4a11d9830b Rm: showInputBox handler in plugin page 2019-04-04 16:58:36 +08:00
Molunerfinn 0165879f2d Changed: showInputBox from plugin page -> mainPage layout 2019-04-04 16:56:38 +08:00
Molunerfinn 17302fe3e4 Finished: log level setting 2019-04-04 15:08:47 +08:00
Molunerfinn f65d2d2df4 Fixed: npm ENOENT bug && mini window display -> false by default 2019-04-02 17:11:01 +08:00
PiEgg af19de7168 Changed: MIT LICENSE -> 996ICU LICENSE 2019-03-30 12:20:00 +08:00
Molunerfinn 9ab9e1973c Finished: guiApi.showMessageBox 2019-03-27 16:38:15 +08:00
Molunerfinn a4056cbe58 Fixed: #175 || improve gallery page scroll performance 2019-03-26 17:18:52 +08:00
zWingzandPiEgg 69d660fe7e Updated: trayPage add img-list object-fit 2019-03-16 14:47:48 +08:00
Molunerfinn 058e903789 Fixed: mime-types bug 2019-03-12 15:11:33 +08:00
Molunerfinn 53cdddf44a 2.0.5 2019-03-12 11:21:02 +08:00
Molunerfinn 2315aad732 Fixed: WIN10 powershell output encoding bug 2019-03-12 11:20:27 +08:00
Molunerfinn 7f8c6fd60e Fixed: #195 || windows7's bug of uploading image in clipboard 2019-03-11 13:40:39 +08:00
123 changed files with 12700 additions and 9181 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 = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
globals: {
__static: 'readonly'
},
env: {
browser: true,
node: true
},
extends: 'standard',
globals: {
__static: true
},
plugins: [
'html'
parser: "vue-eslint-parser",
'extends': [
'plugin:vue/essential',
'@vue/standard',
'@vue/typescript'
],
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
'plugins': ['@typescript-eslint'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
+4
View File
@@ -12,3 +12,7 @@ thumbs.db
!.gitkeep
yarn-error.log
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
#- yarn run pack && xvfb-maybe node_modules/.bin/mocha test/e2e
- npm run release
- yarn run build:docs
# - yarn run build:docs
branches:
only:
- master
after_script:
- cd docs/dist
- git init
- git config user.name "Molunerfinn"
- git config user.email "marksz@teamsz.xyz"
- git add .
- git commit -m "Travis build docs"
- git push --force --quiet "https://${GH_TOKEN}@github.com/Molunerfinn/PicGo.git" master:gh-pages
# after_script:
# - cd docs/dist
# - git init
# - git config user.name "Molunerfinn"
# - git config user.email "marksz@teamsz.xyz"
# - git add .
# - git commit -m "Travis build docs"
# - git push --force --quiet "https://${GH_TOKEN}@github.com/Molunerfinn/PicGo.git" master:gh-pages
+22 -1
View File
@@ -1,4 +1,25 @@
{
"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
@@ -0,0 +1,93 @@
# :tada: 2.2.0 (2020-01-01)
### :sparkles: Features
* add alias for plugin config name ([5a06483](https://github.com/Molunerfinn/PicGo/commit/5a06483))
* add aliyun oss options ([a33f1ad](https://github.com/Molunerfinn/PicGo/commit/a33f1ad)), closes [#347](https://github.com/Molunerfinn/PicGo/issues/347)
* **server:** add http server for uploading images by a http request ([c56d4ef](https://github.com/Molunerfinn/PicGo/commit/c56d4ef))
* add server config settings ([6b57cf7](https://github.com/Molunerfinn/PicGo/commit/6b57cf7))
* only shows visible pic-beds ([9d4d605](https://github.com/Molunerfinn/PicGo/commit/9d4d605)), closes [#310](https://github.com/Molunerfinn/PicGo/issues/310)
### :bug: Bug Fixes
* beforeOpen handler in windows ([cd30a6c](https://github.com/Molunerfinn/PicGo/commit/cd30a6c))
* **website:** website pictures error ([a5b6526](https://github.com/Molunerfinn/PicGo/commit/a5b6526))
* add new tray icon for macOS dark-mode ([c5adf3b](https://github.com/Molunerfinn/PicGo/commit/c5adf3b)), closes [#267](https://github.com/Molunerfinn/PicGo/issues/267)
* busApi event register first && emit later ([e1a0cbb](https://github.com/Molunerfinn/PicGo/commit/e1a0cbb))
* decrease title-bar z-index when config-form dialog shows ([f2750e1](https://github.com/Molunerfinn/PicGo/commit/f2750e1))
* enum type error ([4e3fa28](https://github.com/Molunerfinn/PicGo/commit/4e3fa28))
* handle empty request-body ([81e6acb](https://github.com/Molunerfinn/PicGo/commit/81e6acb))
* launch error in new structrue ([bc8e641](https://github.com/Molunerfinn/PicGo/commit/bc8e641))
* plugin config-form && default plugin logo ([514fc40](https://github.com/Molunerfinn/PicGo/commit/514fc40))
* release script ([b4f10c6](https://github.com/Molunerfinn/PicGo/commit/b4f10c6))
* rename page not work ([29a55ed](https://github.com/Molunerfinn/PicGo/commit/29a55ed))
* save debug mode && PICGO_ENV into config file ([c6ead5b](https://github.com/Molunerfinn/PicGo/commit/c6ead5b))
* settingPage && miniPage style in windows ([3fd9572](https://github.com/Molunerfinn/PicGo/commit/3fd9572))
### :pencil: Documentation
* add note for windows electron mirror ([46a49ed](https://github.com/Molunerfinn/PicGo/commit/46a49ed))
* remove weibo picbed ([e81b8f4](https://github.com/Molunerfinn/PicGo/commit/e81b8f4))
* update installation by scoop ([91b397d](https://github.com/Molunerfinn/PicGo/commit/91b397d)), closes [#295](https://github.com/Molunerfinn/PicGo/issues/295)
* update readme ([1b3522e](https://github.com/Molunerfinn/PicGo/commit/1b3522e))
* update README ([f491209](https://github.com/Molunerfinn/PicGo/commit/f491209))
* update site ([fe9e19a](https://github.com/Molunerfinn/PicGo/commit/fe9e19a))
## :tada: 2.1.2 (2019-04-19)
### :sparkles: Features
* add file-name for customurl ([c59e2bc](https://github.com/Molunerfinn/PicGo/commit/c59e2bc)), closes [#173](https://github.com/Molunerfinn/PicGo/issues/173)
### :bug: Bug Fixes
* log-level filter bug ([4e02244](https://github.com/Molunerfinn/PicGo/commit/4e02244)), closes [#237](https://github.com/Molunerfinn/PicGo/issues/237)
* log-level's reset value from 'all' -> ['all'] ([3c6b329](https://github.com/Molunerfinn/PicGo/commit/3c6b329)), closes [#240](https://github.com/Molunerfinn/PicGo/issues/240) [#237](https://github.com/Molunerfinn/PicGo/issues/237)
* mini window hidden bug in linux ([466dbec](https://github.com/Molunerfinn/PicGo/commit/466dbec)), closes [#239](https://github.com/Molunerfinn/PicGo/issues/239)
## :tada: 2.1.1 (2019-04-16)
### :bug: Bug Fixes
* upload-area can't upload images ([4000cea](https://github.com/Molunerfinn/PicGo/commit/4000cea))
# :tada: 2.1.0 (2019-04-15)
### :sparkles: Features
* add commandline argvs support for picgo app ([6db86ec](https://github.com/Molunerfinn/PicGo/commit/6db86ec))
* add gui-api for remove event ([407b821](https://github.com/Molunerfinn/PicGo/commit/407b821)), closes [#201](https://github.com/Molunerfinn/PicGo/issues/201)
* add windows context menu ([e5fbe75](https://github.com/Molunerfinn/PicGo/commit/e5fbe75))
* add workflow for mac ([7f17697](https://github.com/Molunerfinn/PicGo/commit/7f17697))
* support commandline -> upload images in clipboard ([74c7016](https://github.com/Molunerfinn/PicGo/commit/74c7016))
### :bug: Bug Fixes
* qiniu area option from select group -> input ([c64959a](https://github.com/Molunerfinn/PicGo/commit/c64959a)), closes [#230](https://github.com/Molunerfinn/PicGo/issues/230)
### :pencil: Documentation
* add brew cask source ([6122e17](https://github.com/Molunerfinn/PicGo/commit/6122e17))
### :package: Chore
* add picgo bump-version ([37f1d34](https://github.com/Molunerfinn/PicGo/commit/37f1d34))
+18 -5
View File
@@ -1,8 +1,21 @@
MIT License
Copyright (c) <year> <copyright holders>
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Copyright (c) 2017-present, Molunerfinn
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+39 -35
View File
@@ -1,11 +1,7 @@
# PicGo
> 图片上传+管理新体验
<p align="center">
<div align="center">
<img src="https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/New%20LOGO-150.png" alt="">
</p>
<p align="center">
<h1>PicGo</h1>
<blockquote>图片上传+管理新体验 </blockquote>
<a href="https://github.com/feross/standard">
<img src="https://img.shields.io/badge/code%20style-standard-green.svg?style=flat-square" alt="">
</a>
@@ -18,43 +14,50 @@
<a href="https://github.com/Molunerfinn/PicGo/releases/latest">
<img src="https://img.shields.io/github/release/Molunerfinn/PicGo.svg?style=flat-square" alt="">
</a>
</p>
<a href="https://github.com/PicGo/bump-version">
<img src="https://img.shields.io/badge/picgo-convention-blue.svg?style=flat-square" alt="">
</a>
</div>
## 应用说明
**PicGo在上传图片之后自动会将图片链接复制到你的剪贴板里,可选5种复制的链接格式。**
**PicGo 在上传图片之后自动会将图片链接复制到你的剪贴板里,可选 5 种复制的链接格式。**
PicGo目前支持了
PicGo 目前支持了
- `微博图床` v1.0
- ~~`微博图床` v1.0~~ **微博图床从 2019 年 4 月开始进行防盗链,不建议继续使用**
- `七牛图床` v1.0
- `腾讯云COS v4\v5版本` v1.1 & v1.5.0
- `腾讯云 COS v4\v5版本` v1.1 & v1.5.0
- `又拍云` v1.2.0
- `GitHub` v1.5.0
- `SM.MS` v1.5.1
- `阿里云OSS` v1.6.0
- `阿里云 OSS` 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)用以支持快捷上传剪贴板里的图片(第一张)。
PicGo支持自定义快捷键,使用方法见[配置手册](https://picgo.github.io/PicGo-Doc/zh/guide/config.html)。
支持快捷键`command+shift+p`macOS)或者`control+shift+p`Windows\Linux)用以支持快捷上传剪贴板里的图片(第一张)。
PicGo 支持自定义快捷键,使用方法见[配置手册](https://picgo.github.io/PicGo-Doc/zh/guide/config.html)。
开发进度可以查看[Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度。
开发进度可以查看 [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)。**
## 下载安装
macOS用户请下载最新版本的`dmg`文件,windows用户请下载最新版本的`exe`文件,linux用户请下载`AppImage`文件。
点击此处下载[应用](https://github.com/Molunerfinn/PicGo/releases)。
**如果你是Arch类的Linux用户,可以直接通过`aurman -S picgo-appimage`来安装PicGo。感谢@houbaron的贡献!**
macOS 用户请下载最新版本的 `dmg` 文件,Windows 用户请下载最新版本的 `exe` 文件,Linux用户请下载 `AppImage` 文件。
**如果你是 Arch 类的 Linux 用户,可以直接通过 `aurman -S picgo-appimage` 来安装 PicGo。感谢 @houbaron 的贡献!**
**如果你是 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 的贡献!**
## 应用截图
@@ -64,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` 并进入项目
3. `npm install` 下载依赖
4. Mac需要有Xcode环境,Windows需要有VS环境。
4. Mac 需要有 Xcode 环境,Windows 需要有 VS 环境。
### 开发模式
输入`npm run dev`进入开发模式,开发模式具有热重载特性。不过需要注意的是,开发模式不稳定,会有进程崩溃的情况。此时需要:
输入 `npm run electron:serve` 进入开发模式,开发模式具有热重载特性。不过需要注意的是,开发模式不稳定,会有进程崩溃的情况。此时需要:
```bash
ctrl+c # 退出开发模式
@@ -86,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
export ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/"
# 在 Windows 上,则可以使用 set ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/ (无需引号)
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 并且它对你确实有帮助,欢迎给我打赏一杯咖啡哈~
支付宝:
@@ -117,4 +121,4 @@ npm run build
[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
cache:
- node_modules
- '%APPDATA%\npm-cache'
- '%USERPROFILE%\.electron'
- '%USERPROFILE%\AppData\Local\Yarn\cache'
@@ -20,9 +19,9 @@ init:
- git config --global core.autocrlf input
install:
- ps: Install-Product node 8 x64
- ps: Install-Product node 12 x64
- git reset --hard HEAD
- npm install
- yarn
- node --version
build_script:
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
+13 -8
View File
@@ -1,8 +1,13 @@
!macro preInit
SetRegView 64
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\PicGo"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\PicGo"
SetRegView 32
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\PicGo"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\PicGo"
!macroend
!macro customInstall
SetRegView 64
WriteRegStr HKCR "*\shell\PicGo" "" "Upload pictures w&ith PicGo"
WriteRegStr HKCR "*\shell\PicGo" "Icon" "$INSTDIR\PicGo.exe"
WriteRegStr HKCR "*\shell\PicGo\command" "" '"$INSTDIR\PicGo.exe" "upload" "%1"'
SetRegView 32
WriteRegStr HKCR "*\shell\PicGo" "" "Upload pictures w&ith PicGo"
WriteRegStr HKCR "*\shell\PicGo" "Icon" "$INSTDIR\PicGo.exe"
WriteRegStr HKCR "*\shell\PicGo\command" "" '"$INSTDIR\PicGo.exe" "upload" "%1"'
!macroend
!macro customUninstall
DeleteRegKey HKCR "*\shell\PicGo"
!macroend
View File
View File
+9 -9
View File
@@ -13,10 +13,10 @@
h3.desc
| 支持macOS,Windows,Linux
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
.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
.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
@@ -37,32 +37,32 @@ export default {
year: new Date().getFullYear(),
itemList: [
{
url: 'https://ws1.sinaimg.cn/large/8700af19ly1fma907llb5j20m30ed46a',
url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/second.png',
title: '精致设计',
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小窗',
desc: 'Windows以及Linux系统下提供一个mini悬浮窗用于用户拖拽上传,节约你宝贵的桌面空间。'
},
{
url: 'https://ws1.sinaimg.cn/large/8700af19ly1fmd56zm2nej218g0p0teb',
url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/forth.png',
title: '便捷管理',
desc: '查看你的上传记录,重复使用更方便。支持点击图片大图查看。支持删除图片(仅本地记录),让界面更加干净。'
},
{
url: 'https://ws1.sinaimg.cn/large/8700af19ly1fmd5ck9m0wj20lr0cxmzs',
url: 'https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/fifth.png',
title: '可选图床',
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: '多样链接',
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: '插件系统',
desc: '2.0版本开始支持插件系统,让PicGo发挥无限潜能,成为一个极致的效率工具。'
}
@@ -108,7 +108,7 @@ h1
#header
height 100vh
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-size cover
background-position center
+68 -144
View File
@@ -1,159 +1,83 @@
{
"name": "picgo",
"version": "2.0.4",
"author": "Molunerfinn <marksz@teamsz.xyz>",
"description": "Easy to upload your pic & copy to write",
"license": "MIT",
"main": "./dist/electron/main.js",
"version": "2.2.0",
"private": true,
"scripts": {
"render": "webpack-dev-server --hot --colors --config .electron-vue/webpack.renderer.config.js --port 9080 --content-base app/dist",
"build": "node .electron-vue/build.js && electron-builder",
"release": "node .electron-vue/build.js && electron-builder",
"build:dir": "node .electron-vue/build.js && electron-builder --dir",
"build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
"build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
"dev": "node .electron-vue/dev-runner.js",
"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"
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"cz": "git-cz",
"bump": "bump-version",
"release": "vue-cli-service electron:build --publish always"
},
"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/"
"main": "background.js",
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"config": {
"commitizen": {
"path": "./node_modules/cz-customizable"
},
"cz-customizable": {
"config": "./node_modules/@picgo/bump-version/.cz-config.js"
}
},
"commitlint": {
"extends": [
"./node_modules/@picgo/bump-version/commitlint-picgo"
]
},
"dependencies": {
"axios": "^0.16.1",
"dateformat": "^3.0.3",
"element-ui": "^2.4.11",
"axios": "^0.19.0",
"core-js": "^3.3.2",
"element-ui": "^2.13.0",
"fix-path": "^2.1.0",
"fs-extra": "^4.0.2",
"image-size": "^0.6.1",
"keycode": "^2.1.9",
"fs-extra": "^8.1.0",
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"md5": "^2.2.1",
"melody.css": "^1.0.2",
"picgo": "^1.2.6",
"qiniu": "^7.1.1",
"vue": "^2.3.3",
"vue-electron": "^1.0.6",
"vue-gallery": "^1.2.4",
"picgo": "^1.4.4",
"vue": "^2.6.10",
"vue-gallery": "^2.0.1",
"vue-lazyload": "^1.2.6",
"vue-router": "^2.5.3",
"vuex": "^2.3.1"
"vue-router": "^3.1.3"
},
"devDependencies": {
"babel-core": "^6.25.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.1.1",
"babel-plugin-istanbul": "^4.1.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.0",
"babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.24.1",
"babili-webpack-plugin": "^0.1.2",
"cfonts": "^1.1.3",
"chai": "^4.0.0",
"chalk": "^2.1.0",
"copy-webpack-plugin": "^4.0.1",
"cross-env": "^5.0.5",
"css-loader": "^0.28.4",
"del": "^3.0.0",
"devtron": "^1.4.0",
"electron": "4.0.2",
"electron-builder": "^20.38.4",
"electron-debug": "^1.4.0",
"electron-devtools-installer": "^2.2.0",
"eslint": "^4.4.1",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^2.1.1",
"eslint-plugin-html": "^3.1.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.1.1",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1",
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"inject-loader": "^3.0.0",
"karma": "^1.3.0",
"karma-chai": "^0.1.0",
"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"
"@commitlint/cli": "^8.2.0",
"@picgo/bump-version": "^1.0.3",
"@types/fs-extra": "^8.0.1",
"@types/inquirer": "^6.5.0",
"@types/lowdb": "^1.0.9",
"@types/node": "10.17.6",
"@types/request-promise-native": "^1.0.17",
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-eslint": "^4.0.0",
"@vue/cli-plugin-router": "^4.0.0",
"@vue/cli-plugin-typescript": "^4.0.0",
"@vue/cli-service": "^4.0.0",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/eslint-config-typescript": "^4.0.0",
"commitizen": "^4.0.3",
"conventional-changelog": "^3.1.18",
"cz-customizable": "^6.2.0",
"electron": "^6.0.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"husky": "^3.1.0",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"typescript": "~3.7.3",
"vue-cli-plugin-electron-builder": "^1.4.2",
"vue-property-decorator": "^8.3.0",
"vue-template-compiler": "^2.6.10"
},
"resolutions": {
"@types/node": "12.0.2"
}
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSServices</key>
<array>
<dict>
<key>NSBackgroundColorName</key>
<string>background</string>
<key>NSBackgroundSystemColorName</key>
<string>systemBlueColor</string>
<key>NSIconName</key>
<string>NSTouchBarShare</string>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Upload pictures with PicGo</string>
</dict>
<key>NSMessage</key>
<string>runWorkflowAsService</string>
<key>NSRequiredContext</key>
<dict>
<key>NSApplicationIdentifier</key>
<string>com.apple.finder</string>
</dict>
<key>NSSendFileTypes</key>
<array>
<string>public.image</string>
</array>
</dict>
</array>
</dict>
</plist>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

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

+1 -6
View File
@@ -4,11 +4,6 @@ param($imagePath)
# Adapted from https://github.com/octan3/img-clipboard-dump/blob/master/dump-clipboard-png.ps1
Add-Type -Assembly PresentationCore
$file = Get-Clipboard -Format FileDropList
if ($file -ne $null) {
Convert-Path $file
Exit 1
}
$img = [Windows.Clipboard]::GetImage()
if ($img -eq $null) {
@@ -28,4 +23,4 @@ $encoder.Frames.Add([Windows.Media.Imaging.BitmapFrame]::Create($fcb)) | out-nul
$encoder.Save($stream) | out-null
$stream.Dispose() | out-null
$imagePath
$imagePath
+44
View File
@@ -0,0 +1,44 @@
# Adapted from https://github.com/octan3/img-clipboard-dump/blob/master/dump-clipboard-png.ps1
param($imagePath)
# https://github.com/PowerShell/PowerShell/issues/7233
# fix the output encoding bug
[console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding
Add-Type -Assembly PresentationCore
function main {
$img = [Windows.Clipboard]::GetImage()
if ($img -eq $null) {
"no image"
Exit 1
}
if (-not $imagePath) {
"no image"
Exit 1
}
$fcb = new-object Windows.Media.Imaging.FormatConvertedBitmap($img, [Windows.Media.PixelFormats]::Rgb24, $null, 0)
$stream = [IO.File]::Open($imagePath, "OpenOrCreate")
$encoder = New-Object Windows.Media.Imaging.PngBitmapEncoder
$encoder.Frames.Add([Windows.Media.Imaging.BitmapFrame]::Create($fcb)) | out-null
$encoder.Save($stream) | out-null
$stream.Dispose() | out-null
$imagePath
}
try {
# For WIN10
$file = Get-Clipboard -Format FileDropList
if ($file -ne $null) {
Convert-Path $file
Exit 1
}
} catch {
# For WIN7 WIN8 WIN10
main
}
main
+743
View File
@@ -0,0 +1,743 @@
'use strict'
import {
app,
BrowserWindow,
Tray,
Menu,
Notification,
clipboard,
ipcMain,
globalShortcut,
dialog,
systemPreferences,
WebContents,
IpcMainEvent,
protocol
} from 'electron'
import {
createProtocol,
installVueDevtools
} from 'vue-cli-plugin-electron-builder/lib'
import db from '#/datastore'
import picgo from '~/main/utils/picgo'
import uploader from '~/main/utils/uploader'
import beforeOpen from '~/main/utils/beforeOpen'
import pasteTemplate from '#/utils/pasteTemplate'
import updateChecker from '~/main/utils/updateChecker'
import { getPicBeds } from '~/main/utils/getPicBeds'
import pkg from 'root/package.json'
import picgoCoreIPC from '~/main/utils/picgoCoreIPC'
import fixPath from 'fix-path'
import { getUploadFiles } from '~/main/utils/handleArgv'
import bus from '~/main/utils/eventBus'
import {
updateShortKeyFromVersion212
} from '~/main/migrate/shortKeyUpdateHelper'
import shortKeyHandler from '~/main/utils/shortKeyHandler'
import logger from '~/main/utils/logger'
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 '~/main/utils/busApi/constants'
import server from '~/main/server/index'
const isDevelopment = process.env.NODE_ENV !== 'production'
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
beforeOpen()
let window: BrowserWindow | null
let settingWindow: BrowserWindow | null
let miniWindow: BrowserWindow | null
let tray: Tray | null
let menu: Menu | null
let contextMenu: Menu | null
const winURL = isDevelopment
? (process.env.WEBPACK_DEV_SERVER_URL as string)
: `picgo://./index.html`
const settingWinURL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#setting/upload`
: `picgo://./index.html#setting/upload`
const miniWinURL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page`
: `picgo://./index.html#mini-page`
// fix the $PATH in macOS
fixPath()
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 (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',
// @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: '退出'
}
])
}
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') {
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
})
}
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', () => {
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 imgs = await uploader.setWebContents(window!.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])
}
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: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
backgroundThrottling: false
}
})
window.loadURL(winURL)
window.on('closed', () => {
window = null
})
window.on('blur', () => {
window!.hide()
})
return window
}
const createMiniWindow = () => {
if (miniWindow || process.platform === 'darwin') {
return false
}
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
}
miniWindow = new BrowserWindow(obj)
miniWindow.loadURL(miniWinURL)
miniWindow.on('closed', () => {
miniWindow = null
})
return miniWindow
}
const createSettingWindow = () => {
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`
}
settingWindow = new BrowserWindow(options)
settingWindow!.loadURL(settingWinURL)
settingWindow!.on('closed', () => {
bus.emit('toggleShortKeyModifiedMode', false)
settingWindow = null
if (process.platform === 'linux') {
process.nextTick(() => {
app.quit()
})
}
})
createMenu()
createMiniWindow()
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()
}
}
]
}]
// @ts-ignore
menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
}
const toggleWindow = (bounds: IBounds) => {
if (window!.isVisible()) {
window!.hide()
} else {
showWindow(bounds)
}
}
const showWindow = (bounds: IBounds) => {
window!.setPosition(bounds.x - 98 + 11, bounds.y, false)
window!.webContents.send('updateFiles')
window!.show()
window!.focus()
}
const uploadClipboardFiles = async (): Promise<string> => {
const win = getAvailableWindow()
let img = await uploader.setWebContents(win!.webContents).upload()
if (img !== false) {
if (img.length > 0) {
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])
window!.webContents.send('clipboardFiles', [])
window!.webContents.send('uploadFiles', img)
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
return img[0].imgUrl as string
} else {
const notification = new Notification({
title: '上传不成功',
body: '你剪贴板最新的一条记录不是图片哦'
})
notification.show()
return ''
}
} else {
return ''
}
}
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)
window!.webContents.send('uploadFiles', imgs)
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
return result
} else {
return []
}
}
picgoCoreIPC()
// from macOS tray
ipcMain.on('uploadClipboardFiles', async () => {
const img = await uploader.setWebContents(window!.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])
window!.webContents.send('clipboardFiles', [])
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
}
window!.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', () => {
if (!settingWindow) {
createSettingWindow()
} else {
settingWindow.show()
}
if (miniWindow) {
miniWindow.hide()
}
})
ipcMain.on('openMiniWindow', () => {
if (!miniWindow) {
createMiniWindow()
}
miniWindow!.show()
miniWindow!.focus()
settingWindow!.hide()
})
// from mini window
ipcMain.on('syncPicBed', () => {
if (settingWindow) {
settingWindow.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()
})
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 = getAvailableWindow()
uploadChoosedFiles(win.webContents, files)
}
} else {
if (settingWindow) {
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())
}
}
createWindow()
createSettingWindow()
if (process.platform === 'darwin' || process.platform === 'win32') {
createTray()
}
db.set('needReload', false)
updateChecker()
initEventCenter()
// 不需要阻塞
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 = getAvailableWindow()
uploadChoosedFiles(win.webContents, files)
}
}
}
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
createProtocol('picgo')
if (window === null) {
createWindow()
}
if (settingWindow === null) {
createSettingWindow()
}
})
app.on('will-quit', () => {
globalShortcut.unregisterAll()
bus.removeAllListeners()
server.shutdown()
})
app.setLoginItemSettings({
openAtLogin: db.get('settings.autoStart') || false
})
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
}
for (let i in eventList) {
bus.on(i, eventList[i])
}
}
function getAvailableWindow () {
let win
if (miniWindow && miniWindow.isVisible()) {
win = miniWindow
} else {
win = settingWindow || window || createSettingWindow()
}
return win
}
async function busCallUploadClipboardFiles () {
const imgUrl = await uploadClipboardFiles()
bus.emit(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, imgUrl)
}
async function busCallUploadFiles (pathList: IFileWithPath[]) {
const win = getAvailableWindow()
const urls = await uploadChoosedFiles(win.webContents, pathList)
bus.emit(UPLOAD_WITH_FILES_RESPONSE, urls)
}
function busCallGetWindowId () {
const win = getAvailableWindow()
bus.emit(GET_WINDOW_ID_REPONSE, win.id)
}
function busCallGetSettingWindowId () {
if (!settingWindow) createSettingWindow()
bus.emit(GET_SETTING_WINDOW_ID_RESPONSE, settingWindow!.id)
}
// 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 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 '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 './assets/fonts/iconfont.css'
import 'element-ui/lib/theme-chalk/index.css'
import VueLazyLoad from 'vue-lazyload'
Vue.use(ElementUI)
Vue.use(VueLazyLoad)
import axios from 'axios'
import mainMixin from './renderer/utils/mainMixin'
webFrame.setVisualZoomLevelLimits(1, 1)
webFrame.setLayoutZoomLevelLimits(0, 0)
if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.http = Vue.prototype.$http = axios
Vue.prototype.$db = db
Vue.config.productionTip = false
Vue.prototype.$builtInPicBed = [
'smms',
'weibo',
@@ -29,12 +23,14 @@ Vue.prototype.$builtInPicBed = [
'aliyun',
'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({
components: { App },
router,
store,
template: '<App/>'
render: h => h(App)
}).$mount('#app')
-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')
-565
View File
@@ -1,565 +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 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'
/**
* 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) {
const url = imgs[i].url || imgs[i].imgUrl
clipboard.writeText(pasteTemplate(pasteStyle, url))
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()
})
}
const createMiniWidow = () => {
if (miniWindow) {
return false
}
let obj = {
height: 64,
width: 64,
show: true,
frame: false,
fullscreenable: false,
skipTaskbar: true,
resizable: false,
transparent: true,
icon: `${__static}/logo.png`,
webPreferences: {
backgroundThrottling: false
}
}
if (process.platform === 'linux') {
obj.transparent = false
}
if (process.platform === 'darwin') {
obj.show = false
}
if (db.read().get('settings.miniWindowOntop').value()) {
obj.alwaysOnTop = true
}
miniWindow = new BrowserWindow(obj)
miniWindow.loadURL(miniWinURL)
miniWindow.on('closed', () => {
miniWindow = null
})
}
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()
}
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
}
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'
const url = img[0].url || img[0].imgUrl
clipboard.writeText(pasteTemplate(pasteStyle, url))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl,
icon: img[0].imgUrl
})
notification.show()
db.read().get('uploaded').insert(img[0]).write()
window.webContents.send('clipboardFiles', [])
window.webContents.send('uploadFiles', img)
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
} else {
const notification = new Notification({
title: '上传不成功',
body: '你剪贴板最新的一条记录不是图片哦'
})
notification.show()
}
}
}
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'
const url = img[0].url || img[0].imgUrl
clipboard.writeText(pasteTemplate(pasteStyle, url))
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) => {
const input = files.map(item => item.path)
const imgs = await new Uploader(input, evt.sender).upload()
if (imgs !== false) {
const pasteStyle = db.read().get('settings.pasteStyle').value() || 'markdown'
let pasteText = ''
for (let i in imgs) {
const url = imgs[i].url || imgs[i].imgUrl
pasteText += pasteTemplate(pasteStyle, url) + '\r\n'
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl,
icon: 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')
}
}
})
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 {
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()
})
})
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
}
+90
View File
@@ -0,0 +1,90 @@
import http from 'http'
import routers from './routerManager'
import {
handleResponse
} from './utils'
import picgo from '~/main/utils/picgo'
import logger from '~/main/utils/logger'
class Server {
private httpServer: http.Server
private config: IServerConfig
constructor () {
this.config = picgo.getConfig('settings.server') || {
port: 36677,
host: '127.0.0.1',
enable: true
}
this.httpServer = http.createServer(this.handleRequest)
}
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.port': this.config.port
})
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/utils/busApi/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()
}
+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
+8
View File
@@ -0,0 +1,8 @@
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'
+74
View File
@@ -0,0 +1,74 @@
import bus from '../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)
})
}
+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
}
+19
View File
@@ -0,0 +1,19 @@
import picgo from './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 {
getPicBeds
}
-105
View File
@@ -1,105 +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) {
const url = imgs[i].url || imgs[i].imgUrl
pasteText += pasteTemplate(pasteStyle, url) + '\r\n'
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl,
icon: imgs[i].imgUrl
})
setTimeout(() => {
notification.show()
}, i * 100)
db.read().get('uploaded').insert(imgs[i]).write()
}
clipboard.writeText(pasteText)
this[WEBCONTENTS].send('uploadFiles', imgs)
this[WEBCONTENTS].send('updateGallery')
return imgs
}
return []
}
/**
* For notification
* @param {Object} options
*/
showNotification (options) {
if (options === undefined) {
options = {
title: '',
body: ''
}
}
const notification = new Notification({
title: options.title,
body: options.body
})
notification.show()
}
}
export default GuiApi
+121
View File
@@ -0,0 +1,121 @@
import {
dialog,
BrowserWindow,
clipboard,
Notification,
WebContents,
ipcMain,
webContents
} from 'electron'
import db from '#/datastore'
import uploader from './uploader'
import pasteTemplate from '#/utils/pasteTemplate'
import {
getWindowId,
getSettingWindowId
} from '~/main/utils/busApi'
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
+41
View File
@@ -0,0 +1,41 @@
import path from 'path'
import fs from 'fs-extra'
type ClipboardFileObject = {
path: string
}
type Result = ClipboardFileObject[]
const getUploadFiles = (argv = process.argv, cwd = process.cwd()) => {
let files = argv.slice(1)
if (files.length > 0 && files[0] === 'upload') {
if (files.length === 1) {
return null // for uploading images in clipboard
} else if (files.length > 1) {
files = argv.slice(1)
let result: Result = []
if (files.length > 0) {
result = files.map(item => {
if (path.isAbsolute(item)) {
return {
path: item
}
} else {
let tempPath = path.join(cwd, item)
if (fs.existsSync(tempPath)) {
return {
path: tempPath
}
} else {
return null
}
}
}).filter(item => item !== null) as Result
}
return result
}
}
return []
}
export {
getUploadFiles
}
+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()
-13
View File
@@ -1,13 +0,0 @@
import db from '../../datastore'
export default (style, url) => {
const customLink = db.read().get('settings.customLink').value() || '$url'
const tpl = {
'markdown': `![](${url})`,
'HTML': `<img src="${url}"/>`,
'URL': url,
'UBB': `[IMG]${url}[/IMG]`,
'Custom': customLink.replace(/\$url/g, url)
}
return tpl[style]
}
+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
@@ -1,14 +1,38 @@
import path from 'path'
import GuiApi from './guiApi'
import {
dialog,
shell,
IpcMain,
IpcMainEvent,
App,
ipcMain,
app
} from 'electron'
import PicGoCore from '~/universal/types/picgo'
import { IPicGoHelperType } from '#/types/enum'
import shortKeyHandler from './shortKeyHandler'
import picgo from '~/main/utils/picgo'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo')
const PluginHandler = requireFunc('picgo/dist/lib/PluginHandler').default
// 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
const getConfig = (name, type, ctx) => {
let config = []
const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => {
let config: any[] = []
if (name === '') {
return config
} else {
@@ -22,7 +46,7 @@ const getConfig = (name, type, ctx) => {
}
}
const handleConfigWithFunction = config => {
const handleConfigWithFunction = (config: any[]) => {
for (let i in config) {
if (typeof config[i].default === 'function') {
config[i].default = config[i].default()
@@ -34,9 +58,8 @@ const handleConfigWithFunction = config => {
return config
}
const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => {
ipcMain.on('getPluginList', event => {
const picgo = new PicGo(CONFIG_PATH)
const handleGetPluginList = () => {
ipcMain.on('getPluginList', (event: IpcMainEvent) => {
const pluginList = picgo.pluginLoader.getList()
const list = []
for (let i in pluginList) {
@@ -55,7 +78,7 @@ const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => {
gui = true
}
}
const obj = {
const obj: IPicGoPlugin = {
name: pluginList[i].replace(/picgo-plugin-/, ''),
author: pluginPKG.author.name || pluginPKG.author,
description: pluginPKG.description,
@@ -69,11 +92,11 @@ const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => {
},
uploader: {
name: uploaderName,
config: handleConfigWithFunction(getConfig(uploaderName, 'uploader', picgo))
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.uploader, picgo))
},
transformer: {
name: transformerName,
config: handleConfigWithFunction(getConfig(uploaderName, 'transformer', picgo))
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.transformer, picgo))
}
},
enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`),
@@ -88,45 +111,67 @@ const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => {
})
}
const handlePluginInstall = (ipcMain, CONFIG_PATH) => {
ipcMain.on('installPlugin', (event, msg) => {
const picgo = new PicGo(CONFIG_PATH)
const pluginHandler = new PluginHandler(picgo)
picgo.on('installSuccess', notice => {
const handlePluginInstall = () => {
ipcMain.on('installPlugin', async (event: IpcMainEvent, msg: string) => {
picgo.once('installSuccess', (notice: PicGoNotice) => {
event.sender.send('installSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
shortKeyHandler.registerPluginShortKey(notice.body[0])
picgo.removeAllListeners('installFailed')
})
pluginHandler.install([msg])
picgo.once('installFailed', () => {
handleNPMError()
picgo.removeAllListeners('installSuccess')
})
await picgo.pluginHandler.install([msg])
picgo.cmd.program.removeAllListeners()
})
}
const handlePluginUninstall = (ipcMain, CONFIG_PATH) => {
ipcMain.on('uninstallPlugin', (event, msg) => {
const picgo = new PicGo(CONFIG_PATH)
const pluginHandler = new PluginHandler(picgo)
picgo.on('uninstallSuccess', notice => {
const handlePluginUninstall = () => {
ipcMain.on('uninstallPlugin', async (event: IpcMainEvent, msg: string) => {
picgo.once('uninstallSuccess', (notice: PicGoNotice) => {
event.sender.send('uninstallSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
shortKeyHandler.unregisterPluginShortKey(notice.body[0])
picgo.removeAllListeners('uninstallFailed')
})
pluginHandler.uninstall([msg])
picgo.once('uninstallFailed', () => {
handleNPMError()
picgo.removeAllListeners('uninstallSuccess')
})
await picgo.pluginHandler.uninstall([msg])
picgo.cmd.program.removeAllListeners()
})
}
const handlePluginUpdate = (ipcMain, CONFIG_PATH) => {
ipcMain.on('updatePlugin', (event, msg) => {
const picgo = new PicGo(CONFIG_PATH)
const pluginHandler = new PluginHandler(picgo)
picgo.on('updateSuccess', notice => {
const handlePluginUpdate = () => {
ipcMain.on('updatePlugin', async (event: IpcMainEvent, msg: string) => {
picgo.once('updateSuccess', (notice: { body: string[], title: string }) => {
event.sender.send('updateSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
picgo.removeAllListeners('updateFailed')
})
pluginHandler.update([msg])
picgo.once('updateFailed', () => {
handleNPMError()
picgo.removeAllListeners('updateSuccess')
})
await picgo.pluginHandler.update([msg])
picgo.cmd.program.removeAllListeners()
})
}
const handleGetPicBedConfig = (ipcMain, CONFIG_PATH) => {
ipcMain.on('getPicBedConfig', (event, type) => {
const picgo = new PicGo(CONFIG_PATH)
const handleNPMError = () => {
dialog.showMessageBox({
title: '发生错误',
message: '请安装Node.js并重启PicGo再继续操作',
buttons: ['Yes']
}).then((res) => {
if (res.response === 0) {
shell.openExternal('https://nodejs.org/')
}
})
}
const handleGetPicBedConfig = () => {
ipcMain.on('getPicBedConfig', (event: IpcMainEvent, type: string) => {
const name = picgo.helper.uploader.get(type).name || type
if (picgo.helper.uploader.get(type).config) {
const config = handleConfigWithFunction(picgo.helper.uploader.get(type).config(picgo))
@@ -138,13 +183,12 @@ const handleGetPicBedConfig = (ipcMain, CONFIG_PATH) => {
})
}
const handlePluginActions = (ipcMain, CONFIG_PATH) => {
ipcMain.on('pluginActions', (event, name, label) => {
const picgo = new PicGo(CONFIG_PATH)
const handlePluginActions = () => {
ipcMain.on('pluginActions', (event: IpcMainEvent, name: string, label: string) => {
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) {
const menu = plugin.guiMenu(picgo)
const menu: GuiMenuItem[] = plugin.guiMenu(picgo)
menu.forEach(item => {
if (item.label === label) {
item.handle(picgo, guiApi)
@@ -154,21 +198,28 @@ const handlePluginActions = (ipcMain, CONFIG_PATH) => {
})
}
const handleRemoveFiles = (ipcMain, CONFIG_PATH) => {
ipcMain.on('removeFiles', (event, files) => {
const picgo = new PicGo(CONFIG_PATH)
picgo.emit('remove', files)
const handleRemoveFiles = () => {
ipcMain.on('removeFiles', (event: IpcMainEvent, files: ImgInfo[]) => {
const guiApi = new GuiApi()
setTimeout(() => {
picgo.emit('remove', files, guiApi)
}, 500)
})
}
export default (app, ipcMain) => {
const STORE_PATH = app.getPath('userData')
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
handleGetPluginList(ipcMain, STORE_PATH, CONFIG_PATH)
handlePluginInstall(ipcMain, CONFIG_PATH)
handlePluginUninstall(ipcMain, CONFIG_PATH)
handlePluginUpdate(ipcMain, CONFIG_PATH)
handleGetPicBedConfig(ipcMain, CONFIG_PATH)
handlePluginActions(ipcMain, CONFIG_PATH)
handleRemoveFiles(ipcMain, CONFIG_PATH)
const handlePicGoSaveData = () => {
ipcMain.on('picgoSaveData', (event: IpcMainEvent, data: IObj) => {
picgo.saveConfig(data)
})
}
export default () => {
handleGetPluginList()
handlePluginInstall()
handlePluginUninstall()
handlePluginUpdate()
handleGetPicBedConfig()
handlePluginActions()
handleRemoveFiles()
handlePicGoSaveData()
}
+171
View File
@@ -0,0 +1,171 @@
import bus from './eventBus'
import PicGoCore from '~/universal/types/picgo'
import path from 'path'
import {
app,
globalShortcut,
BrowserWindow
} from 'electron'
import logger from './logger'
import GuiApi from './guiApi'
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 './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()
@@ -1,21 +1,26 @@
import { dialog, shell } from 'electron'
import db from '../../datastore'
import db from '#/datastore'
import axios from 'axios'
import pkg from '../../../package.json'
import pkg from 'root/package.json'
const version = pkg.version
const release = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
let release = 'https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo/package.json'
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
const checkVersion = async () => {
let showTip = db.read().get('settings.showUpdateTip').value()
let showTip = db.get('settings.showUpdateTip')
if (showTip === undefined) {
db.read().set('settings.showUpdateTip', true).write()
db.set('settings.showUpdateTip', true)
showTip = true
}
if (showTip) {
const res = await axios.get(release)
let res: any
try {
res = await axios.get(release)
} catch (err) {
console.log(err)
}
if (res.status === 200) {
const latest = res.data.name
const latest = res.data.version
const result = compareVersion2Update(version, latest)
if (result) {
dialog.showMessageBox({
@@ -25,11 +30,11 @@ const checkVersion = async () => {
message: `发现新版本${latest},更新了很多功能,是否去下载最新的版本?`,
checkboxLabel: '以后不再提醒',
checkboxChecked: false
}, (res, checkboxChecked) => {
if (res === 0) { // if selected yes
}).then(res => {
if (res.response === 0) { // if selected yes
shell.openExternal(downloadUrl)
}
db.read().set('settings.showUpdateTip', !checkboxChecked).write()
db.set('settings.showUpdateTip', !res.checkboxChecked)
})
}
} else {
@@ -41,7 +46,7 @@ const checkVersion = async () => {
}
// 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 latestVersion = latest.split('.').map(item => parseInt(item))
@@ -2,20 +2,19 @@ import {
app,
Notification,
BrowserWindow,
ipcMain
ipcMain,
WebContents
} from 'electron'
import path from 'path'
import dayjs from 'dayjs'
import picgo from '~/main/utils/picgo'
import db from '#/datastore'
// 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 renameURL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page`
: `picgo://./index.html#rename-page`
const createRenameWindow = (win) => {
let options = {
const createRenameWindow = (currentWindow: BrowserWindow) => {
let options: IBrowserWindowOptions = {
height: 175,
width: 300,
show: true,
@@ -23,6 +22,8 @@ const createRenameWindow = (win) => {
resizable: false,
vibrancy: 'ultra-dark',
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
backgroundThrottling: false
}
}
@@ -37,9 +38,9 @@ const createRenameWindow = (win) => {
const window = new BrowserWindow(options)
window.loadURL(renameURL)
// check if this window is visible
if (win.isVisible()) {
if (currentWindow && currentWindow.isVisible()) {
// bounds: { x: 821, y: 75, width: 800, height: 450 }
const bounds = win.getBounds()
const bounds = currentWindow.getBounds()
const positionX = bounds.x + bounds.width / 2 - 150
let positionY
// if is the settingWindow
@@ -53,19 +54,19 @@ const createRenameWindow = (win) => {
return window
}
const waitForShow = (webcontent) => {
const waitForShow = (webcontent: WebContents) => {
return new Promise((resolve, reject) => {
webcontent.on('dom-ready', () => {
webcontent.on('did-finish-load', () => {
resolve()
})
})
}
const waitForRename = (window, id) => {
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
return new Promise((resolve, reject) => {
ipcMain.once(`rename${id}`, (evt, newName) => {
ipcMain.once(`rename${id}`, (evt: Event, newName: string) => {
resolve(newName)
window.hide()
window.close()
})
window.on('close', () => {
resolve(null)
@@ -75,34 +76,44 @@ const waitForRename = (window, id) => {
}
class Uploader {
constructor (img, webContents, picgo = undefined) {
this.img = img
this.webContents = webContents
this.picgo = picgo
private webContents: WebContents | null = null
private currentWindow: BrowserWindow | null = null
constructor () {
this.init()
}
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
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 = picgo.getConfig('settings.rename')
const autoRename = picgo.getConfig('settings.autoRename')
const rename = db.get('settings.rename')
const autoRename = db.get('settings.autoRename')
await Promise.all(ctx.output.map(async (item, index) => {
let name
let fileName
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 = createRenameWindow(win)
const window = createRenameWindow(this.currentWindow!)
await waitForShow(window.webContents)
window.webContents.send('rename', fileName, window.webContents.id)
name = await waitForRename(window, window.webContents.id)
@@ -111,46 +122,40 @@ class Uploader {
}))
}
})
}
picgo.on('beforeTransform', ctx => {
if (ctx.getConfig('settings.uploadNotification')) {
const notification = new Notification({
title: '上传进度',
body: '正在上传'
})
notification.show()
}
})
setWebContents (webContents: WebContents) {
this.webContents = webContents
return this
}
picgo.upload(input)
upload (img?: IUploadOption): Promise<ImgInfo[]|false> {
this.currentWindow = BrowserWindow.fromWebContents(this.webContents!)
picgo.on('notification', message => {
const notification = new Notification(message)
notification.show()
})
picgo.on('uploadProgress', progress => {
this.webContents.send('uploadProgress', progress)
})
picgo.upload(img)
return new Promise((resolve) => {
picgo.on('finished', ctx => {
if (ctx.output.every(item => item.imgUrl)) {
picgo.once('finished', ctx => {
if (ctx.output.every((item: ImgInfo) => item.imgUrl)) {
resolve(ctx.output)
} else {
resolve(false)
}
picgo.removeAllListeners('failed')
})
picgo.on('failed', ctx => {
const notification = new Notification({
title: '上传失败',
body: '请检查配置和上传的文件是否符合要求'
})
notification.show()
picgo.once('failed', ctx => {
setTimeout(() => {
const notification = new Notification({
title: '上传失败',
body: '请检查配置和上传的文件是否符合要求'
})
notification.show()
}, 500)
picgo.removeAllListeners('finished')
resolve(false)
})
})
}
}
export default Uploader
export default new Uploader()
+3 -3
View File
@@ -5,9 +5,9 @@
</template>
<script>
export default {
name: 'picgo'
}
export default {
name: 'picgo'
}
</script>
<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 () {
if (this.type === this.$db.get('picBed.current').value()) {
if (this.type === this.$db.get('picBed.current')) {
this.value = true
}
},
methods: {
choosePicBed (val) {
this.$db.set('picBed.current', this.type)
this.letPicGoSaveData({
'picBed.current': this.type
})
this.$emit('update:choosed', this.type)
}
}
}
</script>
<style lang='stylus'>
</style>
</style>
+56 -61
View File
@@ -9,7 +9,7 @@
>
<el-form-item
v-for="(item, index) in configList"
:label="item.name"
:label="item.alias || item.name"
:required="item.required"
:prop="item.name"
:key="item.name + index"
@@ -26,7 +26,7 @@
:placeholder="item.message || item.name"
>
<el-option
v-for="(choice, idx) in item.choices"
v-for="choice in item.choices"
:label="choice.name || choice.value || choice"
:key="choice.name || choice.value || choice"
:value="choice.value || choice"
@@ -40,7 +40,7 @@
collapse-tags
>
<el-option
v-for="(choice, idx) in item.choices"
v-for="choice in item.choices"
:label="choice.name || choice.value || choice"
:key="choice.value || choice"
:value="choice.value || choice"
@@ -58,68 +58,63 @@
</el-form>
</div>
</template>
<script>
<script lang="ts">
import {
Component,
Vue,
Prop,
Watch
} from 'vue-property-decorator'
import { cloneDeep, union } from 'lodash'
export default {
name: 'config-form',
props: {
config: Array,
type: String,
id: {
type: String,
default: ''
}
},
data () {
return {
configList: [],
ruleForm: {}
}
},
created () {
},
watch: {
config: {
deep: true,
handler (val) {
this.ruleForm = Object.assign({}, {})
const config = this.$db.read().get(`picBed.${this.id}`).value()
if (val.length > 0) {
this.configList = cloneDeep(val).map(item => {
let defaultValue = item.default !== undefined
? item.default : item.type === 'checkbox'
? [] : null
if (item.type === 'checkbox') {
const defaults = item.choices.filter(i => {
return i.checked
}).map(i => i.value)
defaultValue = union(defaultValue, defaults)
}
if (config && config[item.name] !== undefined) {
defaultValue = config[item.name]
}
this.$set(this.ruleForm, item.name, defaultValue)
return item
})
@Component({
name: 'config-form'
})
export default class extends Vue {
@Prop() private config!: any[]
@Prop() readonly type!: string
@Prop() readonly id!: string
configList = []
ruleForm = {}
@Watch('config', {
deep: true,
immediate: true
})
handleConfigChange (val: any) {
this.ruleForm = Object.assign({}, {})
const config = this.$db.get(`picBed.${this.id}`)
if (val.length > 0) {
this.configList = cloneDeep(val).map((item: any) => {
let defaultValue = item.default !== undefined
? item.default : item.type === 'checkbox'
? [] : null
if (item.type === 'checkbox') {
const defaults = item.choices.filter((i: any) => {
return i.checked
}).map((i: any) => i.value)
defaultValue = union(defaultValue, defaults)
}
},
immediate: true
}
},
methods: {
async validate () {
return new Promise((resolve, reject) => {
this.$refs.form.validate(valid => {
if (valid) {
resolve(this.ruleForm)
} else {
resolve(false)
return false
}
})
if (config && config[item.name] !== undefined) {
defaultValue = config[item.name]
}
this.$set(this.ruleForm, item.name, defaultValue)
return item
})
}
}
async validate () {
return new Promise((resolve, reject) => {
// @ts-ignore
this.$refs.form.validate((valid: boolean) => {
if (valid) {
resolve(this.ruleForm)
} else {
resolve(false)
return false
}
})
})
}
}
</script>
<style lang='stylus'>
@@ -139,4 +134,4 @@ export default {
.el-switch__label
&.is-active
color #409EFF
</style>
</style>
+158 -157
View File
@@ -91,29 +91,6 @@
</el-col>
</el-row>
</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
title="自定义链接格式"
:visible.sync="customLinkVisible"
@@ -128,7 +105,7 @@
label="用占位符$url来表示url的位置"
prop="value"
>
<el-input
<el-input
class="align-center"
v-model="customLink.value"
:autofocus="true"
@@ -143,152 +120,176 @@
<el-button type="primary" @click="confirmCustomLink">确定</el-button>
</span>
</el-dialog>
<el-dialog
:title="inputBoxOptions.title || '输入框'"
:visible.sync="showInputBoxVisible"
:modal-append-to-body="false"
@close="handleInputBoxClose"
>
<el-input
v-model="inputBoxValue"
:placeholder="inputBoxOptions.placeholder"></el-input>
<span slot="footer">
<el-button @click="showInputBoxVisible = false" round>取消</el-button>
<el-button type="primary" @click="showInputBoxVisible = false" round>确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import pkg from 'root/package.json'
import keyDetect from 'utils/key-binding'
import { remote } from 'electron'
import db from '~/datastore'
import keyDetect from '@/utils/key-binding'
import { remote, ipcRenderer, IpcRendererEvent } from 'electron'
import db from '#/datastore'
import mixin from '@/utils/mixin'
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',
mixins: [mixin],
data () {
const customLinkRule = (rule, value, callback) => {
if (!/\$url/.test(value)) {
return callback(new Error('必须含有$url'))
} else {
return callback()
}
}
return {
version: process.env.NODE_ENV === 'production' ? pkg.version : 'Dev',
defaultActive: 'upload',
menu: null,
visible: false,
keyBindingVisible: false,
customLinkVisible: false,
customLink: {
value: db.read().get('customLink').value() || '$url'
},
rules: {
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
},
os: '',
shortKey: {
upload: db.read().get('shortKey.upload').value()
},
picBed: []
}
},
mixins: [mixin]
})
export default class extends Vue {
version = process.env.NODE_ENV === 'production' ? pkg.version : 'Dev'
defaultActive = 'upload'
menu: Electron.Menu | null = null
visible = false
keyBindingVisible = false
customLinkVisible = false
customLink = {
value: db.get('customLink') || '$url'
}
rules = {
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
}
os = ''
shortKey: IShortKeyMap = {
upload: db.get('shortKey.upload')
}
picBed: IPicBedType[] = []
// for showInputBox
showInputBoxVisible = false
inputBoxValue = ''
inputBoxOptions = {
title: '',
placeholder: ''
}
created () {
this.os = process.platform
this.buildMenu()
this.$electron.ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds)
},
methods: {
handleSelect (index) {
const type = index.match(/picbeds-/)
if (type === null) {
ipcRenderer.send('getPicBeds')
ipcRenderer.on('getPicBeds', this.getPicBeds)
ipcRenderer.on('showInputBox', (evt: IpcRendererEvent, options: IShowInputBoxOption) => {
this.inputBoxValue = ''
this.inputBoxOptions.title = options.title || ''
this.inputBoxOptions.placeholder = options.placeholder || ''
this.showInputBoxVisible = true
})
}
handleSelect (index: string) {
const type = index.match(/picbeds-/)
if (type === null) {
this.$router.push({
name: index
})
} else {
const picBed = index.replace(/picbeds-/, '')
if (this.$builtInPicBed.includes(picBed)) {
this.$router.push({
name: index
name: picBed
})
} else {
const picBed = index.replace(/picbeds-/, '')
if (this.$builtInPicBed.includes(picBed)) {
this.$router.push({
name: picBed
})
} else {
this.$router.push({
name: 'others',
params: {
type: picBed
}
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 () {
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
}
}
]
this.menu = Menu.buildFromTemplate(template)
},
openDialog () {
this.menu.popup(remote.getCurrentWindow())
},
keyDetect (type, event) {
this.shortKey[type] = keyDetect(event).join('+')
},
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = db.read().get('shortKey').value()
},
confirmKeyBinding () {
const oldKey = db.read().get('shortKey').value()
db.read().set('shortKey', this.shortKey).write()
this.keyBindingVisible = false
this.$electron.ipcRenderer.send('updateShortKey', oldKey)
},
cancelCustomLink () {
this.customLinkVisible = false
this.customLink.value = db.read().get('customLink').value() || '$url'
},
confirmCustomLink () {
this.$refs.customLink.validate((valid) => {
if (valid) {
db.read().set('customLink', this.customLink.value).write()
this.customLinkVisible = false
this.$electron.ipcRenderer.send('updateCustomLink')
} else {
return false
}
})
},
openMiniWindow () {
this.$electron.ipcRenderer.send('openMiniWindow')
},
getPicBeds (event, picBeds) {
this.picBed = picBeds
}
},
beforeRouteEnter: (to, from, next) => {
next(vm => {
]
this.menu = Menu.buildFromTemplate(template)
}
openDialog () {
// this.menu!.popup(remote.getCurrentWindow())
this.menu!.popup()
}
keyDetect (type: string, event: KeyboardEvent) {
this.shortKey[type] = keyDetect(event).join('+')
}
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = db.get('shortKey')
}
cancelCustomLink () {
this.customLinkVisible = false
this.customLink.value = db.get('customLink') || '$url'
}
confirmCustomLink () {
// @ts-ignore
this.$refs.customLink.validate((valid: boolean) => {
if (valid) {
db.set('customLink', this.customLink.value)
this.customLinkVisible = false
ipcRenderer.send('updateCustomLink')
} else {
return false
}
})
}
openMiniWindow () {
ipcRenderer.send('openMiniWindow')
}
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
this.picBed = picBeds
}
handleInputBoxClose () {
ipcRenderer.send('showInputBox', this.inputBoxValue)
}
beforeRouteEnter (to: any, from: any, next: any) {
next((vm: this) => {
vm.defaultActive = to.name
})
},
}
beforeDestroy () {
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
ipcRenderer.removeAllListeners('showInputBox')
}
}
</script>
@@ -333,12 +334,12 @@ $darwinBg = transparentify(#172426, #000, 0.7)
position absolute
top 2px
right 4px
width 60px
z-index 10000
-webkit-app-region no-drag
i
cursor pointer
font-size 16px
margin-left 5px
.el-icon-minus
&:hover
color #409EFF
@@ -358,7 +359,7 @@ $darwinBg = transparentify(#172426, #000, 0.7)
overflow-y auto
width 170px
.el-icon-info.setting-window
position fixed
position fixed
bottom 4px
left 4px
cursor poiter
@@ -382,7 +383,7 @@ $darwinBg = transparentify(#172426, #000, 0.7)
&:before
content ''
position absolute
width 3px
width 3px
height 20px
right 0
top 18px
@@ -404,7 +405,7 @@ $darwinBg = transparentify(#172426, #000, 0.7)
padding-top 22px
position relative
z-index 10
.el-dialog__body
.el-dialog__body
padding 20px
.support
text-align center
@@ -422,4 +423,4 @@ $darwinBg = transparentify(#172426, #000, 0.7)
background #6f6f6f
*::-webkit-scrollbar-track
background-color transparent
</style>
</style>
+229 -222
View File
@@ -72,18 +72,18 @@
:options="options"
></gallerys>
<el-col :span="6" v-for="(item, index) in images" :key="item.id" class="gallery-list__img">
<div
<div
class="gallery-list__item"
v-lazy:background-image="item.imgUrl"
@click="zoomImage(index)"
>
<img v-lazy="item.imgUrl" class="gallery-list__item-img">
</div>
<div class="gallery-list__tool-panel">
<i class="el-icon-document" @click="copy(item)"></i>
<i class="el-icon-edit-outline" @click="openDialog(item)"></i>
<i class="el-icon-delete" @click="remove(item.id)"></i>
<el-checkbox v-model="choosedList[item.id]" class="pull-right" @change=" handleBarActive = true"></el-checkbox>
</div>
</div>
</el-col>
</el-row>
</el-col>
@@ -102,260 +102,266 @@
</el-dialog>
</div>
</template>
<script>
<script lang="ts">
// @ts-ignore
import gallerys from 'vue-gallery'
import pasteStyle from '~/main/utils/pasteTemplate'
export default {
import pasteStyle from '#/utils/pasteTemplate'
import { Component, Vue } from 'vue-property-decorator'
import {
ipcRenderer,
clipboard,
IpcRendererEvent
} from 'electron'
@Component({
name: 'gallery',
components: {
gallerys
},
data () {
return {
images: [],
idx: null,
options: {
titleProperty: 'fileName',
urlProperty: 'imgUrl',
closeOnSlideClick: true
},
dialogVisible: false,
imgInfo: {
id: null,
imgUrl: ''
},
choosedList: {},
choosedPicBed: [],
searchText: '',
handleBarActive: false,
pasteStyle: '',
pasteStyleMap: {
Markdown: 'markdown',
HTML: 'HTML',
URL: 'URL',
UBB: 'UBB',
Custom: 'Custom'
},
picBed: []
}
},
beforeRouteEnter (to, from, next) {
next(vm => {
}
})
export default class extends Vue {
images: ImgInfo[] = []
idx: null | number = null
options = {
titleProperty: 'fileName',
urlProperty: 'imgUrl',
closeOnSlideClick: true
}
dialogVisible = false
imgInfo = {
id: null,
imgUrl: ''
}
choosedList: IObjT<boolean> = {}
choosedPicBed: string[] = []
searchText = ''
handleBarActive = false
pasteStyle = ''
pasteStyleMap = {
Markdown: 'markdown',
HTML: 'HTML',
URL: 'URL',
UBB: 'UBB',
Custom: 'Custom'
}
picBed: IPicBedType[] = []
beforeRouteEnter (to: any, from: any, next: any) {
next((vm: any) => {
vm.getGallery()
vm.getPasteStyle()
vm.getPicBeds()
})
},
}
created () {
this.$electron.ipcRenderer.on('updateGallery', (event) => {
ipcRenderer.on('updateGallery', (event: IpcRendererEvent) => {
this.$nextTick(() => {
this.filterList = this.getGallery()
})
})
this.$electron.ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds)
},
computed: {
filterList: {
get () {
return this.getGallery()
},
set (val) {
return this.val
ipcRenderer.send('getPicBeds')
ipcRenderer.on('getPicBeds', this.getPicBeds)
}
get filterList () {
return this.getGallery()
}
set filterList (val) {
this.images = 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()
}
}
},
methods: {
getPicBeds (event, picBeds) {
this.picBed = picBeds
},
getGallery () {
if (this.choosedPicBed.length > 0) {
let arr = []
this.choosedPicBed.forEach(item => {
let obj = {
type: item
}
if (this.searchText) {
obj.fileName = this.searchText
}
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')
.filter(item => {
return item.fileName.indexOf(this.searchText) !== -1
}).reverse().value()
this.images = data
} else {
this.images = this.$db.read().get('uploaded').slice().reverse().value()
}
}
return this.images
},
zoomImage (index) {
this.idx = index
this.changeZIndexForGallery(true)
},
changeZIndexForGallery (isOpen) {
if (isOpen) {
document.querySelector('.main-content.el-row').style.zIndex = 101
} else {
document.querySelector('.main-content.el-row').style.zIndex = 10
}
},
handleClose () {
this.idx = null
this.changeZIndexForGallery(false)
},
copy (item) {
const url = item.url || item.imgUrl
const style = this.$db.read().get('settings.pasteStyle').value() || 'markdown'
const copyLink = pasteStyle(style, url)
return this.images
}
zoomImage (index: number) {
this.idx = index
this.changeZIndexForGallery(true)
}
changeZIndexForGallery (isOpen: boolean) {
if (isOpen) {
// @ts-ignore
document.querySelector('.main-content.el-row').style.zIndex = 101
} else {
// @ts-ignore
document.querySelector('.main-content.el-row').style.zIndex = 10
}
}
handleClose () {
this.idx = null
this.changeZIndexForGallery(false)
}
copy (item: ImgInfo) {
const style = this.$db.get('settings.pasteStyle') || 'markdown'
const copyLink = pasteStyle(style, item)
const obj = {
title: '复制链接成功',
body: copyLink,
icon: item.url || item.imgUrl
}
const myNotification = new Notification(obj.title, obj)
clipboard.writeText(copyLink)
myNotification.onclick = () => {
return true
}
}
remove (id: string) {
this.$confirm('此操作将把该图片移出相册, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const file = this.$db.get('uploaded').getById(id)
// @ts-ignore
this.$db.read().get('uploaded').removeById(id).write()
ipcRenderer.send('removeFiles', [file])
const obj = {
title: '复制链接成功',
body: copyLink,
icon: url
title: '操作结果',
body: '删除成功'
}
const myNotification = new window.Notification(obj.title, obj)
this.$electron.clipboard.writeText(copyLink)
const myNotification = new Notification(obj.title, obj)
myNotification.onclick = () => {
return true
}
},
remove (id) {
this.$confirm('此操作将把该图片移出相册, 是否继续?', '提示', {
this.getGallery()
}).catch(() => {
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: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const file = this.$db.read().get('uploaded').getById(id).value()
this.$db.read().get('uploaded').removeById(id).write()
this.$electron.ipcRenderer.send('removeFiles', [file])
let files: ImgInfo[] = []
Object.keys(this.choosedList).forEach(key => {
if (this.choosedList[key]) {
// @ts-ignore
const file = this.$db.read().get('uploaded').getById(key).value()
files.push(file)
// @ts-ignore
this.$db.read().get('uploaded').removeById(key).write()
}
})
this.choosedList = {}
this.getGallery()
const obj = {
title: '操作结果',
body: '删除成功'
}
const myNotification = new window.Notification(obj.title, obj)
ipcRenderer.send('removeFiles', files)
const myNotification = new Notification(obj.title, obj)
myNotification.onclick = () => {
return true
}
this.getGallery()
}).catch(() => {
return true
})
},
openDialog (item) {
this.imgInfo.id = item.id
this.imgInfo.imgUrl = item.imgUrl
this.dialogVisible = true
},
confirmModify () {
this.$db.read().get('uploaded')
.getById(this.imgInfo.id)
.assign({imgUrl: this.imgInfo.imgUrl})
.write()
}
}
multiCopy () {
if (Object.values(this.choosedList).some(item => item)) {
let copyString = ''
const style = this.$db.get('settings.pasteStyle') || 'markdown'
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
Object.keys(this.choosedList).forEach(key => {
if (this.choosedList[key]) {
// @ts-ignore
const item = this.$db.read().get('uploaded').getById(key).value()
copyString += pasteStyle(style, item) + '\n'
this.choosedList[key] = false
}
})
const obj = {
title: '修改图片URL成功',
body: this.imgInfo.imgUrl,
icon: this.imgInfo.imgUrl
title: '批量复制链接成功',
body: copyString
}
const myNotification = new window.Notification(obj.title, obj)
const myNotification = new Notification(obj.title, obj)
clipboard.writeText(copyString)
myNotification.onclick = () => {
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()
const url = item.url || item.imgUrl
copyString += pasteStyle(style, url) + '\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 () {
this.$electron.ipcRenderer.removeAllListeners('updateGallery')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
ipcRenderer.removeAllListeners('updateGallery')
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}
}
</script>
@@ -423,22 +429,23 @@ export default {
&__item
width 100%
height 120px
background-size cover
background-position 50% 50%
background-repeat no-repeat
transition all .2s ease-in-out
cursor pointer
margin-bottom 8px
overflow hidden
display flex
&-fake
position absolute
top 0
left 0
left 0
opacity 0
width 100%
z-index -1
&:hover
background-color #49B1F5
transform scale(1.1)
&-img
width 100%
object-fit fill
&__tool-panel
color #ddd
i
@@ -458,4 +465,4 @@ export default {
margin-bottom 10px
.el-input__inner
border-radius 14px
</style>
</style>
+153 -145
View File
@@ -15,31 +15,36 @@
</div>
</div>
</template>
<script>
<script lang="ts">
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',
mixins: [mixin],
data () {
return {
logo: 'static/squareLogo.png',
dragover: false,
progress: 0,
showProgress: false,
showError: false,
dragging: false,
wX: '',
wY: '',
screenX: '',
screenY: '',
menu: null,
os: '',
picBed: []
}
},
mixins: [mixin]
})
export default class extends Vue {
logo = require('../assets/squareLogo.png')
dragover = false
progress = 0
showProgress = false
showError = false
dragging = false
wX: number = -1
wY: number = -1
screenX: number = -1
screenY: number = -1
menu: Electron.Menu | null = null
os = ''
picBed: IPicBedType[] = []
created () {
this.os = process.platform
this.$electron.ipcRenderer.on('uploadProgress', (event, progress) => {
ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, progress: number) => {
if (progress !== -1) {
this.showProgress = true
this.progress = progress
@@ -49,140 +54,143 @@ export default {
}
})
this.getPicBeds()
},
}
mounted () {
window.addEventListener('mousedown', this.handleMouseDown, false)
window.addEventListener('mousemove', this.handleMouseMove, false)
window.addEventListener('mouseup', this.handleMouseUp, false)
},
watch: {
progress (val) {
if (val === 100) {
setTimeout(() => {
this.showProgress = false
this.showError = false
}, 1000)
setTimeout(() => {
this.progress = 0
}, 1200)
}
@Watch('progress')
onProgressChange (val: number) {
if (val === 100) {
setTimeout(() => {
this.showProgress = false
this.showError = false
}, 1000)
setTimeout(() => {
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: {
getPicBeds () {
this.picBed = this.$electron.ipcRenderer.sendSync('getPicBeds')
this.buildMenu()
},
onDrop (e) {
this.dragover = false
this.ipcSendFiles(e.dataTransfer.files)
},
openUploadWindow () {
document.getElementById('file-uploader').click()
},
onChange (e) {
this.ipcSendFiles(e.target.files)
document.getElementById('file-uploader').value = ''
},
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 () {
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')
}
}
},
openContextMenu () {
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')
}
})
const template = [
{
label: '打开详细窗口',
click () {
ipcRenderer.send('openSettingWindow')
}
})
const template = [
{
label: '打开详细窗口',
click () {
_this.$electron.ipcRenderer.send('openSettingWindow')
}
},
{
label: '选择默认图床',
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: '退出'
},
{
label: '选择默认图床',
type: 'submenu',
submenu
},
{
label: '剪贴板图片上传',
click () {
ipcRenderer.send('uploadClipboardFilesFromUploadPage')
}
]
this.menu = this.$electron.remote.Menu.buildFromTemplate(template)
}
},
},
{
label: '最小化窗口',
role: 'minimize'
},
{
label: '重启应用',
click () {
remote.app.relaunch()
remote.app.exit(0)
}
},
{
role: 'quit',
label: '退出'
}
]
// @ts-ignore
this.menu = remote.Menu.buildFromTemplate(template)
}
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('uploadProgress')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
ipcRenderer.removeAllListeners('uploadProgress')
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
window.removeEventListener('mousedown', this.handleMouseDown, false)
window.removeEventListener('mousemove', this.handleMouseMove, false)
window.removeEventListener('mouseup', this.handleMouseUp, false)
@@ -225,4 +233,4 @@ export default {
background rgba(0,0,0,0.3)
#file-uploader
display none
</style>
</style>
+398 -205
View File
@@ -1,7 +1,7 @@
<template>
<div id="picgo-setting">
<div class="view-title">
PicGo设置
PicGo设置 - <i class="el-icon-document" @click="goConfigPage"></i>
</div>
<el-row class="setting-list">
<el-col :span="15" :offset="4">
@@ -14,12 +14,17 @@
<el-form-item
label="打开配置文件"
>
<el-button type="primary" round size="mini" @click="openConfigFile">点击打开</el-button>
<el-button type="primary" round size="mini" @click="openFile('data.json')">点击打开</el-button>
</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="openLogSetting">点击设置</el-button>
</el-form-item>
<el-form-item
label="修改快捷键"
>
<el-button type="primary" round size="mini" @click="goShortCutPage">点击设置</el-button>
</el-form-item>
<el-form-item
label="自定义链接格式"
@@ -31,6 +36,11 @@
>
<el-button type="primary" round size="mini" @click="proxyVisible = true">点击设置</el-button>
</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
label="检查更新"
>
@@ -115,30 +125,6 @@
</el-row>
</el-col>
</el-row>
<el-dialog
title="修改上传快捷键"
:visible.sync="keyBindingVisible"
:modal-append-to-body="false"
>
<el-form
label-width="80px"
>
<el-form-item
label="快捷上传"
>
<el-input
class="align-center"
@keydown.native.prevent="keyDetect('upload', $event)"
v-model="shortKey.upload"
:autofocus="true"
></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="cancelKeyBinding" round>取消</el-button>
<el-button type="primary" @click="confirmKeyBinding" round>确定</el-button>
</span>
</el-dialog>
<el-dialog
title="自定义链接格式"
:visible.sync="customLinkVisible"
@@ -149,12 +135,18 @@
:model="customLink"
ref="customLink"
:rules="rules"
size="small"
>
<el-form-item
label="用占位符$url来表示url的位置"
prop="value"
>
<el-input
<div class="custom-title">
用占位符 <b>$url</b> 来表示url的位置
</div>
<div class="custom-title">
用占位符 <b>$fileName</b> 来表示文件名的位置
</div>
<el-input
class="align-center"
v-model="customLink.value"
:autofocus="true"
@@ -162,7 +154,7 @@
</el-form-item>
</el-form>
<div>
[]($url)
[$fileName]($url)
</div>
<span slot="footer">
<el-button @click="cancelCustomLink" round>取消</el-button>
@@ -184,7 +176,7 @@
<el-form-item
label="代理地址"
>
<el-input
<el-input
v-model="proxy"
:autofocus="true"
placeholder="例如:http://127.0.0.1:1080"
@@ -215,200 +207,394 @@
<el-button type="primary" @click="confirmCheckVersion" round>确定</el-button>
</span>
</el-dialog>
<el-dialog
title="设置日志文件"
:visible.sync="logFileVisible"
:modal-append-to-body="false"
>
<el-form
label-position="right"
label-width="100px"
>
<el-form-item
label="日志文件"
>
<el-button type="primary" round size="mini" @click="openFile('picgo.log')">点击打开</el-button>
</el-form-item>
<el-form-item
label="日志记录等级"
>
<el-select
v-model="form.logLevel"
multiple
collapse-tags
>
<el-option
v-for="(value, key) of logLevel"
:key="key"
:label="value"
:value="key"
:disabled="handleLevelDisabled(key)"
></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="cancelLogLevelSetting" round>取消</el-button>
<el-button type="primary" @click="confirmLogLevelSetting" round>确定</el-button>
</span>
</el-dialog>
<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>
</template>
<script>
import keyDetect from 'utils/key-binding'
<script lang="ts">
import keyDetect from '@/utils/key-binding'
import pkg from 'root/package.json'
import path from 'path'
import {
ipcRenderer,
remote
} from 'electron'
import { Component, Vue } from 'vue-property-decorator'
import db from '#/datastore'
const release = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
export default {
name: 'picgo-setting',
computed: {
needUpdate () {
if (this.latestVersion) {
return this.compareVersion2Update(this.version, this.latestVersion)
const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) => void) => {
if (!/\$url/.test(value)) {
return callback(new Error('必须含有$url'))
} else {
return callback()
}
}
let logLevel = db.get('settings.logLevel')
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
@Component({
name: 'picgo-setting'
})
export default class extends Vue {
form: ISettingForm = {
updateHelper: db.get('settings.showUpdateTip'),
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 {
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 () {
const customLinkRule = (rule, value, callback) => {
if (!/\$url/.test(value)) {
return callback(new Error('必须含有$url'))
}
updateHelperChange (val: boolean) {
db.set('settings.showUpdateTip', val)
}
handleShowPicBedListChange (val: string[]) {
const list = this.picBed.map(item => {
if (!val.includes(item.name)) {
item.visible = false
} else {
return callback()
item.visible = true
}
}
return {
form: {
updateHelper: this.$db.read().get('settings.showUpdateTip').value(),
showPicBedList: [],
autoStart: this.$db.read().get('settings.autoStart').value() || false,
rename: this.$db.read().get('settings.rename').value() || false,
autoRename: this.$db.read().get('settings.autoRename').value() || false,
uploadNotification: this.$db.read().get('settings.uploadNotification').value() || false,
miniWindowOntop: this.$db.read().get('settings.miniWindowOntop').value() || false
},
picBed: [],
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' }
]
},
version: pkg.version,
latestVersion: '',
os: ''
}
},
created () {
this.os = process.platform
this.$electron.ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds)
},
methods: {
getPicBeds (event, picBeds) {
this.picBed = picBeds
this.form.showPicBedList = this.picBed.map(item => {
if (item.visible) {
return item.name
}
})
},
openConfigFile () {
const { app, shell } = this.$electron.remote
const STORE_PATH = app.getPath('userData')
const CONFIG_FILE = path.join(STORE_PATH, '/data.json')
shell.openItem(CONFIG_FILE)
},
keyDetect (type, event) {
this.shortKey[type] = keyDetect(event).join('+')
},
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = 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 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
}
},
updateHelperChange (val) {
this.$db.read().set('settings.showUpdateTip', val).write()
},
handleShowPicBedListChange (val) {
const list = this.picBed.map(item => {
if (!val.includes(item.name)) {
item.visible = false
} else {
item.visible = true
}
return item
})
this.$db.read().set('picBed.list', list).write()
this.$electron.ipcRenderer.send('getPicBeds')
},
handleAutoStartChange (val) {
this.$db.read().set('settings.autoStart', val).write()
this.$electron.ipcRenderer.send('autoStart', val)
},
handleRename (val) {
this.$db.read().set('settings.rename', val).write()
},
handleAutoRename (val) {
this.$db.read().set('settings.autoRename', val).write()
},
compareVersion2Update (current, latest) {
const currentVersion = current.split('.').map(item => parseInt(item))
const latestVersion = latest.split('.').map(item => parseInt(item))
for (let i = 0; i < 3; i++) {
if (currentVersion[i] < latestVersion[i]) {
return true
}
if (currentVersion[i] > latestVersion[i]) {
return false
}
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('需要重启生效')
}
},
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) {
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 (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
this.form.logLevel = logLevel
}
confirmServerSetting () {
this.letPicGoSaveData({
'settings.server': this.server
})
const successNotification = new Notification('设置PicGo-Server', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
this.serverVisible = false
ipcRenderer.send('updateServer')
}
cancelServerSetting () {
this.serverVisible = false
this.server = db.get('settings.server') || {
port: 36677,
host: '127.0.0.1',
enable: true
}
}
handleLevelDisabled (val: string) {
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 () {
remote.shell.openExternal('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
}
goShortCutPage () {
this.$router.push('shortKey')
}
beforeDestroy () {
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}
}
</script>
<style lang='stylus'>
.el-message
left 60%
.view-title
.el-icon-document
cursor pointer
transition color .2s ease-in-out
&:hover
color #49B1F5
#picgo-setting
.sub-title
font-size 14px
@@ -419,7 +605,7 @@ export default {
overflow-x hidden
.setting-list
.el-form
label
label
line-height 32px
padding-bottom 0
color #eee
@@ -453,4 +639,11 @@ export default {
margin-left 0
.confirm-button
width 100%
</style>
.server-dialog
.notice-text
color: #49B1F5
.el-dialog__body
padding-top: 0
.el-form-item
margin-bottom: 10px
</style>
+300 -285
View File
@@ -17,7 +17,7 @@
<div class="plugin-item" :class="{ 'darwin': os === 'darwin' }">
<div class="cli-only-badge" v-if="!item.gui" title="CLI only">CLI</div>
<img class="plugin-item__logo" :src="item.logo"
onerror="this.src='static/roundLogo.png'"
:onerror="defaultLogo"
>
<div
class="plugin-item__content"
@@ -91,81 +91,78 @@
<el-button type="primary" @click="handleConfirmConfig" round>确定</el-button>
</span>
</el-dialog>
<el-dialog
:title="inputBoxOptions.title || '输入框'"
:visible.sync="showInputBoxVisible"
:modal-append-to-body="false"
@close="handleInputBoxClose"
>
<el-input
v-model="inputBoxValue"
:placeholder="inputBoxOptions.placeholder"></el-input>
<span slot="footer">
<el-button @click="showInputBoxVisible = false" round>取消</el-button>
<el-button type="primary" @click="showInputBoxVisible = false" round>确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import ConfigForm from '@/components/ConfigForm'
<script lang="ts">
import {
Component,
Vue,
Watch
} from 'vue-property-decorator'
import ConfigForm from '@/components/ConfigForm.vue'
import { debounce } from 'lodash'
export default {
import {
ipcRenderer,
remote,
IpcRendererEvent
} from 'electron'
const { Menu } = remote
@Component({
name: 'plugin',
components: {
ConfigForm
},
data () {
return {
searchText: '',
pluginList: [],
menu: null,
config: [],
currentType: '',
configName: '',
dialogVisible: false,
pluginNameList: [],
loading: true,
needReload: false,
id: '',
os: '',
// for showInputBox
showInputBoxVisible: false,
inputBoxValue: '',
inputBoxOptions: {
title: '',
placeholder: ''
}
}
})
export default class extends Vue {
searchText = ''
pluginList: IPicGoPlugin[] = []
menu: Electron.Menu | null = null
config: any[] = []
currentType = ''
configName = ''
dialogVisible = false
pluginNameList: string[] = []
loading = true
needReload = false
id = ''
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: {
npmSearchText () {
return this.searchText.match('picgo-plugin-')
? this.searchText
: this.searchText !== ''
? `picgo-plugin-${this.searchText}`
: this.searchText
}
@Watch('dialogVisible')
onDialogVisible (val: boolean) {
if (val) {
// @ts-ignore
document.querySelector('.main-content.el-row').style.zIndex = 101
} else {
// @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 () {
this.os = process.platform
this.$electron.ipcRenderer.on('pluginList', (evt, list) => {
ipcRenderer.on('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => {
this.pluginList = list
this.pluginNameList = list.map(item => item.name)
this.loading = false
})
this.$electron.ipcRenderer.on('installSuccess', (evt, plugin) => {
ipcRenderer.on('installSuccess', (evt: IpcRendererEvent, plugin: string) => {
this.loading = false
this.pluginList.forEach(item => {
if (item.name === plugin) {
@@ -174,7 +171,7 @@ export default {
}
})
})
this.$electron.ipcRenderer.on('updateSuccess', (evt, plugin) => {
ipcRenderer.on('updateSuccess', (evt: IpcRendererEvent, plugin: string) => {
this.loading = false
this.pluginList.forEach(item => {
if (item.name === plugin) {
@@ -186,7 +183,7 @@ export default {
this.handleReload()
this.getPluginList()
})
this.$electron.ipcRenderer.on('uninstallSuccess', (evt, plugin) => {
ipcRenderer.on('uninstallSuccess', (evt: IpcRendererEvent, plugin: string) => {
this.loading = false
this.pluginList = this.pluginList.filter(item => {
if (item.name === plugin) { // restore Uploader & Transformer after uninstalling
@@ -204,252 +201,267 @@ export default {
})
this.getPluginList()
this.getSearchResult = debounce(this.getSearchResult, 50)
this.needReload = this.$db.read().get('needReload').value()
this.$electron.ipcRenderer.on('showInputBox', (evt, options) => {
this.inputBoxValue = ''
this.inputBoxOptions.title = options.title || ''
this.inputBoxOptions.placeholder = options.placeholder || ''
this.showInputBoxVisible = true
})
},
methods: {
buildContextMenu (plugin) {
const _this = this
let menu = [{
label: '启用插件',
enabled: !plugin.enabled,
click () {
_this.$db.read().set(`picgoPlugins.picgo-plugin-${plugin.name}`, true).write()
plugin.enabled = true
_this.getPicBeds()
this.needReload = this.$db.get('needReload')
}
buildContextMenu (plugin: IPicGoPlugin) {
const _this = this
let menu = [{
label: '启用插件',
enabled: !plugin.enabled,
click () {
_this.letPicGoSaveData({
[`picgoPlugins.picgo-plugin-${plugin.name}`]: true
})
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)
}
}, {
label: '禁用插件',
enabled: plugin.enabled,
click () {
_this.$db.read().set(`picgoPlugins.picgo-plugin-${plugin.name}`, false).write()
plugin.enabled = false
_this.getPicBeds()
if (plugin.config.transformer.name) {
_this.handleRestoreState('transformer', plugin.config.transformer.name)
}
if (plugin.config.uploader.name) {
_this.handleRestoreState('uploader', plugin.config.uploader.name)
}
}
}, {
label: '卸载插件',
click () {
_this.uninstallPlugin(plugin.name)
}
}, {
label: '更新插件',
click () {
_this.updatePlugin(plugin.name)
}
}]
for (let i in plugin.config) {
if (plugin.config[i].config.length > 0) {
const obj = {
label: `配置${i} - ${plugin.config[i].name}`,
click () {
_this.currentType = i
_this.configName = plugin.config[i].name
_this.dialogVisible = true
_this.config = plugin.config[i].config
}
}
menu.push(obj)
if (plugin.config.uploader.name) {
_this.handleRestoreState('uploader', plugin.config.uploader.name)
}
}
// handle transformer
if (plugin.config.transformer.name) {
let currentTransformer = this.$db.read().get('picBed.transformer').value() || 'path'
let pluginTransformer = plugin.config.transformer.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: `${currentTransformer === pluginTransformer ? '禁用' : '启用'}transformer - ${plugin.config.transformer.name}`,
label: `配置${i} - ${plugin.config[i].name}`,
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)
}
}
// plugin custom menus
if (plugin.guiMenu) {
// handle transformer
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({
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.popup(this.$electron.remote.getCurrentWindow())
},
getPluginList () {
this.$electron.ipcRenderer.send('getPluginList')
},
getPicBeds () {
this.$electron.ipcRenderer.send('getPicBeds')
},
installPlugin (item) {
if (!item.gui) {
this.$confirm('该插件未对可视化界面进行优化, 是否继续安装?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
item.ing = true
this.$electron.ipcRenderer.send('installPlugin', item.name)
}).catch(() => {
console.log('Install canceled')
})
} else {
this.menu = Menu.buildFromTemplate(menu)
this.menu.popup()
}
getPluginList () {
ipcRenderer.send('getPluginList')
}
getPicBeds () {
ipcRenderer.send('getPicBeds')
}
installPlugin (item: IPicGoPlugin) {
if (!item.gui) {
this.$confirm('该插件未对可视化界面进行优化, 是否继续安装?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
item.ing = true
ipcRenderer.send('installPlugin', item.name)
}).catch(() => {
console.log('Install canceled')
})
} else {
item.ing = true
ipcRenderer.send('installPlugin', item.name)
}
}
uninstallPlugin (val: string) {
this.pluginList.forEach(item => {
if (item.name === val) {
item.ing = true
this.$electron.ipcRenderer.send('installPlugin', item.name)
}
},
uninstallPlugin (val) {
this.pluginList.forEach(item => {
if (item.name === val) {
item.ing = true
}
})
ipcRenderer.send('uninstallPlugin', val)
}
updatePlugin (val: string) {
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)
},
updatePlugin (val) {
this.pluginList.forEach(item => {
if (item.name === val) {
item.ing = true
}
} else {
this.letPicGoSaveData({
'picBed.transformer': transformer
})
this.$electron.ipcRenderer.send('updatePlugin', val)
},
reloadApp () {
this.$electron.remote.app.relaunch()
this.$electron.remote.app.exit(0)
},
handleReload () {
this.$db.read().set('needReload', true).write()
this.needReload = true
const successNotification = new window.Notification('更新成功', {
body: '请点击此通知重启应用以生效'
}
}
async handleConfirmConfig () {
// @ts-ignore
const result = await this.$refs.configForm.validate()
if (result !== false) {
switch (this.currentType) {
case 'plugin':
this.letPicGoSaveData({
[`picgo-plugin-${this.configName}`]: result
})
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 = () => {
this.reloadApp()
return true
}
},
cleanSearch () {
this.searchText = ''
},
toggleTransformer (transformer) {
let currentTransformer = this.$db.read().get('picBed.transformer').value() || 'path'
if (currentTransformer === transformer) {
this.$db.read().set('picBed.transformer', 'path').write()
} else {
this.$db.read().set('picBed.transformer', transformer).write()
}
},
async handleConfirmConfig () {
const result = await this.$refs.configForm.validate()
if (result !== false) {
switch (this.currentType) {
case 'plugin':
this.$db.read().set(`picgo-plugin-${this.configName}`, result).write()
break
case 'uploader':
this.$db.read().set(`picBed.${this.configName}`, result).write()
break
case 'transformer':
this.$db.read().set(`transformer.${this.configName}`, result).write()
break
}
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
this.dialogVisible = false
this.getPluginList()
}
},
getSearchResult: function (val) {
// this.$http.get(`https://api.npms.io/v2/search?q=${val}`)
this.$http.get(`https://registry.npmjs.com/-/v1/search?text=${val}`)
.then(res => {
this.pluginList = res.data.objects.map(item => {
this.dialogVisible = false
this.getPluginList()
}
}
getSearchResult (val: string) {
// this.$http.get(`https://api.npms.io/v2/search?q=${val}`)
this.$http.get(`https://registry.npmjs.com/-/v1/search?text=${val}`)
.then((res: INPMSearchResult) => {
this.pluginList = res.data.objects
.filter((item:INPMSearchResultObject) => {
return item.package.name.includes('picgo-plugin-')
})
.map((item: INPMSearchResultObject) => {
return this.handleSearchResult(item)
})
this.loading = false
})
.catch(err => {
console.log(err)
this.loading = false
})
},
handleSearchResult (item) {
const name = item.package.name.replace(/picgo-plugin-/, '')
let gui = false
if (item.package.keywords && item.package.keywords.length > 0) {
if (item.package.keywords.includes('picgo-gui-plugin')) {
gui = true
}
this.loading = false
})
.catch(err => {
console.log(err)
this.loading = false
})
}
handleSearchResult (item: INPMSearchResultObject) {
const name = item.package.name.replace(/picgo-plugin-/, '')
let gui = false
if (item.package.keywords && item.package.keywords.length > 0) {
if (item.package.keywords.includes('picgo-gui-plugin')) {
gui = true
}
return {
name: name,
author: item.package.author.name,
description: item.package.description,
logo: `https://cdn.jsdelivr.net/npm/${item.package.name}/logo.png`,
config: {},
homepage: item.package.links ? item.package.links.homepage : '',
hasInstall: this.pluginNameList.some(plugin => plugin === item.package.name.replace(/picgo-plugin-/, '')),
version: item.package.version,
gui,
ing: false // installing or uninstalling
}
},
// restore Uploader & Transformer
handleRestoreState (item, name) {
if (item === 'uploader') {
const current = this.$db.read().get('picBed.current').value()
if (current === name) {
this.$db.read().set('picBed.current', 'smms').write()
}
}
if (item === 'transformer') {
const current = this.$db.read().get('picBed.transformer').value()
if (current === name) {
this.$db.read().set('picBed.transformer', 'path').write()
}
}
},
openHomepage (url) {
if (url) {
this.$electron.remote.shell.openExternal(url)
}
},
handleInputBoxClose () {
this.$electron.ipcRenderer.send('showInputBox', this.inputBoxValue)
},
goAwesomeList () {
this.$electron.remote.shell.openExternal('https://github.com/PicGo/Awesome-PicGo')
}
},
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 () {
this.$electron.ipcRenderer.removeAllListeners('pluginList')
this.$electron.ipcRenderer.removeAllListeners('installSuccess')
this.$electron.ipcRenderer.removeAllListeners('uninstallSuccess')
this.$electron.ipcRenderer.removeAllListeners('updateSuccess')
this.$electron.ipcRenderer.removeAllListeners('showInputBox')
ipcRenderer.removeAllListeners('pluginList')
ipcRenderer.removeAllListeners('installSuccess')
ipcRenderer.removeAllListeners('uninstallSuccess')
ipcRenderer.removeAllListeners('updateSuccess')
}
}
</script>
@@ -483,6 +495,9 @@ $darwinBg = #172426
font-size 20px
vertical-align middle
cursor pointer
transition color .2s ease-in-out
&:hover
color #49B1F5
.handle-bar
margin-bottom 20px
&.cut-width
@@ -590,4 +605,4 @@ $darwinBg = #172426
padding 10px 0
&.cut-width
width calc(100% - 48px)
</style>
</style>
+23 -22
View File
@@ -6,7 +6,7 @@
<el-form-item
label="文件改名"
>
<el-input
<el-input
v-model="fileName"
size="small"
@keyup.enter.native="confirmName"
@@ -21,33 +21,34 @@
</el-row>
</div>
</template>
<script>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/mixin'
export default {
import {
ipcRenderer,
IpcRendererEvent
} from 'electron'
@Component({
name: 'rename-page',
mixins: [mixin],
data () {
return {
fileName: '',
id: null
}
},
mixins: [mixin]
})
export default class extends Vue {
fileName: string = ''
id: string | null = null
created () {
this.$electron.ipcRenderer.on('rename', (event, name, id) => {
ipcRenderer.on('rename', (event: IpcRendererEvent, name: string, id: string) => {
this.fileName = name
this.id = id
})
},
methods: {
confirmName () {
this.$electron.ipcRenderer.send(`rename${this.id}`, this.fileName)
},
cancel () {
this.$electron.ipcRenderer.send(`rename${this.id}`, null)
}
},
}
confirmName () {
ipcRenderer.send(`rename${this.id}`, this.fileName)
}
cancel () {
ipcRenderer.send(`rename${this.id}`, null)
}
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('rename')
ipcRenderer.removeAllListeners('rename')
}
}
</script>
@@ -58,4 +59,4 @@ export default {
float right
.el-form-item__label
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>
+88 -88
View File
@@ -4,7 +4,7 @@
<div class="content">
<div class="wait-upload-img" v-if="clipboardFiles.length > 0">
<div class="list-title">等待上传</div>
<div v-for="(item, index) in clipboardFiles" :key="index" class="img-list" :style="{height: calcHeight(item.width, item.height) + 'px'}">
<div v-for="(item, index) in clipboardFiles" :key="index" class="img-list">
<div
class="upload-img__container"
:class="{ upload: uploadFlag }"
@@ -15,7 +15,7 @@
</div>
<div class="uploaded-img">
<div class="list-title">已上传</div>
<div v-for="item in files" :key="item.imgUrl" class="img-list" :style="{height: calcHeight(item.width, item.height) + 'px'}">
<div v-for="item in files" :key="item.imgUrl" class="img-list">
<div class="upload-img__container" @click="copyTheLink(item)">
<img :src="item.imgUrl" class="upload-img">
</div>
@@ -25,91 +25,91 @@
</div>
</template>
<script>
import mixin from '@/utils/mixin'
import pasteTemplate from '~/main/utils/pasteTemplate'
export default {
name: 'tray-page',
mixins: [mixin],
data () {
return {
files: [],
notification: {
title: '复制链接成功',
body: '',
icon: ''
},
clipboardFiles: [],
uploadFlag: false
}
},
computed: {
reverseList () {
return this.files.slice().reverse()
}
},
mounted () {
this.disableDragFile()
this.getData()
this.$electron.ipcRenderer.on('dragFiles', (event, files) => {
files.forEach(item => {
this.$db.read().get('uploaded').insert(item).write()
})
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
})
this.$electron.ipcRenderer.on('clipboardFiles', (event, files) => {
this.clipboardFiles = files
})
this.$electron.ipcRenderer.on('uploadFiles', (event) => {
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
this.uploadFlag = false
})
this.$electron.ipcRenderer.on('updateFiles', (event) => {
this.getData()
})
},
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.imgUrl))
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')
}
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/mixin'
import pasteTemplate from '#/utils/pasteTemplate'
import { ipcRenderer, clipboard } from 'electron'
@Component({
name: 'tray-page',
mixins: [mixin]
})
export default class extends Vue {
files = []
notification = {
title: '复制链接成功',
body: '',
icon: ''
}
clipboardFiles: ImgInfo[] = []
uploadFlag = false
get reverseList () {
return this.files.slice().reverse()
}
getData () {
// @ts-ignore
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
}
copyTheLink (item: ImgInfo) {
this.notification.body = item.imgUrl!
this.notification.icon = item.imgUrl!
const myNotification = new Notification(this.notification.title, this.notification)
const pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
clipboard.writeText(pasteTemplate(pasteStyle, item))
myNotification.onclick = () => {
return true
}
}
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>
<style lang="stylus">
@@ -146,11 +146,11 @@ body::-webkit-scrollbar
top 0px
width 100%
.img-list
padding 16px 8px
padding 8px 8px
display flex
justify-content space-between
align-items center
height 45px
// height 45px
cursor pointer
transition all .2s ease-in-out
&:hover
@@ -158,8 +158,8 @@ body::-webkit-scrollbar
.upload-img__index
color #fff
.upload-img
height 100%
width 100%
object-fit scale-down
margin 0 auto
&__container
width 100%
+111 -104
View File
@@ -20,12 +20,12 @@
<input type="file" id="file-uploader" @change="onChange" multiple>
</div>
</div>
<el-progress
:percentage="progress"
:show-text="false"
<el-progress
:percentage="progress"
:show-text="false"
class="upload-progress"
:class="{ 'show': showProgress }"
:status="showError ? 'exception' : 'text'"
:status="showError ? 'exception' : undefined"
></el-progress>
<div class="paste-style">
<div class="el-col-16">
@@ -55,23 +55,28 @@
</el-row>
</div>
</template>
<script>
export default {
name: 'upload',
data () {
return {
dragover: false,
progress: 0,
showProgress: false,
showError: false,
pasteStyle: '',
picBed: [],
picBedName: '',
menu: null
}
},
<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'
import {
ipcRenderer,
IpcRendererEvent,
remote
} from 'electron'
const { Menu } = remote
@Component({
name: 'upload'
})
export default class extends Vue {
dragover = false
progress = 0
showProgress = false
showError = false
pasteStyle = ''
picBed: IPicBedType[] = []
picBedName = ''
menu: Electron.Menu | null= null
mounted () {
this.$electron.ipcRenderer.on('uploadProgress', (event, progress) => {
ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, progress: number) => {
if (progress !== -1) {
this.showProgress = true
this.progress = progress
@@ -82,94 +87,94 @@ export default {
})
this.getPasteStyle()
this.getDefaultPicBed()
this.$electron.ipcRenderer.on('syncPicBed', () => {
ipcRenderer.on('syncPicBed', () => {
this.getDefaultPicBed()
})
this.$electron.ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds)
},
watch: {
progress (val) {
if (val === 100) {
setTimeout(() => {
this.showProgress = false
this.showError = false
}, 1000)
setTimeout(() => {
this.progress = 0
}, 1200)
}
ipcRenderer.send('getPicBeds')
ipcRenderer.on('getPicBeds', this.getPicBeds)
}
@Watch('progress')
onProgressChange (val: number) {
if (val === 100) {
setTimeout(() => {
this.showProgress = false
this.showError = false
}, 1000)
setTimeout(() => {
this.progress = 0
}, 1200)
}
},
}
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('uploadProgress')
this.$electron.ipcRenderer.removeAllListeners('syncPicBed')
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
},
methods: {
onDrop (e) {
this.dragover = false
this.ipcSendFiles(e.dataTransfer.files)
},
openUplodWindow () {
document.getElementById('file-uploader').click()
},
onChange (e) {
this.ipcSendFiles(e.target.files)
document.getElementById('file-uploader').value = ''
},
ipcSendFiles (files) {
let sendFiles = []
Array.from(files).forEach((item, index) => {
let obj = {
name: item.name,
path: item.path
ipcRenderer.removeAllListeners('uploadProgress')
ipcRenderer.removeAllListeners('syncPicBed')
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}
onDrop (e: DragEvent) {
this.dragover = false
this.ipcSendFiles(e.dataTransfer!.files)
}
openUplodWindow () {
document.getElementById('file-uploader')!.click()
}
onChange (e: any) {
this.ipcSendFiles(e.target.files);
(document.getElementById('file-uploader') as HTMLInputElement).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)
}
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)
},
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)
}
}
})
// @ts-ignore
this.menu = Menu.buildFromTemplate(submenu)
}
}
</script>
@@ -236,4 +241,6 @@ export default {
border-radius 0 14px 14px 0
.paste-upload
width 100%
</style>
.el-icon-caret-bottom
cursor pointer
</style>
+43 -39
View File
@@ -5,7 +5,7 @@
<div class="view-title">
阿里云OSS设置
</div>
<el-form
<el-form
ref="aliyun"
label-position="right"
label-width="120px"
@@ -48,6 +48,11 @@
>
<el-input v-model="form.path" @keyup.native.enter="confirm" placeholder="例如img/"></el-input>
</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
label="设定自定义域名"
>
@@ -64,54 +69,53 @@
</el-row>
</div>
</template>
<script>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
mixins: [mixin],
@Component({
name: 'aliyun',
data () {
return {
form: {
accessKeyId: '',
accessKeySecret: '',
bucket: '',
area: '',
path: '',
customUrl: ''
}
}
},
mixins: [mixin]
})
export default class extends Vue {
form: IAliYunConfig = {
accessKeyId: '',
accessKeySecret: '',
bucket: '',
area: '',
path: '',
customUrl: '',
options: ''
}
created () {
const config = this.$db.get('picBed.aliyun').value()
const config = this.$db.get('picBed.aliyun') as IAliYunConfig
if (config) {
for (let i in config) {
this.form[i] = config[i]
}
this.form = Object.assign({}, config)
}
},
methods: {
confirm () {
this.$refs.aliyun.validate((valid) => {
if (valid) {
this.$db.set('picBed.aliyun', this.form).write()
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
} else {
return false
}
confirm () {
// @ts-ignore
this.$refs.aliyun.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
'picBed.aliyun': this.form
})
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
})
}
} else {
return false
}
})
}
}
</script>
<style lang='stylus'>
#aliyun-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@@ -122,7 +126,7 @@ export default {
.el-radio-group
width 100%
label
width 25%
width 25%
.el-radio-button__inner
width 100%
.el-radio-button:first-child
@@ -146,4 +150,4 @@ export default {
transition .2s color ease-in-out
&:hover
color #409EFF
</style>
</style>
+36 -38
View File
@@ -5,7 +5,7 @@
<div class="view-title">
GitHub设置
</div>
<el-form
<el-form
ref="github"
label-position="right"
label-width="120px"
@@ -33,7 +33,7 @@
:rules="{
required: true, message: 'Token不能为空', trigger: 'blur'
}">
<el-input v-model="form.token" @keyup.native.enter="confirm" placeholder="token"></el-input>
<el-input v-model="form.token" @keyup.native.enter="confirm" placeholder="token" type="password"></el-input>
</el-form-item>
<el-form-item
label="指定存储路径"
@@ -56,53 +56,51 @@
</el-row>
</div>
</template>
<script>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
@Component({
name: 'github',
mixins: [mixin],
data () {
return {
form: {
repo: '',
token: '',
path: '',
customUrl: '',
branch: ''
}
}
},
mixins: [mixin]
})
export default class extends Vue {
form: IGitHubConfig = {
repo: '',
token: '',
path: '',
customUrl: '',
branch: ''
}
created () {
const config = this.$db.get('picBed.github').value()
const config = this.$db.get('picBed.github') as IGitHubConfig
if (config) {
for (let i in config) {
this.form[i] = config[i]
}
this.form = Object.assign({}, config)
}
},
methods: {
confirm () {
this.$refs.github.validate((valid) => {
if (valid) {
this.$db.set('picBed.github', this.form).write()
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
} else {
return false
}
confirm () {
// @ts-ignore
this.$refs.github.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
'picBed.github': this.form
})
const successNotification = new Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
})
}
} else {
return false
}
})
}
}
</script>
<style lang='stylus'>
#github-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@@ -111,7 +109,7 @@ export default {
.el-radio-group
width 100%
label
width 25%
width 25%
.el-radio-button__inner
width 100%
.el-radio-button:first-child
+33 -35
View File
@@ -5,7 +5,7 @@
<div class="view-title">
Imgur图床设置
</div>
<el-form
<el-form
ref="imgur"
label-position="right"
label-width="120px"
@@ -36,50 +36,48 @@
</el-row>
</div>
</template>
<script>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
@Component({
name: 'imgur',
mixins: [mixin],
data () {
return {
form: {
clientId: '',
proxy: ''
}
}
},
mixins: [mixin]
})
export default class extends Vue {
form: IImgurConfig = {
clientId: '',
proxy: ''
}
created () {
const config = this.$db.get('picBed.imgur').value()
const config = this.$db.get('picBed.imgur') as IImgurConfig
if (config) {
for (let i in config) {
this.form[i] = config[i]
}
this.form = Object.assign({}, config)
}
},
methods: {
confirm () {
this.$refs.imgur.validate((valid) => {
if (valid) {
this.$db.set('picBed.imgur', this.form).write()
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
} else {
return false
}
confirm () {
// @ts-ignore
this.$refs.imgur.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
'picBed.imgur': this.form
})
const successNotification = new Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
})
}
} else {
return false
}
})
}
}
</script>
<style lang='stylus'>
#imgur-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@@ -90,7 +88,7 @@ export default {
.el-radio-group
width 100%
label
width 25%
width 25%
.el-radio-button__inner
width 100%
.el-radio-button:first-child
@@ -114,4 +112,4 @@ export default {
transition .2s color ease-in-out
&:hover
color #409EFF
</style>
</style>
+47 -39
View File
@@ -27,64 +27,72 @@
</el-row>
</div>
</template>
<script>
import ConfigForm from '@/components/ConfigForm'
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import ConfigForm from '@/components/ConfigForm.vue'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
import {
ipcRenderer,
IpcRendererEvent
} from 'electron'
@Component({
name: 'OtherPicBed',
mixins: [mixin],
components: {
ConfigForm
},
data () {
return {
type: '',
config: [],
picBedName: ''
}
},
}
})
export default class extends Vue {
type: string = ''
config: any[] = []
picBedName: string = ''
created () {
this.type = this.$route.params.type
this.$electron.ipcRenderer.send('getPicBedConfig', this.$route.params.type)
this.$electron.ipcRenderer.on('getPicBedConfig', this.getPicBeds)
},
methods: {
async handleConfirm () {
const result = await this.$refs.configForm.validate()
if (result !== false) {
this.$db.read().set(`picBed.${this.type}`, result).write()
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
}
},
setDefaultPicBed (type) {
this.$db.read().set('picBed.current', type).write()
this.defaultPicBed = type
const successNotification = new window.Notification('设置默认图床', {
ipcRenderer.send('getPicBedConfig', this.$route.params.type)
ipcRenderer.on('getPicBedConfig', this.getPicBeds)
}
async handleConfirm () {
// @ts-ignore
const result = await this.$refs.configForm.validate()
if (result !== false) {
this.letPicGoSaveData({
[`picBed.${this.type}`]: result
})
const successNotification = new Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
},
getPicBeds (event, config, name) {
this.config = config
this.picBedName = name
}
},
}
setDefaultPicBed (type: string) {
this.letPicGoSaveData({
'picBed.current': type
})
// @ts-ignore 来自mixin的数据
this.defaultPicBed = type
const successNotification = new Notification('设置默认图床', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
}
getPicBeds (event: IpcRendererEvent, config: any[], name: string) {
this.config = config
this.picBedName = name
}
beforeDestroy () {
this.$electron.ipcRenderer.removeListener('getPicBedConfig', this.getPicBeds)
ipcRenderer.removeListener('getPicBedConfig', this.getPicBeds)
}
}
</script>
<style lang='stylus'>
#others-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@@ -106,4 +114,4 @@ export default {
margin-bottom 10px
.single
text-align center
</style>
</style>
+42 -47
View File
@@ -5,7 +5,7 @@
<div class="view-title">
七牛图床设置
</div>
<el-form
<el-form
ref="qiniu"
label-position="right"
label-width="120px"
@@ -45,13 +45,10 @@
</el-form-item>
<el-form-item
label="确认存储区域"
>
<el-radio-group v-model="form.area" size="mini">
<el-radio-button label="z0">华东</el-radio-button>
<el-radio-button label="z1">华北</el-radio-button>
<el-radio-button label="z2">华南</el-radio-button>
<el-radio-button label="na0">北美</el-radio-button>
</el-radio-group>
:rules="{
required: true, message: '区域代码不能为空', trigger: 'blur'
}">
<el-input v-model="form.area" placeholder="例如z0"></el-input>
</el-form-item>
<el-form-item
label="设定网址后缀"
@@ -74,55 +71,53 @@
</el-row>
</div>
</template>
<script>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
mixins: [mixin],
@Component({
name: 'qiniu',
data () {
return {
form: {
accessKey: '',
secretKey: '',
bucket: '',
url: '',
area: 'z0',
options: '',
path: ''
}
}
},
mixins: [mixin]
})
export default class extends Vue {
form: IQiniuConfig = {
accessKey: '',
secretKey: '',
bucket: '',
url: '',
area: '',
options: '',
path: ''
}
created () {
const config = this.$db.get('picBed.qiniu').value()
const config = this.$db.get('picBed.qiniu') as IQiniuConfig
if (config) {
for (let i in config) {
this.form[i] = config[i]
}
this.form = Object.assign({}, config)
}
},
methods: {
confirm () {
this.$refs.qiniu.validate((valid) => {
if (valid) {
this.$db.set('picBed.qiniu', this.form).write()
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
} else {
return false
}
confirm () {
// @ts-ignore
this.$refs.qiniu.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
'picBed.qiniu': this.form
})
const successNotification = new Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
})
}
} else {
return false
}
})
}
}
</script>
<style lang='stylus'>
#qiniu-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@@ -133,7 +128,7 @@ export default {
.el-radio-group
width 100%
label
width 25%
width 25%
.el-radio-button__inner
width 100%
.el-radio-button:first-child
@@ -144,4 +139,4 @@ export default {
.el-radio-button__inner
border-left none
border-radius 0 14px 14px 0
</style>
</style>
+18 -17
View File
@@ -15,24 +15,25 @@
</el-row>
</div>
</template>
<script>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
mixins: [mixin],
name: 'upyun',
data () {
return {}
},
methods: {
confirm () {
this.$db.set('picBed.smms', true).write()
this.setDefaultPicBed('smms')
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
@Component({
name: 'smms',
mixins: [mixin]
})
export default class extends Vue {
confirm () {
this.letPicGoSaveData({
'picBed.smms': true
})
// @ts-ignore 来自mixin
this.setDefaultPicBed('smms')
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
}
}
+43 -44
View File
@@ -5,7 +5,7 @@
<div class="view-title">
腾讯云COS设置
</div>
<el-form
<el-form
ref="tcyun"
label-position="right"
label-width="120px"
@@ -85,59 +85,58 @@
</el-row>
</div>
</template>
<script>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
mixins: [mixin],
import { remote } from 'electron'
@Component({
name: 'tcyun',
data () {
return {
form: {
secretId: '',
secretKey: '',
bucket: '',
appId: '',
area: '',
path: '',
customUrl: '',
version: 'v4'
}
}
},
mixins: [mixin]
})
export default class extends Vue {
form: ITcYunConfig = {
secretId: '',
secretKey: '',
bucket: '',
appId: '',
area: '',
path: '',
customUrl: '',
version: 'v4'
}
created () {
const config = this.$db.get('picBed.tcyun').value()
const config = this.$db.get('picBed.tcyun') as ITcYunConfig
if (config) {
for (let i in config) {
this.form[i] = config[i]
}
this.form = Object.assign({}, config)
}
},
methods: {
confirm () {
this.$refs.tcyun.validate((valid) => {
if (valid) {
this.$db.set('picBed.tcyun', this.form).write()
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
} else {
return false
}
confirm () {
// @ts-ignore
this.$refs.tcyun.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
'picBed.tcyun': this.form
})
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
})
},
openWiki () {
this.$electron.remote.shell.openExternal('https://github.com/Molunerfinn/PicGo/wiki/%E8%AF%A6%E7%BB%86%E7%AA%97%E5%8F%A3%E7%9A%84%E4%BD%BF%E7%94%A8#腾讯云cos')
}
} else {
return false
}
})
}
openWiki () {
remote.shell.openExternal('https://github.com/Molunerfinn/PicGo/wiki/%E8%AF%A6%E7%BB%86%E7%AA%97%E5%8F%A3%E7%9A%84%E4%BD%BF%E7%94%A8#腾讯云cos')
}
}
</script>
<style lang='stylus'>
#tcyun-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@@ -148,7 +147,7 @@ export default {
.el-radio-group
width 100%
label
width 25%
width 25%
.el-radio-button__inner
width 100%
.el-radio-button:first-child
@@ -172,4 +171,4 @@ export default {
transition .2s color ease-in-out
&:hover
color #409EFF
</style>
</style>
+36 -38
View File
@@ -5,7 +5,7 @@
<div class="view-title">
又拍云设置
</div>
<el-form
<el-form
ref="tcyun"
label-position="right"
label-width="120px"
@@ -64,53 +64,51 @@
</el-row>
</div>
</template>
<script>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/ConfirmButtonMixin'
export default {
@Component({
name: 'upyun',
mixins: [mixin],
data () {
return {
form: {
bucket: '',
operator: '',
password: '',
options: '',
path: ''
}
}
},
mixins: [mixin]
})
export default class extends Vue {
form: IUpYunConfig = {
bucket: '',
operator: '',
password: '',
options: '',
path: ''
}
created () {
const config = this.$db.get('picBed.upyun').value()
const config = this.$db.get('picBed.upyun') as IUpYunConfig
if (config) {
for (let i in config) {
this.form[i] = config[i]
}
this.form = Object.assign({}, config)
}
},
methods: {
confirm () {
this.$refs.tcyun.validate((valid) => {
if (valid) {
this.$db.set('picBed.upyun', this.form).write()
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
} else {
return false
}
confirm () {
// @ts-ignore
this.$refs.tcyun.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
'picBed.upyun': this.form
})
const successNotification = new Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
})
}
} else {
return false
}
})
}
}
</script>
<style lang='stylus'>
#tcyun-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@@ -119,7 +117,7 @@ export default {
.el-radio-group
width 100%
label
width 25%
width 25%
.el-radio-button__inner
width 100%
.el-radio-button:first-child
@@ -130,4 +128,4 @@ export default {
.el-radio-button__inner
border-left none
border-radius 0 14px 14px 0
</style>
</style>
+14 -12
View File
@@ -3,9 +3,9 @@
<el-row :gutter="16">
<el-col :span="16" :offset="4">
<div class="view-title">
微博图床设置
微博图床设置[已停止支持]
</div>
<el-form
<el-form
ref="weiboForm"
label-position="right"
label-width="120px"
@@ -78,7 +78,7 @@ export default {
}
},
created () {
const config = this.$db.read().get('picBed.weibo').value()
const config = this.$db.get('picBed.weibo')
if (config) {
this.form.username = config.username
this.form.password = config.password
@@ -91,13 +91,15 @@ export default {
confirm (formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$db.read().set('picBed.weibo', {
username: this.form.username,
password: this.form.password,
quality: this.quality,
cookie: this.form.cookie,
chooseCookie: this.chooseCookie
}).write()
this.letPicGoSaveData({
'picBed.weibo': {
username: this.form.username,
password: this.form.password,
quality: this.quality,
cookie: this.form.cookie,
chooseCookie: this.chooseCookie
}
})
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
@@ -123,7 +125,7 @@ export default {
left 60%
#weibo-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@@ -148,4 +150,4 @@ export default {
transition .2s color ease-in-out
&:hover
color #409EFF
</style>
</style>

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