🔨 Refactor(ts): change js -> ts

This commit is contained in:
Molunerfinn 2019-12-19 19:17:21 +08:00
parent 72e6e2aed5
commit 4e67a75be0
91 changed files with 8223 additions and 6755 deletions

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"]
}

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
.gitignore vendored
View File

@ -12,3 +12,7 @@ thumbs.db
!.gitkeep
yarn-error.log
docs/dist/
# local env files
.env.local
.env.*.local
dist_electron/

23
.vscode/settings.json vendored
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
}
}

28
LICENSE
View File

@ -1,13 +1,21 @@
The 996ICU License (996ICU)
Version 0.1, March 2019
The MIT License (MIT)
PACKAGE is distributed under LICENSE with the following restriction:
Copyright (c) 2017-present, Molunerfinn
The above license is only granted to entities that act in concordance
with local labor laws. In addition, the following requirements must be
observed:
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 licencee must not, explicitly or implicitly, request or schedule
their employees to work more than 45 hours in any single week.
* The licencee must not, explicitly or implicitly, request or schedule
their employees to be at work consecutively for 10 hours.
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.

View File

@ -121,4 +121,4 @@ npm run build
[MIT](http://opensource.org/licenses/MIT)
Copyright (c) 2017 - 2019 Molunerfinn
Copyright (c) 2017 - 2019 Molunerfinn

5
babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

0
dist/web/.gitkeep vendored
View File

View File

@ -1,35 +1,20 @@
{
"name": "picgo",
"version": "2.1.2",
"author": "Molunerfinn <marksz@teamsz.xyz>",
"description": "Easy to upload your pic & copy to write",
"license": "MIT",
"main": "./dist/electron/main.js",
"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"
"bump": "bump-version",
"release": "electron:build"
},
"main": "background.js",
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
@ -44,144 +29,53 @@
}
},
"commitlint": {
"extends": [
"./node_modules/@picgo/bump-version/commitlint-picgo"
]
},
"build": {
"productName": "PicGo",
"appId": "com.molunerfinn.picgo",
"directories": {
"output": "build"
},
"files": [
"dist/electron/**/*"
],
"dmg": {
"contents": [
{
"x": 410,
"y": 150,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 150,
"type": "file"
}
]
},
"mac": {
"icon": "build/icons/icon.icns",
"extendInfo": {
"LSUIElement": 1
}
},
"win": {
"icon": "build/icons/icon.ico",
"target": "nsis"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
},
"linux": {
"icon": "build/icons/"
}
"extends": ["./node_modules/@picgo/bump-version/commitlint-picgo"]
},
"dependencies": {
"axios": "^0.19.0",
"dateformat": "^3.0.3",
"element-ui": "^2.4.11",
"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.3.7",
"qiniu": "^7.1.1",
"vue": "^2.3.3",
"vue-electron": "^1.0.6",
"vue-gallery": "^1.2.4",
"vue": "^2.6.10",
"vue-gallery": "^2.0.1",
"vue-lazyload": "^1.2.6",
"vue-router": "^2.5.3",
"vuex": "^2.3.1",
"ws": "3.3.1"
"vue-router": "^3.1.3"
},
"devDependencies": {
"@commitlint/cli": "^7.5.2",
"@picgo/bump-version": "^1.0.2",
"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",
"commitizen": "^3.0.7",
"conventional-changelog": "^3.0.6",
"copy-webpack-plugin": "^4.0.1",
"cross-env": "^5.0.5",
"css-loader": "^0.28.4",
"cz-customizable": "^5.10.0",
"del": "^3.0.0",
"devtron": "^1.4.0",
"electron": "^5.0.1",
"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",
"husky": "^1.3.1",
"inject-loader": "^3.0.0",
"inquirer": "^6.3.1",
"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.5.3",
"vue-cli-plugin-electron-builder": "^1.4.2",
"vue-property-decorator": "^8.3.0",
"vue-template-compiler": "^2.6.10"
},
"resolutions": {
"**/@types/node": "10.17.6"
}
}

5
postcss.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

18
public/index.html Normal file
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

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 497 B

After

Width:  |  Height:  |  Size: 497 B

View File

Before

Width:  |  Height:  |  Size: 915 B

After

Width:  |  Height:  |  Size: 915 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 823 B

After

Width:  |  Height:  |  Size: 823 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 777 B

After

Width:  |  Height:  |  Size: 777 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,6 +1,77 @@
// 'use strict'
// import { app, protocol, BrowserWindow } from 'electron'
// import {
// createProtocol,
// installVueDevtools
// } from 'vue-cli-plugin-electron-builder/lib'
// const isDevelopment = process.env.NODE_ENV !== 'production'
// // Keep a global reference of the window object, if you don't, the window will
// // be closed automatically when the JavaScript object is garbage collected.
// let win: BrowserWindow | null
// // Scheme must be registered before the app is ready
// protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }])
// function createWindow () {
// // Create the browser window.
// win = new BrowserWindow({ width: 800,
// height: 600,
// webPreferences: {
// nodeIntegration: true
// } })
// if (process.env.WEBPACK_DEV_SERVER_URL) {
// // Load the url of the dev server if in development mode
// win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string)
// if (!process.env.IS_TEST) win.webContents.openDevTools()
// } else {
// createProtocol('app')
// // Load the index.html when not in development
// win.loadURL('app://./index.html')
// }
// win.on('closed', () => {
// win = null
// })
// }
// // Quit when all windows are closed.
// app.on('window-all-closed', () => {
// // On macOS it is common for applications and their menu bar
// // to stay active until the user quits explicitly with Cmd + Q
// if (process.platform !== 'darwin') {
// app.quit()
// }
// })
// app.on('activate', () => {
// // On macOS it's common to re-create a window in the app when the
// // dock icon is clicked and there are no other windows open.
// if (win === null) {
// createWindow()
// }
// })
// // This method will be called when Electron has finished
// // initialization and is ready to create browser windows.
// // Some APIs can only be used after this event occurs.
// app.on('ready', async () => {
// if (isDevelopment && !process.env.IS_TEST) {
// // Install Vue Devtools
// try {
// await installVueDevtools()
// } catch (e) {
// console.error('Vue Devtools failed to install:', e.toString())
// }
// }
// createWindow()
// })
'use strict'
import Uploader from './utils/uploader.js'
import Uploader from '~/main/utils/uploader'
import {
app,
BrowserWindow,
@ -11,54 +82,54 @@ import {
ipcMain,
globalShortcut,
dialog,
systemPreferences
systemPreferences,
WebContents,
IpcMainEvent,
protocol
} from 'electron'
import db from '../datastore'
import beforeOpen from './utils/beforeOpen'
import pasteTemplate from './utils/pasteTemplate'
import updateChecker from './utils/updateChecker'
import { getPicBeds } from './utils/getPicBeds'
import pkg from '../../package.json'
import picgoCoreIPC from './utils/picgoCoreIPC'
import {
createProtocol,
installVueDevtools
} from 'vue-cli-plugin-electron-builder/lib'
import db from '#/datastore'
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 './utils/handleArgv'
import bus from './utils/eventBus'
import { getUploadFiles } from '~/main/utils/handleArgv'
import bus from '~/main/utils/eventBus'
import {
updateShortKeyFromVersion212
} from './migrate/shortKeyUpdateHelper'
} from '~/main/migrate/shortKeyUpdateHelper'
import {
shortKeyUpdater,
initShortKeyRegister
} from './utils/shortKeyHandler'
} from '~/main/utils/shortKeyHandler'
const isDevelopment = process.env.NODE_ENV !== 'production'
protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }])
if (process.platform === 'darwin') {
beforeOpen()
}
/**
* Set `__static` path to static files in production
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
*/
if (process.env.NODE_ENV !== 'development') {
global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
}
if (process.env.DEBUG_ENV === 'debug') {
global.__static = require('path').join(__dirname, '../../static').replace(/\\/g, '\\\\')
}
let window
let settingWindow
let miniWindow
let tray
let menu
let contextMenu
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080`
: `file://${__dirname}/index.html`
const settingWinURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/#setting/upload`
: `file://${__dirname}/index.html#setting/upload`
const miniWinURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/#mini-page`
: `file://${__dirname}/index.html#mini-page`
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()
@ -94,7 +165,7 @@ function createContextMenu () {
click () {
if (settingWindow === null) {
createSettingWindow()
settingWindow.show()
settingWindow!.show()
} else {
settingWindow.show()
settingWindow.focus()
@ -107,8 +178,10 @@ function createContextMenu () {
{
label: '选择默认图床',
type: 'submenu',
// @ts-ignore
submenu
},
// @ts-ignore
{
label: '打开更新助手',
type: 'checkbox',
@ -125,6 +198,7 @@ function createContextMenu () {
app.exit(0)
}
},
// @ts-ignore
{
role: 'quit',
label: '退出'
@ -140,14 +214,15 @@ function createTray () {
window.hide()
}
createContextMenu()
tray.popUpContextMenu(contextMenu)
tray!.popUpContextMenu(contextMenu!)
})
tray.on('click', (event, bounds) => {
if (process.platform === 'darwin') {
let img = clipboard.readImage()
let obj = []
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,
@ -157,7 +232,7 @@ function createTray () {
}
toggleWindow(bounds)
setTimeout(() => {
window.webContents.send('clipboardFiles', obj)
window!.webContents.send('clipboardFiles', obj)
}, 0)
} else {
if (window) {
@ -165,7 +240,7 @@ function createTray () {
}
if (settingWindow === null) {
createSettingWindow()
settingWindow.show()
settingWindow!.show()
} else {
settingWindow.show()
settingWindow.focus()
@ -178,25 +253,25 @@ function createTray () {
tray.on('drag-enter', () => {
if (systemPreferences.isDarkMode()) {
tray.setImage(`${__static}/upload-dark.png`)
tray!.setImage(`${__static}/upload-dark.png`)
} else {
tray.setImage(`${__static}/upload.png`)
tray!.setImage(`${__static}/upload.png`)
}
})
tray.on('drag-end', () => {
tray.setImage(`${__static}/menubar.png`)
tray!.setImage(`${__static}/menubar.png`)
})
tray.on('drop-files', async (event, files) => {
tray.on('drop-files', async (event: Event, files: string[]) => {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const imgs = await new Uploader(files, window.webContents).upload()
const imgs = await new Uploader(files, window!.webContents).upload()
if (imgs !== false) {
for (let i in imgs) {
for (let i = 0; i < imgs.length; i++) {
clipboard.writeText(pasteTemplate(pasteStyle, imgs[i]))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl,
body: imgs[i].imgUrl!,
icon: files[i]
})
setTimeout(() => {
@ -204,7 +279,7 @@ function createTray () {
}, i * 100)
db.insert('uploaded', imgs[i])
}
window.webContents.send('dragFiles', imgs)
window!.webContents.send('dragFiles', imgs)
}
})
// toggleWindow()
@ -237,7 +312,7 @@ const createWindow = () => {
})
window.on('blur', () => {
window.hide()
window!.hide()
})
return window
}
@ -246,7 +321,7 @@ const createMiniWidow = () => {
if (miniWindow) {
return false
}
let obj = {
let obj: BrowserWindowOptions = {
height: 64,
width: 64,
show: process.platform === 'linux',
@ -278,7 +353,7 @@ const createMiniWidow = () => {
}
const createSettingWindow = () => {
const options = {
const options: BrowserWindowOptions = {
height: 450,
width: 800,
show: false,
@ -306,9 +381,9 @@ const createSettingWindow = () => {
}
settingWindow = new BrowserWindow(options)
settingWindow.loadURL(settingWinURL)
settingWindow!.loadURL(settingWinURL)
settingWindow.on('closed', () => {
settingWindow!.on('closed', () => {
bus.emit('toggleShortKeyModifiedMode', false)
settingWindow = null
if (process.platform === 'linux') {
@ -343,47 +418,48 @@ const createMenu = () => {
}
]
}]
// @ts-ignore
menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
}
const toggleWindow = (bounds) => {
if (window.isVisible()) {
window.hide()
const toggleWindow = (bounds: 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 showWindow = (bounds: 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()) {
if (miniWindow!.isVisible()) {
win = miniWindow
} else {
win = settingWindow || window || createSettingWindow()
}
let img = await new Uploader(undefined, win.webContents).upload()
let img = await new Uploader(undefined, 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,
body: img[0].imgUrl!,
icon: img[0].imgUrl
})
notification.show()
db.insert('uploaded', img[0])
window.webContents.send('clipboardFiles', [])
window.webContents.send('uploadFiles', img)
window!.webContents.send('clipboardFiles', [])
window!.webContents.send('uploadFiles', img)
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
@ -397,17 +473,17 @@ const uploadClipboardFiles = async () => {
}
}
const uploadChoosedFiles = async (webContents, files) => {
const uploadChoosedFiles = async (webContents: WebContents, files: FileWithPath[]) => {
const input = files.map(item => item.path)
const imgs = await new Uploader(input, webContents).upload()
if (imgs !== false) {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
let pasteText = ''
for (let i in imgs) {
for (let i = 0; i < imgs.length; i++) {
pasteText += pasteTemplate(pasteStyle, imgs[i]) + '\r\n'
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl,
body: imgs[i].imgUrl!,
icon: files[i].path
})
setTimeout(() => {
@ -416,7 +492,7 @@ const uploadChoosedFiles = async (webContents, files) => {
db.insert('uploaded', imgs[i])
}
clipboard.writeText(pasteText)
window.webContents.send('uploadFiles', imgs)
window!.webContents.send('uploadFiles', imgs)
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
@ -426,36 +502,36 @@ const uploadChoosedFiles = async (webContents, files) => {
picgoCoreIPC(app, ipcMain)
// from macOS tray
ipcMain.on('uploadClipboardFiles', async (evt, file) => {
const img = await new Uploader(undefined, window.webContents).upload()
ipcMain.on('uploadClipboardFiles', async () => {
const img = await new Uploader(undefined, 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,
body: img[0].imgUrl!,
// icon: file[0]
icon: img[0].imgUrl
})
notification.show()
db.insert('uploaded', img[0])
window.webContents.send('clipboardFiles', [])
window!.webContents.send('clipboardFiles', [])
if (settingWindow) {
settingWindow.webContents.send('updateGallery')
}
}
window.webContents.send('uploadFiles')
window!.webContents.send('uploadFiles')
})
ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
uploadClipboardFiles()
})
ipcMain.on('uploadChoosedFiles', async (evt, files) => {
ipcMain.on('uploadChoosedFiles', async (evt: IpcMainEvent, files: FileWithPath[]) => {
return uploadChoosedFiles(evt.sender, files)
})
ipcMain.on('updateShortKey', (evt, item, oldKey) => {
ipcMain.on('updateShortKey', (evt: IpcMainEvent, item: ShortKeyConfig, oldKey: string) => {
shortKeyUpdater(globalShortcut, item, oldKey)
const notification = new Notification({
title: '操作成功',
@ -464,7 +540,7 @@ ipcMain.on('updateShortKey', (evt, item, oldKey) => {
notification.show()
})
ipcMain.on('updateCustomLink', (evt, oldLink) => {
ipcMain.on('updateCustomLink', () => {
const notification = new Notification({
title: '操作成功',
body: '你的自定义链接格式已经修改成功'
@ -472,44 +548,46 @@ ipcMain.on('updateCustomLink', (evt, oldLink) => {
notification.show()
})
ipcMain.on('autoStart', (evt, val) => {
ipcMain.on('autoStart', (evt: IpcMainEvent, val: boolean) => {
app.setLoginItemSettings({
openAtLogin: val
})
})
ipcMain.on('openSettingWindow', (evt) => {
ipcMain.on('openSettingWindow', () => {
if (!settingWindow) {
createSettingWindow()
} else {
settingWindow.show()
}
miniWindow.hide()
if (miniWindow) {
miniWindow.hide()
}
})
ipcMain.on('openMiniWindow', (evt) => {
ipcMain.on('openMiniWindow', () => {
if (!miniWindow) {
createMiniWidow()
}
miniWindow.show()
miniWindow.focus()
settingWindow.hide()
miniWindow!.show()
miniWindow!.focus()
settingWindow!.hide()
})
// from mini window
ipcMain.on('syncPicBed', (evt) => {
ipcMain.on('syncPicBed', () => {
if (settingWindow) {
settingWindow.webContents.send('syncPicBed')
}
})
ipcMain.on('getPicBeds', (evt) => {
ipcMain.on('getPicBeds', (evt: IpcMainEvent) => {
const picBeds = getPicBeds(app)
evt.sender.send('getPicBeds', picBeds)
evt.returnValue = picBeds
})
ipcMain.on('toggleShortKeyModifiedMode', (evt, val) => {
ipcMain.on('toggleShortKeyModifiedMode', (evt: IpcMainEvent, val: boolean) => {
bus.emit('toggleShortKeyModifiedMode', val)
})
@ -548,14 +626,22 @@ if (!gotTheLock) {
}
if (process.platform === 'win32') {
app.setAppUserModelId(pkg.build.appId)
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', () => {
app.on('ready', async () => {
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') {
@ -595,6 +681,7 @@ app.on('window-all-closed', () => {
})
app.on('activate', () => {
createProtocol('picgo')
if (window === null) {
createWindow()
}
@ -613,7 +700,7 @@ app.setLoginItemSettings({
})
function initEventCenter () {
const eventList = {
const eventList: any = {
'picgo:upload': uploadClipboardFiles
}
for (let i in eventList) {
@ -621,6 +708,21 @@ function initEventCenter () {
}
}
// 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()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
/**
* Auto Updater
*

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.get('picBed.list') || []
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

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>

View File

@ -1,24 +1,16 @@
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)
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 +21,12 @@ Vue.prototype.$builtInPicBed = [
'aliyun',
'github'
]
Vue.config.productionTip = false
Vue.prototype.$db = db
Vue.use(ElementUI)
Vue.use(VueLazyLoad)
/* eslint-disable no-new */
new Vue({
components: { App },
router,
store,
template: '<App/>'
render: h => h(App)
}).$mount('#app')

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')

View File

@ -1,8 +1,10 @@
import DB from '#/datastore'
// from v2.1.2
const updateShortKeyFromVersion212 = (db, shortKeyConfig) => {
const updateShortKeyFromVersion212 = (db: typeof DB, shortKeyConfig: ShortKeyConfigs | OldShortKeyConfigs) => {
let needUpgrade = false
if (shortKeyConfig.upload) {
needUpgrade = true
// @ts-ignore
shortKeyConfig['picgo:upload'] = {
enable: true,
key: shortKeyConfig.upload,

View File

@ -2,12 +2,6 @@ import fs from 'fs-extra'
import path from 'path'
import os from 'os'
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')
@ -50,7 +44,7 @@ function resolveClipboardImageGenerator () {
})
}
function diffFilesAndUpdate (filePath1, filePath2) {
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
let file1 = fs.readFileSync(filePath1)
let file2 = fs.readFileSync(filePath2)

View File

@ -1,23 +1,24 @@
import path from 'path'
import db from '../../datastore'
import db from '#/datastore'
import { App } from 'electron'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const getPicBeds = (app) => {
const getPicBeds = (app: 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.get('picBed.list') || []
const picBeds = picBedTypes.map(item => {
const visible = picBedFromDB.find(i => i.type === item) // object or undefined
const picBeds = picBedTypes.map((item: string) => {
const visible = picBedFromDB.find((i: PicBedType) => i.type === item) // object or undefined
return {
type: item,
name: picgo.helper.uploader.get(item).name || item,
visible: visible ? visible.visible : true
}
})
}) as PicBedType[]
picgo.cmd.program.removeAllListeners()
return picBeds
}

View File

@ -2,17 +2,23 @@ import {
dialog,
BrowserWindow,
clipboard,
Notification
Notification,
IpcMain,
WebContents
} from 'electron'
import db from '../../datastore'
import db from '#/datastore'
import Uploader from './uploader'
import pasteTemplate from './pasteTemplate'
import pasteTemplate from '#/utils/pasteTemplate'
import PicGoCore from '~/universal/types/picgo'
const WEBCONTENTS = Symbol('WEBCONTENTS')
const IPCMAIN = Symbol('IPCMAIN')
const PICGO = Symbol('PICGO')
class GuiApi {
constructor (ipcMain, webcontents, picgo) {
private [WEBCONTENTS]: WebContents
private [IPCMAIN]: IpcMain
private [PICGO]: PicGoCore
constructor (ipcMain: IpcMain, webcontents: WebContents, picgo: PicGoCore) {
this[WEBCONTENTS] = webcontents
this[IPCMAIN] = ipcMain
this[PICGO] = picgo
@ -23,7 +29,7 @@ class GuiApi {
* @param {object} options
* return type is string or ''
*/
showInputBox (options) {
showInputBox (options: IShowInputBoxOption) {
if (options === undefined) {
options = {
title: '',
@ -32,7 +38,7 @@ class GuiApi {
}
this[WEBCONTENTS].send('showInputBox', options)
return new Promise((resolve, reject) => {
this[IPCMAIN].once('showInputBox', (event, value) => {
this[IPCMAIN].once('showInputBox', (event: Event, value: string) => {
resolve(value)
})
})
@ -42,12 +48,12 @@ class GuiApi {
* for plugin show file explorer
* @param {object} options
*/
showFileExplorer (options) {
showFileExplorer (options: {}) {
if (options === undefined) {
options = {}
}
return new Promise((resolve, reject) => {
dialog.showOpenDialog(BrowserWindow.fromWebContents(this[WEBCONTENTS]), options, filename => {
dialog.showOpenDialog(BrowserWindow.fromWebContents(this[WEBCONTENTS]), options, (filename: string) => {
resolve(filename)
})
})
@ -57,16 +63,16 @@ class GuiApi {
* for plugin to upload file
* @param {array} input
*/
async upload (input) {
async upload (input: []) {
const imgs = await new Uploader(input, this[WEBCONTENTS], this[PICGO]).upload()
if (imgs !== false) {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
let pasteText = ''
for (let i in imgs) {
for (let i = 0; i < imgs.length; i++) {
pasteText += pasteTemplate(pasteStyle, imgs[i]) + '\r\n'
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl,
body: imgs[i].imgUrl as string,
icon: imgs[i].imgUrl
})
setTimeout(() => {
@ -110,13 +116,13 @@ class GuiApi {
return new Promise((resolve, reject) => {
dialog.showMessageBox(
BrowserWindow.fromWebContents(this[WEBCONTENTS]),
options,
(result, checkboxChecked) => {
resolve({
result,
checkboxChecked
})
options
).then((res) => {
resolve({
result: res.response,
checkboxChecked: res.checkboxChecked
})
})
})
}
}

View File

@ -1,5 +1,9 @@
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') {
@ -7,7 +11,7 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd()) => {
return null // for uploading images in clipboard
} else if (files.length > 1) {
files = argv.slice(1)
let result = []
let result: Result = []
if (files.length > 0) {
result = files.map(item => {
if (path.isAbsolute(item)) {
@ -24,7 +28,7 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd()) => {
return null
}
}
}).filter(item => item !== null)
}).filter(item => item !== null) as Result
}
return result
}

View File

@ -1,16 +1,27 @@
import path from 'path'
import GuiApi from './guiApi'
import { dialog, shell } from 'electron'
import db from '../../datastore'
import { dialog, shell, IpcMain, IpcMainEvent, App } from 'electron'
import db from '#/datastore'
import PicGoCore from '~/universal/types/picgo'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo')
const PicGo = requireFunc('picgo') as typeof PicGoCore
const PluginHandler = requireFunc('picgo/dist/lib/PluginHandler').default
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: PicGoHelperType, ctx: PicGoCore) => {
let config: any[] = []
if (name === '') {
return config
} else {
@ -24,7 +35,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()
@ -36,8 +47,8 @@ const handleConfigWithFunction = config => {
return config
}
const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => {
ipcMain.on('getPluginList', event => {
const handleGetPluginList = (ipcMain: IpcMain, STORE_PATH: string, CONFIG_PATH: string) => {
ipcMain.on('getPluginList', (event: IpcMainEvent) => {
const picgo = new PicGo(CONFIG_PATH)
const pluginList = picgo.pluginLoader.getList()
const list = []
@ -57,7 +68,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,
@ -71,11 +82,11 @@ const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => {
},
uploader: {
name: uploaderName,
config: handleConfigWithFunction(getConfig(uploaderName, 'uploader', picgo))
config: handleConfigWithFunction(getConfig(uploaderName, PicGoHelperType.uploader, picgo))
},
transformer: {
name: transformerName,
config: handleConfigWithFunction(getConfig(uploaderName, 'transformer', picgo))
config: handleConfigWithFunction(getConfig(uploaderName, PicGoHelperType.transformer, picgo))
}
},
enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`),
@ -90,11 +101,11 @@ const handleGetPluginList = (ipcMain, STORE_PATH, CONFIG_PATH) => {
})
}
const handlePluginInstall = (ipcMain, CONFIG_PATH) => {
ipcMain.on('installPlugin', async (event, msg) => {
const handlePluginInstall = (ipcMain: IpcMain, CONFIG_PATH: string) => {
ipcMain.on('installPlugin', async (event: IpcMainEvent, msg: string) => {
const picgo = new PicGo(CONFIG_PATH)
const pluginHandler = new PluginHandler(picgo)
picgo.on('installSuccess', notice => {
picgo.on('installSuccess', (notice: PicGoNotice) => {
event.sender.send('installSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
})
picgo.on('failed', () => {
@ -106,11 +117,11 @@ const handlePluginInstall = (ipcMain, CONFIG_PATH) => {
})
}
const handlePluginUninstall = (ipcMain, CONFIG_PATH) => {
ipcMain.on('uninstallPlugin', async (event, msg) => {
const handlePluginUninstall = (ipcMain: IpcMain, CONFIG_PATH: string) => {
ipcMain.on('uninstallPlugin', async (event: IpcMainEvent, msg: string) => {
const picgo = new PicGo(CONFIG_PATH)
const pluginHandler = new PluginHandler(picgo)
picgo.on('uninstallSuccess', notice => {
picgo.on('uninstallSuccess', (notice: PicGoNotice) => {
event.sender.send('uninstallSuccess', notice.body[0].replace(/picgo-plugin-/, ''))
})
picgo.on('failed', () => {
@ -121,8 +132,8 @@ const handlePluginUninstall = (ipcMain, CONFIG_PATH) => {
})
}
const handlePluginUpdate = (ipcMain, CONFIG_PATH) => {
ipcMain.on('updatePlugin', async (event, msg) => {
const handlePluginUpdate = (ipcMain: IpcMain, CONFIG_PATH: string) => {
ipcMain.on('updatePlugin', async (event: IpcMainEvent, msg: string) => {
const picgo = new PicGo(CONFIG_PATH)
const pluginHandler = new PluginHandler(picgo)
picgo.on('updateSuccess', notice => {
@ -141,15 +152,15 @@ const handleNPMError = () => {
title: '发生错误',
message: '请安装Node.js并重启PicGo再继续操作',
buttons: ['Yes']
}, (res) => {
if (res === 0) {
}).then((res) => {
if (res.response === 0) {
shell.openExternal('https://nodejs.org/')
}
})
}
const handleGetPicBedConfig = (ipcMain, CONFIG_PATH) => {
ipcMain.on('getPicBedConfig', (event, type) => {
const handleGetPicBedConfig = (ipcMain: IpcMain, CONFIG_PATH: string) => {
ipcMain.on('getPicBedConfig', (event: IpcMainEvent, type: string) => {
const picgo = new PicGo(CONFIG_PATH)
const name = picgo.helper.uploader.get(type).name || type
if (picgo.helper.uploader.get(type).config) {
@ -162,13 +173,13 @@ const handleGetPicBedConfig = (ipcMain, CONFIG_PATH) => {
})
}
const handlePluginActions = (ipcMain, CONFIG_PATH) => {
ipcMain.on('pluginActions', (event, name, label) => {
const handlePluginActions = (ipcMain: IpcMain, CONFIG_PATH: string) => {
ipcMain.on('pluginActions', (event: IpcMainEvent, name: string, label: string) => {
const picgo = new PicGo(CONFIG_PATH)
const plugin = picgo.pluginLoader.getPlugin(`picgo-plugin-${name}`)
const guiApi = new GuiApi(ipcMain, event.sender, picgo)
if (plugin.guiMenu && plugin.guiMenu(picgo).length > 0) {
const menu = plugin.guiMenu(picgo)
const menu: GuiMenuItem[] = plugin.guiMenu(picgo)
menu.forEach(item => {
if (item.label === label) {
item.handle(picgo, guiApi)
@ -178,8 +189,8 @@ const handlePluginActions = (ipcMain, CONFIG_PATH) => {
})
}
const handleRemoveFiles = (ipcMain, CONFIG_PATH) => {
ipcMain.on('removeFiles', (event, files) => {
const handleRemoveFiles = (ipcMain: IpcMain, CONFIG_PATH: string) => {
ipcMain.on('removeFiles', (event: IpcMainEvent, files: ImgInfo[]) => {
const picgo = new PicGo(CONFIG_PATH)
const guiApi = new GuiApi(ipcMain, event.sender, picgo)
setTimeout(() => {
@ -188,18 +199,18 @@ const handleRemoveFiles = (ipcMain, CONFIG_PATH) => {
})
}
const handlePluginShortKeyRegister = (plugin) => {
if (plugin.shortKeys && plugin.shortKeys.length > 0) {
let shortKeyConfig = db.get('settings.shortKey')
plugin.shortKeys.forEach(item => {
if (!shortKeyConfig[item.name]) {
shortKeyConfig[item.name] = item
}
})
}
}
// const handlePluginShortKeyRegister = (plugin) => {
// if (plugin.shortKeys && plugin.shortKeys.length > 0) {
// let shortKeyConfig = db.get('settings.shortKey')
// plugin.shortKeys.forEach(item => {
// if (!shortKeyConfig[item.name]) {
// shortKeyConfig[item.name] = item
// }
// })
// }
// }
export default (app, ipcMain) => {
export default (app: App, ipcMain: IpcMain) => {
const STORE_PATH = app.getPath('userData')
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
handleGetPluginList(ipcMain, STORE_PATH, CONFIG_PATH)
@ -209,5 +220,5 @@ export default (app, ipcMain) => {
handleGetPicBedConfig(ipcMain, CONFIG_PATH)
handlePluginActions(ipcMain, CONFIG_PATH)
handleRemoveFiles(ipcMain, CONFIG_PATH)
handlePluginShortKeyRegister({})
// handlePluginShortKeyRegister({})
}

View File

@ -1,4 +1,7 @@
import bus from './eventBus'
import {
GlobalShortcut
} from 'electron'
let isInModifiedMode = false // 修改快捷键模式
bus.on('toggleShortKeyModifiedMode', flag => {
isInModifiedMode = flag
@ -7,7 +10,7 @@ bus.on('toggleShortKeyModifiedMode', flag => {
*
* @param {string} name
*/
const shortKeyHandler = (name) => {
const shortKeyHandler = (name: string) => {
if (isInModifiedMode) {
return
}
@ -20,11 +23,8 @@ const shortKeyHandler = (name) => {
/**
*
* @param {globalShortcut} globalShortcut
* @param {keyObject} item
* @param {string} oldKey
*/
const shortKeyUpdater = (globalShortcut, item, oldKey) => {
const shortKeyUpdater = (globalShortcut: GlobalShortcut, item: ShortKeyConfig, oldKey: string) => {
// 如果提供了旧key则解绑
if (oldKey) {
globalShortcut.unregister(oldKey)
@ -39,7 +39,7 @@ const shortKeyUpdater = (globalShortcut, item, oldKey) => {
}
// 初始化阶段的注册
const initShortKeyRegister = (globalShortcut, shortKeys) => {
const initShortKeyRegister = (globalShortcut: GlobalShortcut, shortKeys: ShortKeyConfig[]) => {
let errorList = []
for (let i in shortKeys) {
try {

View File

@ -1,10 +1,15 @@
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://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
const isDevelopment = process.env.NODE_ENV !== 'production'
if (isDevelopment) {
release = `${release}?access_token=${process.env.GITHUB_TOKEN}`
}
const checkVersion = async () => {
let showTip = db.get('settings.showUpdateTip')
@ -13,7 +18,12 @@ const checkVersion = async () => {
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 result = compareVersion2Update(version, latest)
@ -25,11 +35,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.set('settings.showUpdateTip', !checkboxChecked)
db.set('settings.showUpdateTip', !res.checkboxChecked)
})
}
} else {
@ -41,7 +51,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))

View File

@ -2,20 +2,39 @@ import {
app,
Notification,
BrowserWindow,
ipcMain
ipcMain,
WebContents
} from 'electron'
import path from 'path'
import dayjs from 'dayjs'
import PicGoCore from '~/universal/types/picgo'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo')
const PicGo = requireFunc('picgo') as typeof PicGoCore
const STORE_PATH = app.getPath('userData')
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
const renameURL = process.env.NODE_ENV === 'development' ? `http://localhost:9080/#rename-page` : `file://${__dirname}/index.html#rename-page`
const createRenameWindow = (win) => {
let options = {
// type BrowserWindowOptions = {
// height: number,
// width: number,
// show: boolean,
// fullscreenable: boolean,
// resizable: boolean,
// vibrancy: string | any,
// webPreferences: {
// nodeIntegration: boolean,
// nodeIntegrationInWorker: boolean,
// backgroundThrottling: boolean
// },
// backgroundColor?: string
// autoHideMenuBar?: boolean
// transparent?: boolean
// }
const createRenameWindow = (win: BrowserWindow) => {
let options: BrowserWindowOptions = {
height: 175,
width: 300,
show: true,
@ -55,7 +74,7 @@ const createRenameWindow = (win) => {
return window
}
const waitForShow = (webcontent) => {
const waitForShow = (webcontent: WebContents) => {
return new Promise((resolve, reject) => {
webcontent.on('dom-ready', () => {
resolve()
@ -63,9 +82,9 @@ const waitForShow = (webcontent) => {
})
}
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()
})
@ -77,15 +96,18 @@ const waitForRename = (window, id) => {
}
class Uploader {
constructor (img, webContents, picgo = undefined) {
private picgo: PicGoCore
private webContents: WebContents
private img: undefined | string[]
constructor (img: undefined | string[], webContents: WebContents, picgo: PicGoCore | undefined = undefined) {
this.img = img
this.webContents = webContents
this.picgo = picgo
this.picgo = picgo || new PicGo(CONFIG_PATH)
}
upload () {
upload (): Promise<ImgInfo[]|false> {
const win = BrowserWindow.fromWebContents(this.webContents)
const picgo = this.picgo || new PicGo(CONFIG_PATH)
const picgo = this.picgo
picgo.config.debug = true
// for picgo-core
picgo.config.PICGO_ENV = 'GUI'
@ -96,8 +118,8 @@ class Uploader {
const rename = picgo.getConfig('settings.rename')
const autoRename = picgo.getConfig('settings.autoRename')
await Promise.all(ctx.output.map(async (item, index) => {
let name
let fileName
let name: undefined | string | null
let fileName: string | undefined
if (autoRename) {
fileName = dayjs().add(index, 'second').format('YYYYMMDDHHmmss') + item.extname
} else {
@ -137,7 +159,7 @@ class Uploader {
return new Promise((resolve) => {
picgo.on('finished', ctx => {
if (ctx.output.every(item => item.imgUrl)) {
if (ctx.output.every((item: ImgInfo) => item.imgUrl)) {
resolve(ctx.output)
} else {
resolve(false)

View File

@ -5,9 +5,9 @@
</template>
<script>
export default {
name: 'picgo'
}
export default {
name: 'picgo'
}
</script>
<style lang="stylus">

View File

@ -34,4 +34,4 @@ export default {
}
</script>
<style lang='stylus'>
</style>
</style>

View File

@ -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.get(`picBed.${this.id}`)
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(Array) readonly config: Array<any> = []
@Prop(String) readonly type: string = ''
@Prop(String) 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>

View File

@ -105,7 +105,7 @@
label="用占位符$url来表示url的位置"
prop="value"
>
<el-input
<el-input
class="align-center"
v-model="customLink.value"
:autofocus="true"
@ -136,161 +136,160 @@
</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.get('customLink') || '$url'
},
rules: {
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
},
os: '',
shortKey: {
upload: db.get('shortKey.upload')
},
picBed: [],
// for showInputBox
showInputBoxVisible: false,
inputBoxValue: '',
inputBoxOptions: {
title: '',
placeholder: ''
}
}
},
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: ShortKeyMap = {
upload: db.get('shortKey.upload')
}
picBed: PicBedType[] = []
// 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)
this.$electron.ipcRenderer.on('showInputBox', (evt, options) => {
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
})
},
methods: {
handleSelect (index) {
const type = index.match(/picbeds-/)
if (type === null) {
}
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.get('shortKey')
},
cancelCustomLink () {
this.customLinkVisible = false
this.customLink.value = db.get('customLink') || '$url'
},
confirmCustomLink () {
this.$refs.customLink.validate((valid) => {
if (valid) {
db.set('customLink', this.customLink.value)
this.customLinkVisible = false
this.$electron.ipcRenderer.send('updateCustomLink')
} else {
return false
}
})
},
openMiniWindow () {
this.$electron.ipcRenderer.send('openMiniWindow')
},
getPicBeds (event, picBeds) {
this.picBed = picBeds
},
handleInputBoxClose () {
this.$electron.ipcRenderer.send('showInputBox', this.inputBoxValue)
}
},
beforeRouteEnter: (to, from, next) => {
next(vm => {
]
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: PicBedType[]) {
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)
this.$electron.ipcRenderer.removeAllListeners('showInputBox')
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
ipcRenderer.removeAllListeners('showInputBox')
}
}
</script>
@ -360,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
@ -384,7 +383,7 @@ $darwinBg = transparentify(#172426, #000, 0.7)
&:before
content ''
position absolute
width 3px
width 3px
height 20px
right 0
top 18px
@ -406,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
@ -424,4 +423,4 @@ $darwinBg = transparentify(#172426, #000, 0.7)
background #6f6f6f
*::-webkit-scrollbar-track
background-color transparent
</style>
</style>

View File

@ -72,7 +72,7 @@
: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"
@click="zoomImage(index)"
>
@ -83,7 +83,7 @@
<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,257 +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: ObjT<boolean> = {}
choosedPicBed: string[] = []
searchText = ''
handleBarActive = false
pasteStyle = ''
pasteStyleMap = {
Markdown: 'markdown',
HTML: 'HTML',
URL: 'URL',
UBB: 'UBB',
Custom: 'Custom'
}
picBed: PicBedType[] = []
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: PicBedType[]) {
this.picBed = picBeds
}
getGallery () {
if (this.choosedPicBed.length > 0) {
let arr: ImgInfo[] = []
this.choosedPicBed.forEach(item => {
let obj: Obj = {
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 style = this.$db.get('settings.pasteStyle') || 'markdown'
const copyLink = pasteStyle(style, item)
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: item.url || item.imgUrl
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: 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(() => {
const file = this.$db.get('uploaded').getById(id)
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.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]) {
const item = this.$db.read().get('uploaded').getById(key).value()
copyString += pasteStyle(style, item) + '\n'
this.choosedList[key] = false
}
})
const obj = {
title: '批量复制链接成功',
body: copyString
}
const myNotification = new window.Notification(obj.title, obj)
this.$electron.clipboard.writeText(copyString)
myNotification.onclick = () => {
return true
}
}
},
toggleHandleBar () {
this.handleBarActive = !this.handleBarActive
},
getPasteStyle () {
this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
},
handlePasteStyleChange (val) {
this.$db.set('settings.pasteStyle', val)
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>
@ -428,7 +437,7 @@ export default {
&-fake
position absolute
top 0
left 0
left 0
opacity 0
width 100%
z-index -1
@ -456,4 +465,4 @@ export default {
margin-bottom 10px
.el-input__inner
border-radius 14px
</style>
</style>

View File

@ -15,31 +15,35 @@
</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'
@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 = 'static/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: PicBedType[] = []
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 +53,141 @@ 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: FileWithPath[] = []
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.map(item => {
return {
label: item.name,
type: 'radio',
checked: this.$db.get('picBed.current') === item.type,
click () {
_this.$db.set('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.get('picBed.current') === item.type,
click () {
_this.$db.set('picBed.current', item.type)
_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 +230,4 @@ export default {
background rgba(0,0,0,0.3)
#file-uploader
display none
</style>
</style>

View File

@ -141,7 +141,7 @@
<div class="custom-title">
用占位符 <b>$fileName</b> 来表示文件名的位置
</div>
<el-input
<el-input
class="align-center"
v-model="customLink.value"
:autofocus="true"
@ -171,7 +171,7 @@
<el-form-item
label="代理地址"
>
<el-input
<el-input
v-model="proxy"
:autofocus="true"
placeholder="例如http://127.0.0.1:1080"
@ -241,32 +241,224 @@
</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: PicBedType[] = []
logFileVisible = false
keyBindingVisible = false
customLinkVisible = false
checkUpdateVisible = false
proxyVisible = false
customLink = {
value: db.get('settings.customLink') || '$url'
}
shortKey: ShortKeyMap = {
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'
}
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: PicBedType[]) {
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('+')
}
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = db.get('settings.shortKey')
}
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
db.set('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 item
})
db.set('picBed.list', list)
ipcRenderer.send('getPicBeds')
}
handleAutoStartChange (val: boolean) {
db.set('settings.autoStart', val)
ipcRenderer.send('autoStart', val)
}
handleRename (val: boolean) {
db.set('settings.rename', val)
}
handleAutoRename (val: boolean) {
db.set('settings.autoRename', val)
}
compareVersion2Update (current: string, latest: string) {
const currentVersion = current.split('.').map(item => parseInt(item))
const latestVersion = latest.split('.').map(item => parseInt(item))
for (let i = 0; i < 3; i++) {
if (currentVersion[i] < latestVersion[i]) {
return true
}
if (currentVersion[i] > latestVersion[i]) {
return false
}
}
let logLevel = this.$db.get('settings.logLevel')
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('请选择日志记录等级')
}
db.set('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]
@ -274,225 +466,36 @@ export default {
logLevel = ['all']
}
}
return {
form: {
updateHelper: this.$db.get('settings.showUpdateTip'),
showPicBedList: [],
autoStart: this.$db.get('settings.autoStart') || false,
rename: this.$db.get('settings.rename') || false,
autoRename: this.$db.get('settings.autoRename') || false,
uploadNotification: this.$db.get('settings.uploadNotification') || false,
miniWindowOntop: this.$db.get('settings.miniWindowOntop') || false,
logLevel
},
picBed: [],
logFileVisible: false,
keyBindingVisible: false,
customLinkVisible: false,
checkUpdateVisible: false,
proxyVisible: false,
customLink: {
value: this.$db.get('settings.customLink') || '$url'
},
shortKey: {
upload: this.$db.get('settings.shortKey.upload')
},
proxy: this.$db.get('picBed.proxy') || undefined,
rules: {
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
},
logLevel: {
all: '全部-All',
success: '成功-Success',
error: '错误-Error',
info: '普通-Info',
warn: '提醒-Warn',
none: '不记录日志-None'
},
version: pkg.version,
latestVersion: '',
os: ''
}
},
created () {
this.os = process.platform
this.$electron.ipcRenderer.send('getPicBeds')
this.$electron.ipcRenderer.on('getPicBeds', this.getPicBeds)
},
methods: {
getPicBeds (event, picBeds) {
this.picBed = picBeds
this.form.showPicBedList = this.picBed.map(item => {
if (item.visible) {
return item.name
}
})
},
openFile (file) {
const { app, shell } = this.$electron.remote
const STORE_PATH = app.getPath('userData')
const FILE = path.join(STORE_PATH, `/${file}`)
shell.openItem(FILE)
},
openLogSetting () {
this.logFileVisible = true
},
keyDetect (type, event) {
this.shortKey[type] = keyDetect(event).join('+')
},
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = this.$db.get('settings.shortKey')
},
cancelCustomLink () {
this.customLinkVisible = false
this.customLink.value = this.$db.get('settings.customLink') || '$url'
},
confirmCustomLink () {
this.$refs.customLink.validate((valid) => {
if (valid) {
this.$db.set('settings.customLink', this.customLink.value)
this.customLinkVisible = false
this.$electron.ipcRenderer.send('updateCustomLink')
} else {
return false
}
})
},
cancelProxy () {
this.proxyVisible = false
this.proxy = this.$db.get('picBed.proxy') || undefined
},
confirmProxy () {
this.proxyVisible = false
this.$db.set('picBed.proxy', this.proxy)
const successNotification = new window.Notification('设置代理', {
body: '设置成功'
})
successNotification.onclick = () => {
this.form.logLevel = logLevel
}
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
}
},
updateHelperChange (val) {
this.$db.set('settings.showUpdateTip', val)
},
handleShowPicBedListChange (val) {
const list = this.picBed.map(item => {
if (!val.includes(item.name)) {
item.visible = false
} else {
item.visible = true
}
return item
})
this.$db.set('picBed.list', list)
this.$electron.ipcRenderer.send('getPicBeds')
},
handleAutoStartChange (val) {
this.$db.set('settings.autoStart', val)
this.$electron.ipcRenderer.send('autoStart', val)
},
handleRename (val) {
this.$db.set('settings.rename', val)
},
handleAutoRename (val) {
this.$db.set('settings.autoRename', val)
},
compareVersion2Update (current, latest) {
const currentVersion = current.split('.').map(item => parseInt(item))
const latestVersion = latest.split('.').map(item => parseInt(item))
for (let i = 0; i < 3; i++) {
if (currentVersion[i] < latestVersion[i]) {
return true
}
if (currentVersion[i] > latestVersion[i]) {
return false
}
}
return false
},
checkUpdate () {
this.checkUpdateVisible = true
this.$http.get(release)
.then(res => {
this.latestVersion = res.data.name
}).catch(err => {
console.log(err)
})
},
confirmCheckVersion () {
if (this.needUpdate) {
this.$electron.remote.shell.openExternal(downloadUrl)
}
this.checkUpdateVisible = false
},
cancelCheckVersion () {
this.checkUpdateVisible = false
},
handleUploadNotification (val) {
this.$db.set('settings.uploadNotification', val)
},
handleMiniWindowOntop (val) {
this.$db.set('settings.miniWindowOntop', val)
this.$message('需要重启生效')
},
confirmLogLevelSetting () {
if (this.form.logLevel.length === 0) {
return this.$message.error('请选择日志记录等级')
}
this.$db.set('settings.logLevel', this.form.logLevel)
const successNotification = new window.Notification('设置日志', {
body: '设置成功'
})
successNotification.onclick = () => {
} else if (this.form.logLevel.length > 0) {
if (val === 'all' || val === 'none') {
return true
}
this.logFileVisible = false
},
cancelLogLevelSetting () {
this.logFileVisible = false
let logLevel = this.$db.get('settings.logLevel')
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
this.form.logLevel = logLevel
},
handleLevelDisabled (val) {
let currentLevel = val
let flagLevel
let result = this.form.logLevel.some(item => {
if (item === 'all' || item === 'none') {
flagLevel = item
}
return (item === 'all' || item === 'none')
})
if (result) {
if (currentLevel !== flagLevel) {
return true
}
} else if (this.form.logLevel.length > 0) {
if (val === 'all' || val === 'none') {
return true
}
}
return false
},
goConfigPage () {
this.$electron.remote.shell.openExternal('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
},
goShortCutPage () {
this.$router.push('shortcut-page')
}
},
return false
}
goConfigPage () {
remote.shell.openExternal('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
}
goShortCutPage () {
this.$router.push('shortcut')
}
beforeDestroy () {
this.$electron.ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}
}
</script>
@ -515,7 +518,7 @@ export default {
overflow-x hidden
.setting-list
.el-form
label
label
line-height 32px
padding-bottom 0
color #eee
@ -549,4 +552,4 @@ export default {
margin-left 0
.confirm-button
width 100%
</style>
</style>

View File

@ -93,58 +93,65 @@
</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: ''
}
})
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 = ''
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: {
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) {
@ -153,7 +160,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) {
@ -165,7 +172,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
@ -184,241 +191,241 @@ export default {
this.getPluginList()
this.getSearchResult = debounce(this.getSearchResult, 50)
this.needReload = this.$db.get('needReload')
},
methods: {
buildContextMenu (plugin) {
const _this = this
let menu = [{
label: '启用插件',
enabled: !plugin.enabled,
click () {
_this.$db.set(`picgoPlugins.picgo-plugin-${plugin.name}`, true)
plugin.enabled = true
_this.getPicBeds()
}
buildContextMenu (plugin: IPicGoPlugin) {
const _this = this
let menu = [{
label: '启用插件',
enabled: !plugin.enabled,
click () {
_this.$db.set(`picgoPlugins.picgo-plugin-${plugin.name}`, true)
plugin.enabled = true
_this.getPicBeds()
}
}, {
label: '禁用插件',
enabled: plugin.enabled,
click () {
_this.$db.set(`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.set(`picgoPlugins.picgo-plugin-${plugin.name}`, false)
plugin.enabled = false
_this.getPicBeds()
if (plugin.config.transformer.name) {
_this.handleRestoreState('transformer', plugin.config.transformer.name)
}
if (plugin.config.uploader.name) {
_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.get('picBed.transformer') || '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
}
})
this.$electron.ipcRenderer.send('uninstallPlugin', val)
},
updatePlugin (val) {
this.pluginList.forEach(item => {
if (item.name === val) {
item.ing = true
}
})
this.$electron.ipcRenderer.send('updatePlugin', val)
},
reloadApp () {
this.$electron.remote.app.relaunch()
this.$electron.remote.app.exit(0)
},
handleReload () {
this.$db.set('needReload', true)
this.needReload = true
const successNotification = new window.Notification('更新成功', {
body: '请点击此通知重启应用以生效'
})
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.$db.set('picBed.transformer', 'path')
} else {
this.$db.set('picBed.transformer', transformer)
}
}
async handleConfirmConfig () {
// @ts-ignore
const result = await this.$refs.configForm.validate()
if (result !== false) {
switch (this.currentType) {
case 'plugin':
this.$db.set(`picgo-plugin-${this.configName}`, result)
break
case 'uploader':
this.$db.set(`picBed.${this.configName}`, result)
break
case 'transformer':
this.$db.set(`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.get('picBed.transformer') || 'path'
if (currentTransformer === transformer) {
this.$db.set('picBed.transformer', 'path')
} else {
this.$db.set('picBed.transformer', transformer)
}
},
async handleConfirmConfig () {
const result = await this.$refs.configForm.validate()
if (result !== false) {
switch (this.currentType) {
case 'plugin':
this.$db.set(`picgo-plugin-${this.configName}`, result)
break
case 'uploader':
this.$db.set(`picBed.${this.configName}`, result)
break
case 'transformer':
this.$db.set(`transformer.${this.configName}`, result)
break
}
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
this.dialogVisible = false
this.getPluginList()
}
},
getSearchResult: function (val) {
// this.$http.get(`https://api.npms.io/v2/search?q=${val}`)
this.$http.get(`https://registry.npmjs.com/-/v1/search?text=${val}`)
.then(res => {
this.pluginList = res.data.objects.map(item => {
return this.handleSearchResult(item)
})
this.loading = false
})
.catch(err => {
console.log(err)
this.loading = false
})
},
handleSearchResult (item) {
const name = item.package.name.replace(/picgo-plugin-/, '')
let gui = false
if (item.package.keywords && item.package.keywords.length > 0) {
if (item.package.keywords.includes('picgo-gui-plugin')) {
gui = true
}
}
return {
name: name,
author: item.package.author.name,
description: item.package.description,
logo: `https://cdn.jsdelivr.net/npm/${item.package.name}/logo.png`,
config: {},
homepage: item.package.links ? item.package.links.homepage : '',
hasInstall: this.pluginNameList.some(plugin => plugin === item.package.name.replace(/picgo-plugin-/, '')),
version: item.package.version,
gui,
ing: false // installing or uninstalling
}
},
// restore Uploader & Transformer
handleRestoreState (item, name) {
if (item === 'uploader') {
const current = this.$db.get('picBed.current')
if (current === name) {
this.$db.set('picBed.current', 'smms')
}
}
if (item === 'transformer') {
const current = this.$db.get('picBed.transformer')
if (current === name) {
this.$db.set('picBed.transformer', 'path')
}
}
},
openHomepage (url) {
if (url) {
this.$electron.remote.shell.openExternal(url)
}
},
goAwesomeList () {
this.$electron.remote.shell.openExternal('https://github.com/PicGo/Awesome-PicGo')
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.map((item: INPMSearchResultObject) => {
return this.handleSearchResult(item)
})
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: string, name: string) {
if (item === 'uploader') {
const current = this.$db.get('picBed.current')
if (current === name) {
this.$db.set('picBed.current', 'smms')
}
}
if (item === 'transformer') {
const current = this.$db.get('picBed.transformer')
if (current === name) {
this.$db.set('picBed.transformer', 'path')
}
}
}
openHomepage (url: string) {
if (url) {
remote.shell.openExternal(url)
}
}
goAwesomeList () {
remote.shell.openExternal('https://github.com/PicGo/Awesome-PicGo')
}
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('pluginList')
this.$electron.ipcRenderer.removeAllListeners('installSuccess')
this.$electron.ipcRenderer.removeAllListeners('uninstallSuccess')
this.$electron.ipcRenderer.removeAllListeners('updateSuccess')
ipcRenderer.removeAllListeners('pluginList')
ipcRenderer.removeAllListeners('installSuccess')
ipcRenderer.removeAllListeners('uninstallSuccess')
ipcRenderer.removeAllListeners('updateSuccess')
}
}
</script>
@ -562,4 +569,4 @@ $darwinBg = #172426
padding 10px 0
&.cut-width
width calc(100% - 48px)
</style>
</style>

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>

View File

@ -0,0 +1,181 @@
<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="180px"
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="来源"
>
<template slot-scope="scope">
{{ calcOrigin(scope.row.name) }}
</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.name, 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 } from 'electron'
@Component({
name: 'shortcut-page'
})
export default class extends Vue {
list: ShortKeyConfig[] = []
keyBindingVisible = false
shortKeyName = ''
shortKey = ''
currentIndex = 0
created () {
const shortKeyConfig = this.$db.get('settings.shortKey') as ShortKeyConfigs
this.list = Object.keys(shortKeyConfig).map(item => shortKeyConfig[item])
}
@Watch('keyBindingVisible')
onKeyBindingVisibleChange (val: boolean) {
ipcRenderer.send('toggleShortKeyModifiedMode', val)
}
calcOrigin (item: string) {
const [origin] = item.split(':')
return origin
}
toggleEnable (item: ShortKeyConfig) {
const status = !item.enable
item.enable = status
this.$db.set(`settings.shortKey.${item.name}.enable`, status)
ipcRenderer.send('updateShortKey', item)
}
keyDetect (event: KeyboardEvent) {
this.shortKey = keyDetect(event).join('+')
}
openKeyBindingDialog (name: string, index: number) {
this.shortKeyName = name
this.shortKey = this.$db.get(`settings.shortKey.${name}.key`)
this.currentIndex = index
this.keyBindingVisible = true
}
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = this.$db.get(`settings.shortKey.${this.shortKeyName}.key`)
}
confirmKeyBinding () {
const oldKey = this.$db.get(`settings.shortKey.${this.shortKeyName}.key`)
this.$db.set(`settings.shortKey.${this.shortKeyName}.key`, this.shortKey)
const newKey = this.$db.get(`settings.shortKey.${this.shortKeyName}`)
ipcRenderer.send('updateShortKey', newKey, oldKey)
this.list[this.currentIndex].key = this.shortKey
this.keyBindingVisible = false
}
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>

View File

@ -25,92 +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.insert('uploaded', item)
})
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()
console.log(this.files)
this.uploadFlag = false
})
this.$electron.ipcRenderer.on('updateFiles', (event) => {
this.getData()
})
},
beforeDestroy () {
this.$electron.ipcRenderer.removeAllListeners('dragFiles')
this.$electron.ipcRenderer.removeAllListeners('clipboardFiles')
this.$electron.ipcRenderer.removeAllListeners('uploadClipboardFiles')
this.$electron.ipcRenderer.removeAllListeners('updateFiles')
},
methods: {
getData () {
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
},
copyTheLink (item) {
this.notification.body = item.imgUrl
this.notification.icon = item.imgUrl
const myNotification = new window.Notification(this.notification.title, this.notification)
const pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
this.$electron.clipboard.writeText(pasteTemplate(pasteStyle, item))
myNotification.onclick = () => {
return true
}
},
calcHeight (width, height) {
return height * 160 / width
},
disableDragFile () {
window.addEventListener('dragover', (e) => {
e = e || event
e.preventDefault()
}, false)
window.addEventListener('drop', (e) => {
e = e || event
e.preventDefault()
}, false)
},
uploadClipboardFiles () {
if (this.uploadFlag) {
return
}
this.uploadFlag = true
this.$electron.ipcRenderer.send('uploadClipboardFiles')
}
<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">

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: PicBedType[] = []
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,93 +87,92 @@ 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: FileWithPath[] = []
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: PicBedType[]) {
this.picBed = picBeds
this.getDefaultPicBed()
}
handleChangePicBed () {
this.buildMenu()
// this.menu.popup(remote.getCurrentWindow())
this.menu!.popup()
}
buildMenu () {
const _this = this
const submenu = this.picBed.map(item => {
return {
label: item.name,
type: 'radio',
checked: this.$db.get('picBed.current') === item.type,
click () {
_this.$db.set('picBed.current', item.type)
ipcRenderer.send('syncPicBed')
}
sendFiles.push(obj)
})
this.$electron.ipcRenderer.send('uploadChoosedFiles', sendFiles)
},
getPasteStyle () {
this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
},
handlePasteStyleChange (val) {
this.$db.set('settings.pasteStyle', val)
},
uploadClipboardFiles () {
this.$electron.ipcRenderer.send('uploadClipboardFilesFromUploadPage')
},
getDefaultPicBed () {
const current = this.$db.get('picBed.current')
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.get('picBed.current') === item.type,
click () {
_this.$db.set('picBed.current', item.type)
_this.$electron.ipcRenderer.send('syncPicBed')
}
}
})
this.menu = this.$electron.remote.Menu.buildFromTemplate(submenu)
}
}
})
// @ts-ignore
this.menu = Menu.buildFromTemplate(submenu)
}
}
</script>
@ -235,4 +239,6 @@ export default {
border-radius 0 14px 14px 0
.paste-upload
width 100%
</style>
.el-icon-caret-bottom
cursor pointer
</style>

View File

@ -5,7 +5,7 @@
<div class="view-title">
阿里云OSS设置
</div>
<el-form
<el-form
ref="aliyun"
label-position="right"
label-width="120px"
@ -111,7 +111,7 @@ export default {
<style lang='stylus'>
#aliyun-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@ -122,7 +122,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 +146,4 @@ export default {
transition .2s color ease-in-out
&:hover
color #409EFF
</style>
</style>

View File

@ -5,7 +5,7 @@
<div class="view-title">
GitHub设置
</div>
<el-form
<el-form
ref="github"
label-position="right"
label-width="120px"
@ -102,7 +102,7 @@ export default {
<style lang='stylus'>
#github-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@ -111,7 +111,7 @@ export default {
.el-radio-group
width 100%
label
width 25%
width 25%
.el-radio-button__inner
width 100%
.el-radio-button:first-child

View File

@ -5,7 +5,7 @@
<div class="view-title">
Imgur图床设置
</div>
<el-form
<el-form
ref="imgur"
label-position="right"
label-width="120px"
@ -79,7 +79,7 @@ export default {
<style lang='stylus'>
#imgur-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@ -90,7 +90,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 +114,4 @@ export default {
transition .2s color ease-in-out
&:hover
color #409EFF
</style>
</style>

View File

@ -84,7 +84,7 @@ export default {
<style lang='stylus'>
#others-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@ -106,4 +106,4 @@ export default {
margin-bottom 10px
.single
text-align center
</style>
</style>

View File

@ -5,7 +5,7 @@
<div class="view-title">
七牛图床设置
</div>
<el-form
<el-form
ref="qiniu"
label-position="right"
label-width="120px"
@ -119,7 +119,7 @@ export default {
<style lang='stylus'>
#qiniu-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@ -130,7 +130,7 @@ export default {
.el-radio-group
width 100%
label
width 25%
width 25%
.el-radio-button__inner
width 100%
.el-radio-button:first-child
@ -141,4 +141,4 @@ export default {
.el-radio-button__inner
border-left none
border-radius 0 14px 14px 0
</style>
</style>

View File

@ -5,7 +5,7 @@
<div class="view-title">
腾讯云COS设置
</div>
<el-form
<el-form
ref="tcyun"
label-position="right"
label-width="120px"
@ -137,7 +137,7 @@ export default {
<style lang='stylus'>
#tcyun-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@ -148,7 +148,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 +172,4 @@ export default {
transition .2s color ease-in-out
&:hover
color #409EFF
</style>
</style>

View File

@ -5,7 +5,7 @@
<div class="view-title">
又拍云设置
</div>
<el-form
<el-form
ref="tcyun"
label-position="right"
label-width="120px"
@ -110,7 +110,7 @@ export default {
<style lang='stylus'>
#tcyun-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@ -119,7 +119,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 +130,4 @@ export default {
.el-radio-button__inner
border-left none
border-radius 0 14px 14px 0
</style>
</style>

View File

@ -5,7 +5,7 @@
<div class="view-title">
微博图床设置
</div>
<el-form
<el-form
ref="weiboForm"
label-position="right"
label-width="120px"
@ -123,7 +123,7 @@ export default {
left 60%
#weibo-view
.el-form
label
label
line-height 22px
padding-bottom 0
color #eee
@ -148,4 +148,4 @@ export default {
transition .2s color ease-in-out
&:hover
color #409EFF
</style>
</style>

View File

@ -1,108 +0,0 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'tray-page',
component: require('@/pages/TrayPage').default
},
{
path: '/rename-page',
name: 'rename-page',
component: require('@/pages/RenamePage').default
},
{
path: '/mini-page',
name: 'mini-page',
component: require('@/pages/MiniPage').default
},
{
path: '/setting',
name: 'setting-page',
component: require('@/layouts/SettingPage').default,
children: [
{
path: 'upload',
component: require('@/pages/Upload').default,
name: 'upload'
},
{
path: 'weibo',
component: require('@/pages/picbeds/Weibo').default,
name: 'weibo'
},
{
path: 'qiniu',
component: require('@/pages/picbeds/Qiniu').default,
name: 'qiniu'
},
{
path: 'tcyun',
component: require('@/pages/picbeds/TcYun').default,
name: 'tcyun'
},
{
path: 'upyun',
component: require('@/pages/picbeds/UpYun').default,
name: 'upyun'
},
{
path: 'github',
component: require('@/pages/picbeds/GitHub').default,
name: 'github'
},
{
path: 'smms',
component: require('@/pages/picbeds/SMMS').default,
name: 'smms'
},
{
path: 'aliyun',
component: require('@/pages/picbeds/AliYun').default,
name: 'aliyun'
},
{
path: 'imgur',
component: require('@/pages/picbeds/Imgur').default,
name: 'imgur'
},
{
path: 'others/:type',
component: require('@/pages/picbeds/Others').default,
name: 'others'
},
{
path: 'gallery',
component: require('@/pages/Gallery').default,
name: 'gallery',
meta: {
keepAlive: true
}
},
{
path: 'setting',
component: require('@/pages/PicGoSetting').default,
name: 'setting'
},
{
path: 'plugin',
component: require('@/pages/Plugin').default,
name: 'plugin'
},
{
path: 'shortcut-page',
component: require('@/pages/ShortCutPage').default,
name: 'shortcut-page'
}
]
},
{
path: '*',
redirect: '/'
}
]
})

View File

@ -0,0 +1,108 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'tray-page',
component: () => import(/* webpackChunkName: "tray" */ '@/pages/TrayPage.vue')
},
{
path: '/rename-page',
name: 'rename-page',
component: () => import(/* webpackChunkName: "RenamePage" */ '@/pages/RenamePage.vue')
},
{
path: '/mini-page',
name: 'mini-page',
component: () => import(/* webpackChunkName: "MiniPage" */ '@/pages/MiniPage.vue')
},
{
path: '/setting',
name: 'setting-page',
component: () => import(/* webpackChunkName: "SettingPage" */ '@/layouts/SettingPage.vue'),
children: [
{
path: 'upload',
component: () => import(/* webpackChunkName: "Upload" */ '@/pages/Upload.vue'),
name: 'upload'
},
{
path: 'weibo',
component: () => import(/* webpackChunkName: "Weibo" */ '@/pages/picbeds/Weibo.vue'),
name: 'weibo'
},
{
path: 'qiniu',
component: () => import(/* webpackChunkName: "Qiniu" */ '@/pages/picbeds/Qiniu.vue'),
name: 'qiniu'
},
{
path: 'tcyun',
component: () => import(/* webpackChunkName: "TcYun" */ '@/pages/picbeds/TcYun.vue'),
name: 'tcyun'
},
{
path: 'upyun',
component: () => import(/* webpackChunkName: "UpYun" */ '@/pages/picbeds/UpYun.vue'),
name: 'upyun'
},
{
path: 'github',
component: () => import(/* webpackChunkName: "GitHub" */ '@/pages/picbeds/GitHub.vue'),
name: 'github'
},
{
path: 'smms',
component: () => import(/* webpackChunkName: "SMMS" */ '@/pages/picbeds/SMMS.vue'),
name: 'smms'
},
{
path: 'aliyun',
component: () => import(/* webpackChunkName: "AliYun" */ '@/pages/picbeds/AliYun.vue'),
name: 'aliyun'
},
{
path: 'imgur',
component: () => import(/* webpackChunkName: "Imgur" */ '@/pages/picbeds/Imgur.vue'),
name: 'imgur'
},
{
path: 'others/:type',
component: () => import(/* webpackChunkName: "Other" */ '@/pages/picbeds/Others.vue'),
name: 'others'
},
{
path: 'gallery',
component: () => import(/* webpackChunkName: "Gallery" */ '@/pages/Gallery.vue'),
name: 'gallery',
meta: {
keepAlive: true
}
},
{
path: 'setting',
component: () => import(/* webpackChunkName: "setting" */ '@/pages/PicGoSetting.vue'),
name: 'setting'
},
{
path: 'plugin',
component: () => import(/* webpackChunkName: "Plugin" */ '@/pages/Plugin.vue'),
name: 'plugin'
},
{
path: 'shortcut',
component: () => import(/* webpackChunkName: "ShortkeyPage" */ '@/pages/ShortCut.vue'),
name: 'shortcut'
}
]
},
{
path: '*',
redirect: '/'
}
]
})

View File

@ -1,20 +0,0 @@
export default {
name: '',
data () {
return {
defaultPicBed: this.$db.get('picBed.current')
}
},
methods: {
setDefaultPicBed (type) {
this.$db.set('picBed.current', type)
this.defaultPicBed = type
const successNotification = new window.Notification('设置默认图床', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
}
}
}

View File

@ -0,0 +1,15 @@
import { Component, Vue } from 'vue-property-decorator'
@Component
export default class extends Vue {
defaultPicBed = this.$db.get('picBed.current')
setDefaultPicBed (type: string) {
this.$db.set('picBed.current', type)
this.defaultPicBed = type
const successNotification = new Notification('设置默认图床', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
}
}

View File

@ -1,13 +1,13 @@
class LS {
get (name) {
get (name: string) {
if (localStorage.getItem(name)) {
return JSON.parse(localStorage.getItem(name))
return JSON.parse(localStorage.getItem(name) as string)
} else {
return {}
}
}
set (name, value) {
set (name: string, value: any) {
return localStorage.setItem(name, JSON.stringify(value))
}
}

View File

@ -1,6 +1,6 @@
import keycode from 'keycode'
const isSpecialKey = (keyCode) => {
const isSpecialKey = (keyCode: number) => {
const keyArr = [
16, // Shift
17, // Ctrl
@ -12,14 +12,14 @@ const isSpecialKey = (keyCode) => {
return keyArr.includes(keyCode)
}
const keyDetect = (event) => {
const keyDetect = (event: KeyboardEvent) => {
const meta = process.platform === 'darwin' ? 'Cmd' : 'Super'
let specialKey = {
Ctrl: event.ctrlKey,
Shift: event.shiftKey,
Alt: event.altKey
Alt: event.altKey,
[meta]: event.metaKey
}
specialKey[meta] = event.metaKey
let pressKey = []

View File

@ -1,25 +0,0 @@
export default {
mounted () {
this.disableDragEvent()
},
methods: {
disableDragEvent () {
window.addEventListener('dragenter', this.disableDrag, false)
window.addEventListener('dragover', this.disableDrag)
window.addEventListener('drop', this.disableDrag)
},
disableDrag (e) {
const dropzone = document.getElementById('upload-area')
if (dropzone === null || !dropzone.contains(e.target)) {
e.preventDefault()
e.dataTransfer.effectAllowed = 'none'
e.dataTransfer.dropEffect = 'none'
}
}
},
beforeDestroy () {
window.removeEventListener('dragenter', this.disableDrag, false)
window.removeEventListener('dragover', this.disableDrag)
window.removeEventListener('drop', this.disableDrag)
}
}

View File

@ -0,0 +1,25 @@
import { Component, Vue } from 'vue-property-decorator'
@Component
export default class extends Vue {
mounted () {
this.disableDragEvent()
}
disableDragEvent () {
window.addEventListener('dragenter', this.disableDrag, false)
window.addEventListener('dragover', this.disableDrag)
window.addEventListener('drop', this.disableDrag)
}
disableDrag (e: DragEvent) {
const dropzone = document.getElementById('upload-area')
if (dropzone === null || !dropzone.contains(<Node>e.target)) {
e.preventDefault()
e.dataTransfer!.effectAllowed = 'none'
e.dataTransfer!.dropEffect = 'none'
}
}
beforeDestroy () {
window.removeEventListener('dragenter', this.disableDrag, false)
window.removeEventListener('dragover', this.disableDrag)
window.removeEventListener('drop', this.disableDrag)
}
}

View File

@ -1,17 +1,11 @@
import Datastore from 'lowdb'
// @ts-ignore
import LodashId from 'lodash-id'
import FileSync from 'lowdb/adapters/FileSync'
import path from 'path'
import fs from 'fs-extra'
import { remote, app } from 'electron'
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')
@ -22,6 +16,7 @@ if (process.type !== 'renderer') {
}
class DB {
private db: Datastore.LowdbSync<Datastore.AdapterSync>
constructor () {
const adapter = new FileSync(path.join(STORE_PATH, '/data.json'))
@ -53,13 +48,14 @@ class DB {
get (key = '') {
return this.read().get(key).value()
}
set (key, value) {
set (key: string, value: any) {
return this.read().set(key, value).write()
}
has (key) {
has (key: string) {
return this.read().has(key).value()
}
insert (key, value) {
insert (key: string, value: any): void {
// @ts-ignore
return this.read().get(key).insert(value).write()
}
}

12
src/universal/types/extra-vue.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
import VueRouter, { Route } from 'vue-router'
import db from '#/datastore'
import axios from 'axios'
declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter,
$route: Route,
$db: typeof db
$http: typeof axios
$builtInPicBed: string[]
}
}

2
src/universal/types/picgo.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import PicGoCore from 'picgo'
export default PicGoCore

13
src/universal/types/shims-tsx.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any
}
}
}

4
src/universal/types/shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

161
src/universal/types/types.d.ts vendored Normal file
View File

@ -0,0 +1,161 @@
// global
interface Obj {
[propName: string]: any
}
interface ObjT<T> {
[propName: string]: T
}
// Image && PicBed
interface ImgInfo {
buffer?: Buffer
base64Image?: string
fileName?: string
width?: number
height?: number
extname?: string
imgUrl?: string
[propName: string]: any
}
interface PicBedType {
type: string
name: string
visible: boolean
}
// Config Settings
interface ShortKeyConfig {
enable: boolean
key: string // 按键
name: string
label: string
}
interface ShortKeyConfigs {
[propName: string]: ShortKeyConfig
}
interface OldShortKeyConfigs {
upload: string
}
// Main process
interface BrowserWindowOptions {
height: number,
width: number,
show: boolean,
fullscreenable: boolean,
resizable: boolean,
webPreferences: {
nodeIntegration: boolean,
nodeIntegrationInWorker: boolean,
backgroundThrottling: boolean
webSecurity?: boolean
},
vibrancy?: string | any,
frame?: boolean
center?: boolean
title?: string
titleBarStyle?: string | any
backgroundColor?: string
autoHideMenuBar?: boolean
transparent?: boolean
icon?: string
skipTaskbar?: boolean
alwaysOnTop?: boolean
}
interface FileWithPath {
path: string
name?: string
}
interface Bounds {
x: number
y: number
}
declare enum PasteStyle {
MARKDOWN = 'markdown',
HTML = 'HTML',
URL = 'URL',
UBB = 'UBB',
CUSTOM = 'Custom'
}
// global value
declare var __static: string
// third-party
declare module 'fix-path' {
function fixPath(): void
export default fixPath
}
// PicGo Types
declare enum PicGoHelperType {
afterUploadPlugins = 'afterUploadPlugins',
beforeTransformPlugins = 'beforeTransformPlugins',
beforeUploadPlugins = 'beforeUploadPlugins',
uploader = 'uploader',
transformer = 'transformer'
}
interface IPicGoPlugin {
name: string
author: string
description: string
logo: string
version: string | number
gui: boolean
config: {
plugin: IPluginMenuConfig
uploader: IPluginMenuConfig
transformer: IPluginMenuConfig
[index: string]: IPluginMenuConfig
} | {
[propName: string]: any
}
enabled?: boolean
homepage: string
guiMenu?: any[]
ing: boolean
hasInstall?: boolean
}
interface IPluginMenuConfig {
name: string
config: any[]
}
interface INPMSearchResult {
data: {
objects: INPMSearchResultObject[]
}
}
interface INPMSearchResultObject {
package: {
name: string
scope: string
version: string
description: string
keywords: string[]
author: {
name: string
}
links: {
npm: string
homepage: string
}
}
}
// GuiApi
interface IShowInputBoxOption {
title: string
placeholder: string
}

14
src/universal/types/view.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
interface ISettingForm {
updateHelper: boolean
showPicBedList: string[]
autoStart: boolean
rename: boolean
autoRename: boolean
uploadNotification: boolean
miniWindowOntop: boolean
logLevel: string[]
}
interface ShortKeyMap {
[propName: string]: string
}

View File

@ -1,13 +1,13 @@
import db from '../../datastore'
import db from '#/datastore'
const formatCustomLink = (customLink, item) => {
let fileName = item.fileName.replace(new RegExp(`\\${item.extname}$`), '')
const formatCustomLink = (customLink: string, item: ImgInfo) => {
let fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')
let url = item.url || item.imgUrl
let formatObj = {
const formatObj = {
url,
fileName
}
let keys = Object.keys(formatObj)
const keys = Object.keys(formatObj) as ['url', 'fileName']
keys.forEach(item => {
if (customLink.indexOf(`$${item}`) !== -1) {
let reg = new RegExp(`\\$${item}`, 'g')
@ -17,7 +17,7 @@ const formatCustomLink = (customLink, item) => {
return customLink
}
export default (style, item) => {
export default (style: PasteStyle, item: ImgInfo) => {
let url = item.url || item.imgUrl
const customLink = db.get('settings.customLink') || '$url'
const tpl = {

View File

52
tsconfig.json Normal file
View File

@ -0,0 +1,52 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"typeRoots": [
"./src/universal/types/*",
],
"paths": {
"@/*": [
"src/renderer/*"
],
"~/*": [
"src/*"
],
"root/*": [
"./*"
],
"#/*": [
"src/universal/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

61
vue.config.js Normal file
View File

@ -0,0 +1,61 @@
const path = require('path')
function resolve (dir) {
return path.join(__dirname, dir)
}
module.exports = {
chainWebpack: config => {
config.resolve.alias
.set('@', resolve('src/renderer'))
.set('~', resolve('src'))
.set('root', resolve('./'))
.set('#', resolve('src/universal'))
},
pluginOptions: {
electronBuilder: {
chainWebpackMainProcess: config => {
config.resolve.alias
.set('@', resolve('src/renderer'))
.set('~', resolve('src'))
.set('root', resolve('./'))
.set('#', resolve('src/universal'))
},
builderOptions: {
productName: 'PicGo',
appId: 'com.molunerfinn.picgo',
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: {
shortcutName: 'PicGo',
oneClick: false,
allowToChangeInstallationDirectory: true
},
linux: {
icon: 'build/icons/'
}
}
}
}
}

10506
yarn.lock

File diff suppressed because it is too large Load Diff