Compare commits

...
Author SHA1 Message Date
PiEgg 33bfaa5959 🎉 Release: v2.3.1-beta.8 2022-10-30 16:11:43 +08:00
PiEgg 9317871fdb Feature: add remoteNotice 2022-10-30 16:08:29 +08:00
PiEgg a355bc07f4 📝 Docs: add PicHoro 2022-10-25 09:48:54 +08:00
PiEgg ecd462fbd4 Feature: macOS tray icon more clearer 2022-10-24 17:16:09 +08:00
PiEgg 4de7a1d5f2 🐛 Fix: url encode bug && copy-paste url encode bug
ISSUES CLOSED: #996
2022-10-24 17:14:39 +08:00
19 changed files with 310 additions and 15 deletions
+20
View File
@@ -1,3 +1,23 @@
## :tada: 2.3.1-beta.8 (2022-10-30)
### :sparkles: Features
* add remoteNotice ([9317871](https://github.com/Molunerfinn/PicGo/commit/9317871))
* macOS tray icon more clearer ([ecd462f](https://github.com/Molunerfinn/PicGo/commit/ecd462f))
### :bug: Bug Fixes
* url encode bug && copy-paste url encode bug ([4de7a1d](https://github.com/Molunerfinn/PicGo/commit/4de7a1d)), closes [#996](https://github.com/Molunerfinn/PicGo/issues/996)
### :pencil: Documentation
* add PicHoro ([a355bc0](https://github.com/Molunerfinn/PicGo/commit/a355bc0))
## :tada: 2.3.1-beta.7 (2022-10-23)
+3 -2
View File
@@ -48,7 +48,7 @@ PicGo 本体支持如下图床:
- 支持通过发送 HTTP 请求调用 PicGo 上传(v2.2.0+)
- 更多功能等你自己去发现,同时也会不断开发新功能
- 开发进度可以查看 [Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度
- 欢迎加入 [官方讨论区](https://github.com/Molunerfinn/PicGo/discussions) 与我交流
<!-- - 欢迎加入 [官方讨论区](https://github.com/Molunerfinn/PicGo/discussions) 与我交流 -->
**如果第一次使用,请参考应用 [使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html)。遇到问题了还可以看看 [FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md) 以及被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed)。**
@@ -112,7 +112,8 @@ npm run electron:build
## 其他相关
- [vs-picgo](https://github.com/PicGo/vs-picgo)PicGo 的 VS Code 版。
- [flutter-picgo](https://github.com/PicGo/flutter-picgo)PicGo 的手机版(支持 Android 和 iOS )。
- [flutter-picgo](https://github.com/PicGo/flutter-picgo)PicGo 的手机版 App(支持 Android 和 iOS )。
- [PicHoro](https://github.com/Kuingsmile/PicHoro):另一款支持 PicGo 配置的手机版 App(暂时只支持 Android)。
## 赞助
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "picgo",
"version": "2.3.1-beta.7",
"version": "2.3.1-beta.8",
"private": true,
"scripts": {
"dev": "vue-cli-service electron:serve",
@@ -48,7 +48,7 @@
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"picgo": "^1.5.0-alpha.10",
"picgo": "^1.5.0-alpha.15",
"qrcode.vue": "^1.7.0",
"shell-path": "2.1.0",
"uuidv4": "^6.2.11",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 481 B

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+199
View File
@@ -0,0 +1,199 @@
// get notice from remote
// such as some notices for users; some updates for users
import fs from 'fs-extra'
import { app, clipboard, dialog, shell } from 'electron'
import { IRemoteNoticeActionType, IRemoteNoticeTriggerCount, IRemoteNoticeTriggerHook } from '#/types/enum'
import { lte, gte } from 'semver'
import path from 'path'
import axios from 'axios'
import windowManager from '../window/windowManager'
import { showNotification } from '~/main/utils/common'
import { isDev } from '~/universal/utils/common'
// for test
const REMOTE_NOTICE_URL = isDev ? 'http://localhost:8181/remote-notice.json' : 'https://picgo-1251750343.cos.accelerate.myqcloud.com/remote-notice.yml'
const REMOTE_NOTICE_LOCAL_STORAGE_FILE = 'picgo-remote-notice.json'
const STORE_PATH = app.getPath('userData')
const REMOTE_NOTICE_LOCAL_STORAGE_PATH = path.join(STORE_PATH, REMOTE_NOTICE_LOCAL_STORAGE_FILE)
class RemoteNoticeHandler {
private remoteNotice: IRemoteNotice | null = null
private remoteNoticeLocalCountStorage: IRemoteNoticeLocalCountStorage | null = null
async init () {
this.remoteNotice = await this.getRemoteNoticeInfo()
this.initLocalCountStorage()
}
private initLocalCountStorage () {
const localCountStorage = {}
if (!fs.existsSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH)) {
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({}))
}
try {
const localCountStorage: IRemoteNoticeLocalCountStorage = fs.readJSONSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, 'utf8')
this.remoteNoticeLocalCountStorage = localCountStorage
} catch (e) {
console.log(e)
this.remoteNoticeLocalCountStorage = localCountStorage
}
}
private saveLocalCountStorage (newData?: IRemoteNoticeLocalCountStorage) {
if (newData) {
this.remoteNoticeLocalCountStorage = newData
}
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify(this.remoteNoticeLocalCountStorage))
}
private async getRemoteNoticeInfo (): Promise<IRemoteNotice | null> {
try {
const noticeInfo = await axios({
method: 'get',
url: REMOTE_NOTICE_URL,
responseType: 'json'
}).then(res => res.data) as IRemoteNotice
return noticeInfo
} catch {
return null
}
}
/**
* if the notice is not shown or is always shown, then show the notice
* @param action
*/
private checkActionCount (action: IRemoteNoticeAction) {
try {
if (!this.remoteNoticeLocalCountStorage) {
return true
}
const actionCount = this.remoteNoticeLocalCountStorage[action.id]
if (actionCount === undefined) {
if (action.triggerCount === IRemoteNoticeTriggerCount.ALWAYS) {
this.remoteNoticeLocalCountStorage[action.id] = 1 // if always, count number
} else {
this.remoteNoticeLocalCountStorage[action.id] = true
}
return true
} else {
// here is the count of action
// if not always show, then can't show
if (action.triggerCount !== IRemoteNoticeTriggerCount.ALWAYS) {
return false
} else {
const preCount = this.remoteNoticeLocalCountStorage[action.id]
if (typeof preCount !== 'number') {
this.remoteNoticeLocalCountStorage[action.id] = true
return true
} else {
this.remoteNoticeLocalCountStorage[action.id] = preCount + 1
}
return true
}
}
} finally {
this.saveLocalCountStorage()
}
}
private async doActions (actions: IRemoteNoticeAction[]) {
for (const action of actions) {
if (this.checkActionCount(action)) {
switch (action.type) {
case IRemoteNoticeActionType.SHOW_DIALOG: {
// SHOW DIALOG
const currentWindow = windowManager.getAvailableWindow()
dialog.showOpenDialog(currentWindow, action.data?.options)
break
}
case IRemoteNoticeActionType.SHOW_NOTICE:
showNotification({
title: action.data?.title || '',
body: action.data?.content || '',
clickToCopy: !!action.data?.copyToClipboard,
copyContent: action.data?.copyToClipboard || '',
clickFn () {
if (action.data?.url) {
shell.openExternal(action.data.url)
}
}
})
break
case IRemoteNoticeActionType.OPEN_URL:
// OPEN URL
shell.openExternal(action.data?.url || '')
break
case IRemoteNoticeActionType.COMMON:
// DO COMMON CASE
if (action.data?.copyToClipboard) {
clipboard.writeText(action.data.copyToClipboard)
}
if (action.data?.url) {
shell.openExternal(action.data.url)
}
break
case IRemoteNoticeActionType.SHOW_MESSAGE_BOX: {
const currentWindow = windowManager.getAvailableWindow()
dialog.showMessageBox(currentWindow, {
title: action.data?.title || '',
message: action.data?.content || '',
type: 'info',
buttons: action.data?.buttons?.map(item => item.label) || ['Yes']
}).then(res => {
const button = action.data?.buttons?.[res.response]
if (button?.type === 'cancel') {
// do nothing
} else {
if (button?.action) {
this.doActions([button?.action])
}
}
})
break
}
}
}
}
}
triggerHook (hook: IRemoteNoticeTriggerHook) {
if (!this.remoteNotice || !this.remoteNotice.list) {
return
}
const actions = this.remoteNotice.list
.filter(item => {
if (item.versionMatch) {
switch (item.versionMatch) {
case 'exact':
return item.versions.includes(app.getVersion())
case 'gte':
return item.versions.some(version => {
// appVersion >= version
return gte(app.getVersion(), version)
})
case 'lte':
return item.versions.some(version => {
// appVersion <= version
return lte(app.getVersion(), version)
})
}
}
return item.versions.includes(app.getVersion())
})
.map(item => item.actions)
.reduce((pre, cur) => pre.concat(cur), [])
.filter(item => item.hooks.includes(hook))
this.doActions(actions)
}
}
const remoteNoticeHandler = new RemoteNoticeHandler()
export {
remoteNoticeHandler
}
+5 -1
View File
@@ -4,12 +4,13 @@ import {
MINI_WINDOW_URL,
RENAME_WINDOW_URL
} from './constants'
import { IWindowList } from '#/types/enum'
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
import bus from '@core/bus'
import { CREATE_APP_MENU } from '@core/bus/constants'
import db from '~/main/apis/core/datastore'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { app } from 'electron'
import { remoteNoticeHandler } from '../remoteNotice'
// import { i18n } from '~/main/i18n'
// import { URLSearchParams } from 'url'
@@ -88,6 +89,9 @@ windowList.set(IWindowList.SETTING_WINDOW, {
return options
},
callback (window, windowManager) {
window.once('show', () => {
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.SETTING_WINDOW_OPEN)
})
window.loadURL(handleWindowParams(SETTING_WINDOW_URL))
window.on('closed', () => {
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
+4 -1
View File
@@ -12,7 +12,7 @@ import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import beforeOpen from '~/main/utils/beforeOpen'
import ipcList from '~/main/events/ipcList'
import busEventList from '~/main/events/busEventList'
import { IWindowList } from '#/types/enum'
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
import windowManager from 'apis/app/window/windowManager'
import {
updateShortKeyFromVersion212,
@@ -35,6 +35,7 @@ import logger from 'apis/core/picgo/logger'
import picgo from 'apis/core/picgo'
import fixPath from './fixPath'
import { initI18n } from '~/main/utils/handleI18n'
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
const isDevelopment = process.env.NODE_ENV !== 'production'
@@ -101,6 +102,8 @@ class LifeCycle {
notice.show()
}
}
await remoteNoticeHandler.init()
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
}
if (!app.isReady()) {
app.on('ready', readyFunction)
+7 -2
View File
@@ -15,7 +15,9 @@ export const handleCopyUrl = (str: string): void => {
export const showNotification = (options: IPrivateShowNotificationOption = {
title: '',
body: '',
clickToCopy: false
clickToCopy: false,
copyContent: '',
clickFn: () => {}
}) => {
const notification = new Notification({
title: options.title,
@@ -24,7 +26,10 @@ export const showNotification = (options: IPrivateShowNotificationOption = {
})
const handleClick = () => {
if (options.clickToCopy) {
clipboard.writeText(options.body)
clipboard.writeText(options.copyContent || options.body)
}
if (options.clickFn) {
options.clickFn()
}
}
notification.once('click', handleClick)
+2 -1
View File
@@ -1,4 +1,5 @@
import { IPasteStyle } from '#/types/enum'
import { handleUrlEncode } from '#/utils/common'
const formatCustomLink = (customLink: string, item: ImgInfo) => {
const fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')
@@ -18,7 +19,7 @@ const formatCustomLink = (customLink: string, item: ImgInfo) => {
}
export default (style: IPasteStyle, item: ImgInfo, customLink: string | undefined) => {
const url = item.url || item.imgUrl
const url = handleUrlEncode(item.url || item.imgUrl)
const _customLink = customLink || '$url'
const tpl = {
markdown: `![](${url})`,
+19
View File
@@ -27,3 +27,22 @@ export enum IWindowList {
MINI_WINDOW = 'MINI_WINDOW',
RENAME_WINDOW = 'RENAME_WINDOW'
}
export enum IRemoteNoticeActionType {
OPEN_URL = 'OPEN_URL',
SHOW_NOTICE = 'SHOW_NOTICE', // notification
SHOW_DIALOG = 'SHOW_DIALOG', // dialog notice
COMMON = 'COMMON',
VOID = 'VOID', // do nothing
SHOW_MESSAGE_BOX = 'SHOW_MESSAGE_BOX'
}
export enum IRemoteNoticeTriggerHook {
APP_START = 'APP_START',
SETTING_WINDOW_OPEN = 'SETTING_WINDOW_OPEN',
}
export enum IRemoteNoticeTriggerCount {
ONCE = 'ONCE', // default
ALWAYS = 'ALWAYS'
}
+41
View File
@@ -219,6 +219,8 @@ interface IPrivateShowNotificationOption extends IShowNotificationOption{
* click notification to copy the body
*/
clickToCopy?: boolean
copyContent?: string // something to copy
clickFn?: () => void
}
interface IShowMessageBoxOption {
@@ -348,3 +350,42 @@ interface II18nItem {
label: string
value: string
}
interface IRemoteNotice {
version: number
list: Array<{
versions: string[] // matched picgo version
actions: IRemoteNoticeAction[]
versionMatch?: 'exact' | 'gte' | 'lte'
}>
}
interface IRemoteNoticeAction {
type: import('#/types/enum').IRemoteNoticeActionType
// trigger time
hooks: import('#/types/enum').IRemoteNoticeTriggerHook[]
id: string
// trigger count: always or once; default: once
triggerCount: import('#/types/enum').IRemoteNoticeTriggerCount
data?: {
title?: string
content?: string
desc?: string // action desc
buttons?: IRemoteNoticeButton[]
url?: string
copyToClipboard?: string
options: any // for other case
}
}
interface IRemoteNoticeButton {
label: string
labelEN?: string
type: 'confirm' | 'cancel' | 'other'
action: IRemoteNoticeAction
}
interface IRemoteNoticeLocalCountStorage {
[id: string]: true | number
}
+4 -2
View File
@@ -2,7 +2,7 @@ export const isUrl = (url: string): boolean => (url.startsWith('http://') || url
export const isUrlEncode = (url: string): boolean => {
url = url || ''
try {
return url !== decodeURIComponent(url)
return url !== decodeURI(url)
} catch (e) {
// if some error caught, try to let it go
return true
@@ -11,7 +11,7 @@ export const isUrlEncode = (url: string): boolean => {
export const handleUrlEncode = (url: string): string => {
if (!isUrlEncode(url)) {
url = encodeURIComponent(url)
url = encodeURI(url)
}
return url
}
@@ -41,3 +41,5 @@ export const simpleClone = (obj: any) => {
export const enforceNumber = (num: number | string) => {
return isNaN(Number(num)) ? 0 : Number(num)
}
export const isDev = process.env.NODE_ENV === 'development'
+4 -4
View File
@@ -9919,10 +9919,10 @@ performance-now@^2.1.0:
resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
picgo@^1.5.0-alpha.10:
version "1.5.0-alpha.10"
resolved "https://registry.npmjs.org/picgo/-/picgo-1.5.0-alpha.10.tgz#fd567ac60c831395d5f16b80cf59646cab492c9a"
integrity sha512-uFVFXxocbqXyc3lpGGIY2Sn55osXZWrTsNh9/CBSgJSqRVkp13nYnLMffZTp2+zzd/mDTpdqf/AUuEFI9dho+A==
picgo@^1.5.0-alpha.15:
version "1.5.0-alpha.15"
resolved "https://registry.npmjs.org/picgo/-/picgo-1.5.0-alpha.15.tgz#406c1cd48afc5c98f1d5084596e8bcecb637ad58"
integrity sha512-+uU2gbFi/kc5qi0wrqk0hSFusUqEYKTay9hIHcQ13un+7xx5kwir99mwwKpNB+K8wHARntjurN9HCZEszgTKmQ==
dependencies:
"@picgo/i18n" "^1.0.0"
"@picgo/store" "^2.0.2"