mirror of
https://github.com/Molunerfinn/PicGo.git
synced 2026-09-20 11:17:32 +00:00
Compare commits
84
Commits
v2.4.0-beta.1
...
v2.4.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a716ecdbc | ||
|
|
089884b10a | ||
|
|
2ed1dd5abd | ||
|
|
c84d542557 | ||
|
|
dfe92d496b | ||
|
|
3a9ff25071 | ||
|
|
2c9a188dbb | ||
|
|
525492bed1 | ||
|
|
50881b071a | ||
|
|
2936b19008 | ||
|
|
b881161bcd | ||
|
|
a1d22e5742 | ||
|
|
f7f580412f | ||
|
|
25f86a69e1 | ||
|
|
840c33bfa5 | ||
|
|
ee6ca02da7 | ||
|
|
9269f969b8 | ||
|
|
4d04a9b711 | ||
|
|
d071957968 | ||
|
|
d0eb3da45a | ||
|
|
eef736ff57 | ||
|
|
a671ea4b26 | ||
|
|
c658b9bdb1 | ||
|
|
c8c9122e5b | ||
|
|
8c310e7a94 | ||
|
|
f84f5f1d92 | ||
|
|
a0db473178 | ||
|
|
89c24e7f8d | ||
|
|
54d15a6749 | ||
|
|
2cc29833df | ||
|
|
f695e7ccaf | ||
|
|
366ac11ee2 | ||
|
|
9d4ea8277d | ||
|
|
56f61d458e | ||
|
|
8e94b2a4d4 | ||
|
|
db627a450f | ||
|
|
070ce2b666 | ||
|
|
b421c4b42a | ||
|
|
d6c0a85a0f | ||
|
|
ca805f36f8 | ||
|
|
cccd2954db | ||
|
|
cfb6146de5 | ||
|
|
45b3227456 | ||
|
|
2450a524ff | ||
|
|
4a29bf2b50 | ||
|
|
24e4a829d8 | ||
|
|
e0d45fa7a2 | ||
|
|
de441a892e | ||
|
|
f1817eb1e1 | ||
|
|
2bd5d0653b | ||
|
|
9841418779 | ||
|
|
7bc70628f9 | ||
|
|
a928b4c8ab | ||
|
|
7df37a2526 | ||
|
|
dacb926f17 | ||
|
|
1ea074e75f | ||
|
|
04140def7c | ||
|
|
c9fe4023f0 | ||
|
|
c8ba547edb | ||
|
|
316928edc3 | ||
|
|
ff35335126 | ||
|
|
ff7336b99e | ||
|
|
917ec73027 | ||
|
|
8e91582adc | ||
|
|
aaec99f466 | ||
|
|
50e0a64519 | ||
|
|
8cede85732 | ||
|
|
050a3dd602 | ||
|
|
cd07b33e6b | ||
|
|
6d4e1c27d5 | ||
|
|
8fc65f4dcc | ||
|
|
0f7b07dd7b | ||
|
|
dca6667a81 | ||
|
|
1b92f20a7d | ||
|
|
a2320c3503 | ||
|
|
2db0fea6ee | ||
|
|
894d0a243e | ||
|
|
536b70832e | ||
|
|
bc2e92836c | ||
|
|
a5ed1d107e | ||
|
|
e3566b5a8e | ||
|
|
64eb832e06 | ||
|
|
10e0584c96 | ||
|
|
8d9a4000f3 |
@@ -1,4 +0,0 @@
|
||||
test/unit/coverage/**
|
||||
test/unit/*.js
|
||||
test/e2e/*.js
|
||||
dist/
|
||||
@@ -1,37 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
globals: {
|
||||
__static: 'readonly'
|
||||
},
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
parser: 'vue-eslint-parser',
|
||||
extends: [
|
||||
'plugin:vue/vue3-recommended',
|
||||
'@vue/standard',
|
||||
'@vue/typescript'
|
||||
],
|
||||
plugins: ['@typescript-eslint'],
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
indent: 'off',
|
||||
'no-async-promise-executor': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'@typescript-eslint/indent': ['error', 2]
|
||||
},
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.vue'],
|
||||
rules: {
|
||||
'no-undef': 'off' // https://typescript-eslint.io/docs/linting/troubleshooting/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
}
|
||||
}
|
||||
],
|
||||
ignorePatterns: ['src/**/*.d.ts']
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
custom: ["https://paypal.me/Molunerfinn"]
|
||||
github: ["Molunerfinn"]
|
||||
|
||||
+151
-40
@@ -1,61 +1,172 @@
|
||||
# main.yml
|
||||
name: Build & Release
|
||||
|
||||
# Workflow's name
|
||||
name: Build
|
||||
|
||||
# Workflow's trigger
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
test_upload_dist:
|
||||
description: "Test upload-dist.js script"
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
build_os:
|
||||
description: "Build for specific OS: Windows, macOS, Linux, All"
|
||||
required: true
|
||||
default: "All"
|
||||
type: choice
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
- All
|
||||
skip_notarize:
|
||||
description: "Skip Notarization (true/false)"
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
env:
|
||||
NODE_VERSION: 22.x
|
||||
|
||||
# Workflow's jobs
|
||||
jobs:
|
||||
# job's id
|
||||
release:
|
||||
# job's name
|
||||
name: build and release electron app
|
||||
|
||||
# the type of machine to run the job on
|
||||
# parallel build jobs
|
||||
build:
|
||||
name: Build on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# create a build matrix for jobs
|
||||
strategy:
|
||||
# if one job fails, do not stop other jobs
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-11]
|
||||
|
||||
# create steps
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
steps:
|
||||
# step1: check out repository
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# step2: install node env
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
node-version: '16.x'
|
||||
|
||||
- name: Install system deps
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
version: 10
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
- name: Clean workspace on Windows
|
||||
if: runner.os == 'Windows' && (github.event.inputs.build_os == 'Windows' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v'))
|
||||
run: |
|
||||
sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils
|
||||
if (Test-Path dist) { Remove-Item -Recurse -Force dist }
|
||||
if (Test-Path dist_electron) { Remove-Item -Recurse -Force dist_electron }
|
||||
if (Test-Path node_modules) { Remove-Item -Recurse -Force node_modules }
|
||||
|
||||
# step3: yarn
|
||||
- name: Yarn install
|
||||
if (Test-Path "$env:LOCALAPPDATA\electron-builder") {
|
||||
Remove-Item "$env:LOCALAPPDATA\electron-builder" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
if (Test-Path "$env:LOCALAPPDATA\electron") {
|
||||
Remove-Item "$env:LOCALAPPDATA\electron" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
- name: Clean workspace on macOS & Linux
|
||||
if: runner.os == 'macOS' || runner.os == 'Linux' && (github.event.inputs.build_os == 'macOS' || github.event.inputs.build_os == 'Linux' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v'))
|
||||
run: |
|
||||
yarn
|
||||
yarn global add xvfb-maybe
|
||||
|
||||
- name: Build & release app
|
||||
rm -rf dist dist_electron node_modules ~/.cache/electron-builder ~/.cache/electron
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Ubuntu Update with sudo
|
||||
if: runner.os == 'Linux' && (github.event.inputs.build_os == 'Linux' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v'))
|
||||
run: |
|
||||
yarn release
|
||||
yarn upload-dist
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libfuse2
|
||||
- name: Build Windows x64 & ARM64 App
|
||||
if: runner.os == 'Windows' && (github.event.inputs.build_os == 'Windows' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v'))
|
||||
run: pnpm run build:win || true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
PICGO_ENV_COS_SECRET_ID: ${{ secrets.PICGO_ENV_COS_SECRET_ID }}
|
||||
PICGO_ENV_COS_SECRET_KEY: ${{ secrets.PICGO_ENV_COS_SECRET_KEY }}
|
||||
- name: Build macOS x64 & ARM64 App
|
||||
if: runner.os == 'macOS' && (github.event.inputs.build_os == 'macOS' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v'))
|
||||
run: pnpm run build:mac || true
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
# macOS Code Signing
|
||||
# p12 证书的 Base64 字符串
|
||||
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
|
||||
# p12 证书密码
|
||||
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
|
||||
# macOS Notarization (公证所需变量)
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
SKIP_NOTARIZE: ${{ inputs.skip_notarize }}
|
||||
- name: Build Linux x64 & ARM64 App
|
||||
if: runner.os == 'Linux' && (github.event.inputs.build_os == 'Linux' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v'))
|
||||
run: pnpm run build:linux || true
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PicGo-${{ runner.os }}
|
||||
path: dist/*.*
|
||||
- name: Upload to release.picgo.app
|
||||
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.test_upload_dist
|
||||
run: |
|
||||
pnpm run upload-dist
|
||||
env:
|
||||
PICGO_ENV_S3_SECRET_ID: ${{ secrets.PICGO_ENV_S3_SECRET_ID }}
|
||||
PICGO_ENV_S3_SECRET_KEY: ${{ secrets.PICGO_ENV_S3_SECRET_KEY }}
|
||||
PICGO_ENV_S3_ACCOUNT_ID: ${{ secrets.PICGO_ENV_S3_ACCOUNT_ID }}
|
||||
PICGO_ENV_S3_LEGACY_ACCOUNT_ID: ${{ secrets.PICGO_ENV_S3_LEGACY_ACCOUNT_ID }}
|
||||
PICGO_ENV_S3_LEGACY_SECRET_ID: ${{ secrets.PICGO_ENV_S3_LEGACY_SECRET_ID }}
|
||||
PICGO_ENV_S3_LEGACY_SECRET_KEY: ${{ secrets.PICGO_ENV_S3_LEGACY_SECRET_KEY }}
|
||||
release:
|
||||
name: Publish GitHub Release
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Publish GitHub Dev Release
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: softprops/action-gh-release@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
tag_name: dev
|
||||
draft: true
|
||||
prerelease: false
|
||||
files: |
|
||||
!artifacts/**/*-unpacked/**
|
||||
artifacts/**/*.exe
|
||||
artifacts/**/*.dmg
|
||||
artifacts/**/*.zip
|
||||
artifacts/**/*.AppImage
|
||||
artifacts/**/*.deb
|
||||
artifacts/**/*.snap
|
||||
artifacts/**/*.tar.gz
|
||||
artifacts/**/*.yml
|
||||
artifacts/**/*.blockmap
|
||||
- name: Publish GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: softprops/action-gh-release@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
generate_release_notes: true
|
||||
draft: true
|
||||
prerelease: false
|
||||
files: |
|
||||
!artifacts/**/*-unpacked/**
|
||||
artifacts/**/*.exe
|
||||
artifacts/**/*.dmg
|
||||
artifacts/**/*.zip
|
||||
artifacts/**/*.AppImage
|
||||
artifacts/**/*.deb
|
||||
artifacts/**/*.snap
|
||||
artifacts/**/*.tar.gz
|
||||
artifacts/**/*.yml
|
||||
artifacts/**/*.blockmap
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# main.yml
|
||||
|
||||
# Workflow's name
|
||||
name: Build
|
||||
|
||||
# Workflow's trigger
|
||||
on: workflow_dispatch
|
||||
|
||||
# Workflow's jobs
|
||||
jobs:
|
||||
# job's id
|
||||
release:
|
||||
# job's name
|
||||
name: build and release electron app
|
||||
|
||||
# the type of machine to run the job on
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# create a build matrix for jobs
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-11]
|
||||
|
||||
# create steps
|
||||
steps:
|
||||
# step1: check out repository
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# step2: install node env
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16.x'
|
||||
|
||||
- name: Install system deps
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils
|
||||
|
||||
# step3: yarn
|
||||
- name: Yarn install
|
||||
run: |
|
||||
yarn
|
||||
yarn global add xvfb-maybe
|
||||
|
||||
- name: Build & release app
|
||||
run: |
|
||||
yarn release
|
||||
yarn upload-dist
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
PICGO_ENV_COS_SECRET_ID: ${{ secrets.PICGO_ENV_COS_SECRET_ID }}
|
||||
PICGO_ENV_COS_SECRET_KEY: ${{ secrets.PICGO_ENV_COS_SECRET_KEY }}
|
||||
PICGO_ENV_S3_SECRET_ID: ${{ secrets.PICGO_ENV_S3_SECRET_ID }}
|
||||
PICGO_ENV_S3_SECRET_KEY: ${{ secrets.PICGO_ENV_S3_SECRET_KEY }}
|
||||
PICGO_ENV_S3_ACCOUNT_ID: ${{ secrets.PICGO_ENV_S3_ACCOUNT_ID }}
|
||||
+8
-1
@@ -4,6 +4,7 @@ dist/web/*
|
||||
build/*
|
||||
!build/icons
|
||||
!build/installer.nsh
|
||||
!build/entitlements.mac.plist
|
||||
coverage
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
@@ -21,4 +22,10 @@ test.js
|
||||
scripts/*.yml
|
||||
|
||||
#Electron-builder output
|
||||
/dist_electron
|
||||
/dist_electron
|
||||
.serena/
|
||||
dist/*
|
||||
test.js
|
||||
specs/
|
||||
.cache/
|
||||
openspec/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pnpm commitlint ${1}
|
||||
@@ -0,0 +1 @@
|
||||
pnpm check
|
||||
@@ -0,0 +1 @@
|
||||
pnpm test
|
||||
@@ -0,0 +1 @@
|
||||
22
|
||||
Vendored
+11
-3
@@ -11,8 +11,13 @@
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
|
||||
},
|
||||
"preLaunchTask": "electron-debug",
|
||||
"args": ["--remote-debugging-port=9223", "./dist_electron"],
|
||||
"outFiles": ["${workspaceFolder}/dist_electron/**/*.js"]
|
||||
"args": [
|
||||
"--remote-debugging-port=9223",
|
||||
"./dist"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Electron: Renderer",
|
||||
@@ -30,7 +35,10 @@
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Electron: All",
|
||||
"configurations": ["Electron: Main", "Electron: Renderer"]
|
||||
"configurations": [
|
||||
"Electron: Main",
|
||||
"Electron: Renderer"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+8
-2
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"eslint.enable": true,
|
||||
"eslint.alwaysShowStatus": true,
|
||||
"eslint.format.enable": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
@@ -20,6 +21,11 @@
|
||||
"stylusSupremacy.sortProperties": "grouped",
|
||||
"stylusSupremacy.quoteChar": "\"",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"prettier.enable": false,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
PicGo is an Electron + Vue 3 desktop client. Source lives in `src/`: `src/main` for main-process and IPC logic, `src/renderer` for Vue views, and `src/universal` for shared helpers (`types/`, `events/constants.ts`). `background.ts` wires Electron Builder. Static assets and locale YAML files stay in `public/` (add languages under `public/i18n/`), while `docs/` hosts user-facing guides. Automation scripts live in `scripts/`, and legacy tests sit under `test/unit` (Karma) and `test/e2e` (Spectron).
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- `pnpm install` — install dependencies; `npm install` is unsupported. Only run this when the user explicitly asks/coordinates it.
|
||||
- Always add/remove dependencies with `pnpm` (never edit package.json versions by hand then install).
|
||||
- `pnpm dev` — electron-vite dev server for main/preload/renderer.
|
||||
- `pnpm build` — electron-vite build outputs to `dist/main`, `dist/preload`, `dist/renderer`; `pnpm preview` for preview mode.
|
||||
- Packaging config lives in `electron-builder.yml` (read by electron-builder via package.json `build` field/extraResources); set `ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/` if downloads are slow.
|
||||
- `pnpm lint` / `pnpm lint:fix` — run or auto-fix ESLint (Standard, TypeScript, Vue rules).
|
||||
- `pnpm lint:dpdm` — fail fast on circular dependencies in `src/`.
|
||||
- `pnpm check` — run `tsc` + `lint` (run once before finishing a task).
|
||||
- `pnpm gen-i18n` — regenerate typed locales after touching `public/i18n/*.yml`.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
Follow ESLint Standard defaults: two-space indentation, single quotes, trailing commas where allowed, and no stray semicolons. Author new modules in TypeScript. Keep renderer files browser-safe; route Node APIs through IPC helpers such as `src/main/events/picgoCoreIPC.ts`. Name Vue components in PascalCase (`UploadPanel.vue`) and use camelCase for utilities. Centralize IPC event names inside `src/universal/events/constants.ts`, and store enums/types under `src/universal/types/` so they stay reusable. Static assets are served from `public/` and resolved via `getStaticPath`/`getStaticFileUrl` (`src/universal/utils/staticPath.ts`); avoid using `__static` directly.
|
||||
Static assets are served from `public/`. In the main process use `getStaticPath`/`getStaticFileUrl` (`src/universal/utils/staticPath.ts`). In the renderer, place assets under `public/` and resolve them via `import.meta.env.BASE_URL + filename` (helper: `src/renderer/utils/static.ts`); do not rely on `__static` in renderer code.
|
||||
- Do not use `as any` under any circumstances; keep typings explicit and safe.
|
||||
|
||||
## Testing Guidelines
|
||||
Place renderer unit specs in `test/unit/specs` with the `.spec.js` suffix; Karma picks them up via `require.context`. Run them with `npx karma start test/unit/karma.conf.js --single-run` and ensure new renderer folders are covered. Spectron e2e cases live in `test/e2e/specs`; build first (`pnpm build`), then run `npx mocha test/e2e/index.js` so Spectron can launch `dist/electron/main.js`. Document any test data, IPC stubs, or fixtures you add to keep suites reproducible.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
Commits follow the PicGo conventional preset enforced by Husky (`pnpm lint:dpdm` + Commitlint). Stage your changes and run `pnpm cz` to craft messages that pass CI. Pull requests should explain the change, link related issues, and attach UI screenshots or recordings. Note how you validated the work (dev server, build, Karma, Spectron) and call out migration or configuration steps reviewers must perform.
|
||||
|
||||
## Internationalization Tips
|
||||
Add locales by creating `public/i18n/<locale>.yml`, exposing its `LANG_DISPLAY_LABEL`, and registering it in `src/universal/i18n/index.ts`. After editing `public/i18n/*.yml`, run `pnpm gen-i18n` to regenerate TS typings and keep them in sync.
|
||||
|
||||
## Serena MCP & Context7 Tools
|
||||
When starting work or if you hit issues, try checking MCP for Serena or Context7 tooling. If available, use those tools to navigate, edit, or fetch docs efficiently.
|
||||
+251
@@ -1,3 +1,254 @@
|
||||
## :tada: 2.4.3 (2026-01-12)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add global url rewrite support ([#1377](https://github.com/Molunerfinn/PicGo/issues/1377)) ([2ed1dd5](https://github.com/Molunerfinn/PicGo/commit/2ed1dd5)), closes [#1255](https://github.com/Molunerfinn/PicGo/issues/1255) [#1281](https://github.com/Molunerfinn/PicGo/issues/1281)
|
||||
* **upload:** add batch url upload support ([#1376](https://github.com/Molunerfinn/PicGo/issues/1376)) ([c84d542](https://github.com/Molunerfinn/PicGo/commit/c84d542)), closes [#1302](https://github.com/Molunerfinn/PicGo/issues/1302)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* **gallery:** fix picbed list visible status not sync with gallery ([#1373](https://github.com/Molunerfinn/PicGo/issues/1373)) ([dfe92d4](https://github.com/Molunerfinn/PicGo/commit/dfe92d4)), closes [#1372](https://github.com/Molunerfinn/PicGo/issues/1372)
|
||||
* **ui:** some ui bugs ([089884b](https://github.com/Molunerfinn/PicGo/commit/089884b))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.4.2 (2026-01-07)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* **notification:** refactor notification and add notificationSound settings ([#1370](https://github.com/Molunerfinn/PicGo/issues/1370)) ([2936b19](https://github.com/Molunerfinn/PicGo/commit/2936b19)), closes [#1229](https://github.com/Molunerfinn/PicGo/issues/1229)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* **tray:** clamp image titles to two lines ([525492b](https://github.com/Molunerfinn/PicGo/commit/525492b))
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* **2.4.2:** update changelog ([2c9a188](https://github.com/Molunerfinn/PicGo/commit/2c9a188))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.4.2-beta.0 (2025-12-31)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* **config:** add copy config \&\& add double confirm before copy \& delete config ([25f86a6](https://github.com/Molunerfinn/PicGo/commit/25f86a6))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* **icon:** app icon too large in macOS 15.x ([840c33b](https://github.com/Molunerfinn/PicGo/commit/840c33b)), closes [#1367](https://github.com/Molunerfinn/PicGo/issues/1367)
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* electron builder not publish with build command ([9269f96](https://github.com/Molunerfinn/PicGo/commit/9269f96))
|
||||
* fix workflow build ([a1d22e5](https://github.com/Molunerfinn/PicGo/commit/a1d22e5))
|
||||
* **signature:** add signature \& notarization process ([ee6ca02](https://github.com/Molunerfinn/PicGo/commit/ee6ca02))
|
||||
* update FAQ \&\& plugin filter logic ([f7f5804](https://github.com/Molunerfinn/PicGo/commit/f7f5804))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.4.1 (2025-12-23)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add showMenubarIcon setting ([#1366](https://github.com/Molunerfinn/PicGo/issues/1366)) ([d0eb3da](https://github.com/Molunerfinn/PicGo/commit/d0eb3da))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* **custom:** build workflow error ([a0db473](https://github.com/Molunerfinn/PicGo/commit/a0db473))
|
||||
* **custom:** data report ([eef736f](https://github.com/Molunerfinn/PicGo/commit/eef736f))
|
||||
* **custom:** workflow env bug ([f84f5f1](https://github.com/Molunerfinn/PicGo/commit/f84f5f1))
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* **custom:** update readme ([a671ea4](https://github.com/Molunerfinn/PicGo/commit/a671ea4))
|
||||
* **custom:** update readme ([c658b9b](https://github.com/Molunerfinn/PicGo/commit/c658b9b))
|
||||
* **custom:** update README ([c8c9122](https://github.com/Molunerfinn/PicGo/commit/c8c9122))
|
||||
* update 2.4.1 changelog ([d071957](https://github.com/Molunerfinn/PicGo/commit/d071957))
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* **custom:** rm yarn.lock ([8c310e7](https://github.com/Molunerfinn/PicGo/commit/8c310e7))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.4.1-beta.1 (2025-12-10)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* **custom:** the issue that x64 macOS app can't be opened ([54d15a6](https://github.com/Molunerfinn/PicGo/commit/54d15a6)), closes [#1363](https://github.com/Molunerfinn/PicGo/issues/1363)
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* update builder config && add legacy version file upload process ([2cc2983](https://github.com/Molunerfinn/PicGo/commit/2cc2983))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.4.1-beta.0 (2025-12-09)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* fix docs link ([d6c0a85](https://github.com/Molunerfinn/PicGo/commit/d6c0a85))
|
||||
* pic-migrater post handler error ([b421c4b](https://github.com/Molunerfinn/PicGo/commit/b421c4b))
|
||||
* tray window clipboard image not show bug ([#1362](https://github.com/Molunerfinn/PicGo/issues/1362)) ([56f61d4](https://github.com/Molunerfinn/PicGo/commit/56f61d4))
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* change release url ([9d4ea82](https://github.com/Molunerfinn/PicGo/commit/9d4ea82))
|
||||
* disabled build universal installer ([366ac11](https://github.com/Molunerfinn/PicGo/commit/366ac11))
|
||||
* fix some workflow bug ([db627a4](https://github.com/Molunerfinn/PicGo/commit/db627a4))
|
||||
* fix upload dist arch bug ([8e94b2a](https://github.com/Molunerfinn/PicGo/commit/8e94b2a))
|
||||
* upgrade electron version && migrate to electron-vite ([#1361](https://github.com/Molunerfinn/PicGo/issues/1361)) ([070ce2b](https://github.com/Molunerfinn/PicGo/commit/070ce2b))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0 (2025-11-23)
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* add 2.4.0 changelog ([cccd295](https://github.com/Molunerfinn/PicGo/commit/cccd295))
|
||||
* add warp sponsor shoutout ([e0d45fa](https://github.com/Molunerfinn/PicGo/commit/e0d45fa))
|
||||
* add warp sponsor shoutout ([de441a8](https://github.com/Molunerfinn/PicGo/commit/de441a8))
|
||||
* update docs ([4a29bf2](https://github.com/Molunerfinn/PicGo/commit/4a29bf2))
|
||||
* update docs ([24e4a82](https://github.com/Molunerfinn/PicGo/commit/24e4a82))
|
||||
* update readme & warp sponsor link ([45b3227](https://github.com/Molunerfinn/PicGo/commit/45b3227))
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* change funding yml ([2450a52](https://github.com/Molunerfinn/PicGo/commit/2450a52))
|
||||
* update picgo core to 1.5.11 to solve url encode bug ([cfb6146](https://github.com/Molunerfinn/PicGo/commit/cfb6146))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.10 (2025-06-08)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* finish form uploader for picgo server ([9841418](https://github.com/Molunerfinn/PicGo/commit/9841418))
|
||||
* **server:** add support for form upload in PicGo Server ([#1327](https://github.com/Molunerfinn/PicGo/issues/1327)) ([a928b4c](https://github.com/Molunerfinn/PicGo/commit/a928b4c)), closes [#428](https://github.com/Molunerfinn/PicGo/issues/428)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* auto-copy url can't be turned off ([#1300](https://github.com/Molunerfinn/PicGo/issues/1300)) ([dacb926](https://github.com/Molunerfinn/PicGo/commit/dacb926))
|
||||
* encoded url filename unreadable ([2bd5d06](https://github.com/Molunerfinn/PicGo/commit/2bd5d06))
|
||||
* unable to use clip in wayland ([#1301](https://github.com/Molunerfinn/PicGo/issues/1301)) ([7df37a2](https://github.com/Molunerfinn/PicGo/commit/7df37a2))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.9 (2024-12-02)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* clipboard filename missing second ([c9fe402](https://github.com/Molunerfinn/PicGo/commit/c9fe402)), closes [#1293](https://github.com/Molunerfinn/PicGo/issues/1293)
|
||||
* copy text bug ([c8ba547](https://github.com/Molunerfinn/PicGo/commit/c8ba547)), closes [#1210](https://github.com/Molunerfinn/PicGo/issues/1210) [#1280](https://github.com/Molunerfinn/PicGo/issues/1280)
|
||||
* plugin list search bug ([04140de](https://github.com/Molunerfinn/PicGo/commit/04140de)), closes [#1297](https://github.com/Molunerfinn/PicGo/issues/1297)
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* update ci macos version ([316928e](https://github.com/Molunerfinn/PicGo/commit/316928e))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.8 (2024-07-16)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* tencent cos url encode bug ([ff7336b](https://github.com/Molunerfinn/PicGo/commit/ff7336b)), closes [#1265](https://github.com/Molunerfinn/PicGo/issues/1265)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.7 (2024-04-22)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add startup mode ([aaec99f](https://github.com/Molunerfinn/PicGo/commit/aaec99f)), closes [#915](https://github.com/Molunerfinn/PicGo/issues/915)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* config page scroll bug ([8e91582](https://github.com/Molunerfinn/PicGo/commit/8e91582)), closes [#1237](https://github.com/Molunerfinn/PicGo/issues/1237)
|
||||
* tray menu open bug ([50e0a64](https://github.com/Molunerfinn/PicGo/commit/50e0a64)), closes [#1217](https://github.com/Molunerfinn/PicGo/issues/1217)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.6 (2023-11-19)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* app.asar directroy copy error ([#1180](https://github.com/Molunerfinn/PicGo/issues/1180)) ([cd07b33](https://github.com/Molunerfinn/PicGo/commit/cd07b33)), closes [#1179](https://github.com/Molunerfinn/PicGo/issues/1179)
|
||||
* can't add new config for picbed ([050a3dd](https://github.com/Molunerfinn/PicGo/commit/050a3dd)), closes [#1184](https://github.com/Molunerfinn/PicGo/issues/1184)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.5 (2023-09-10)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add gallery toolbox menu ([#1177](https://github.com/Molunerfinn/PicGo/issues/1177)) ([0f7b07d](https://github.com/Molunerfinn/PicGo/commit/0f7b07d))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.4 (2023-08-26)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add configName for upload page ([894d0a2](https://github.com/Molunerfinn/PicGo/commit/894d0a2))
|
||||
* support "tips" option for uploader ([1b92f20](https://github.com/Molunerfinn/PicGo/commit/1b92f20))
|
||||
* **tcyun:** add slim section ([#1165](https://github.com/Molunerfinn/PicGo/issues/1165)) ([a2320c3](https://github.com/Molunerfinn/PicGo/commit/a2320c3))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* open config file bug ([2db0fea](https://github.com/Molunerfinn/PicGo/commit/2db0fea)), closes [#1163](https://github.com/Molunerfinn/PicGo/issues/1163)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.3 (2023-07-09)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* rename page bug ([bc2e928](https://github.com/Molunerfinn/PicGo/commit/bc2e928)), closes [#1130](https://github.com/Molunerfinn/PicGo/issues/1130)
|
||||
* tailwind css bug ([e3566b5](https://github.com/Molunerfinn/PicGo/commit/e3566b5))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.2 (2023-07-09)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* fileName encode bug ([8d9a400](https://github.com/Molunerfinn/PicGo/commit/8d9a400)), closes [#1121](https://github.com/Molunerfinn/PicGo/issues/1121)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.1 (2023-05-03)
|
||||
|
||||
|
||||
|
||||
@@ -1,28 +1,55 @@
|
||||
## 常见问题
|
||||
## Frequently Asked Questions / 常见问题
|
||||
|
||||
> 在使用 PicGo 期间你会遇到很多问题,不过很多问题其实之前就有人提问过,也被解决,所以你可以先看看 [使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B),这份 FAQ,以及那些被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed),应该能找到答案。
|
||||
> While using PicGo you may run into various issues. Many of them have already been asked and resolved, so please check the [documentation](https://picgo.github.io/PicGo-Doc/guide/getting-started.html#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B), this FAQ, and closed [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed) first — you will likely find the answer there.
|
||||
>
|
||||
> 在使用 PicGo 期间你会遇到很多问题,不过很多问题其实之前就有人提问过,也被解决,所以你可以先看看 [使用文档](https://picgo.github.io/PicGo-Doc/guide/getting-started.html#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B),这份 FAQ,以及那些被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed),应该能找到答案。
|
||||
|
||||
## 1. 七牛图床上传图片成功后,相册里无法显示或图片无`http://`前缀
|
||||
## 1. Qiniu image host: upload succeeds but images don’t show in Album, or the URL has no `http://` prefix / 七牛图床上传图片成功后,相册里无法显示或图片无`http://`前缀
|
||||
|
||||
This is usually because the `Set URL` (access URL) in your Qiniu image host configuration does not include the `http://` or `https://` scheme.
|
||||
|
||||
Reference: [issue#79](https://github.com/Molunerfinn/PicGo/issues/79)
|
||||
|
||||
通常是你的七牛图床配置里的`设定访问网址`没有加上`http://`或者`https//`头。
|
||||
|
||||
参考:[issue#79](https://github.com/Molunerfinn/PicGo/issues/79)
|
||||
|
||||
## 2. 能否支持图床远端同步删除
|
||||
## 2. Can PicGo delete images on the remote image host after upload? / 能否支持图床远端同步删除
|
||||
|
||||
不能。有些图床(比如微博图床、SM.MS、Imgur 等)不支持后台管理,为了架构统一不支持远端删除。
|
||||
Not at the moment. Some image hosts (e.g. Weibo image host, SM.MS, Imgur, etc.) don’t provide a backend management API, so PicGo does not support remote deletion for the sake of a consistent architecture.
|
||||
|
||||
## 3. 能否支持上传视频文件
|
||||
暂时不支持。有些图床(比如微博图床、SM.MS、Imgur 等)不支持后台管理,为了架构统一不支持远端删除。
|
||||
|
||||
目前不能。如果有人开发了相应的插件理论可以支持任意文件上传。
|
||||
## 3. Can PicGo upload video files? / 能否支持上传视频文件
|
||||
|
||||
## 4. 微博图床上传之后无法显示预览图
|
||||
Some image hosts support uploading video files, but not all. Please follow the capabilities of the image host (and/or the plugin) you are actually using.
|
||||
|
||||
目前部分图床支持上传视频文件,但并非所有图床都支持,请以实际使用的图床以及插件为准。
|
||||
|
||||
## 4. Weibo image host: uploaded images don’t preview / 微博图床上传之后无法显示预览图
|
||||
|
||||
This is usually caused by having a global proxy enabled.
|
||||
|
||||
Reference: [issue36](https://github.com/Molunerfinn/PicGo/issues/36)
|
||||
|
||||
通常是挂了全局代理导致的。
|
||||
|
||||
参考:[issue36](https://github.com/Molunerfinn/PicGo/issues/36)
|
||||
|
||||
## 5. 能否支持某某某图床
|
||||
## 5. Can you add support for an image host? / 能否支持某某某图床
|
||||
|
||||
As of v1.6, PicGo supports the following built-in image hosts:
|
||||
|
||||
- `Weibo image host` v1.0
|
||||
- `Qiniu image host` v1.0
|
||||
- `Tencent Cloud COS v4/v5` v1.1 & v1.5.0
|
||||
- `Upyun` v1.2.0
|
||||
- `GitHub` v1.5.0
|
||||
- `SM.MS` v1.5.1
|
||||
- `Alibaba Cloud OSS` v1.6.0
|
||||
- `Imgur` v1.6.0
|
||||
|
||||
PicGo itself will not add support for additional third-party image hosts as built-in features. If you need other image hosts, please refer to existing third-party [plugins](https://github.com/PicGo/Awesome-PicGo). If the one you need doesn’t exist yet, you’re welcome to develop a plugin and share it with the community.
|
||||
|
||||
截止 v1.6,PicGo 支持了如下图床:
|
||||
|
||||
@@ -35,44 +62,141 @@
|
||||
- `阿里云 OSS` v1.6.0
|
||||
- `Imgur` v1.6.0
|
||||
|
||||
所以本体内将不会再支持其他图床。需要其他图床支持可以参考目前已有的三方 [插件](https://github.com/PicGo/Awesome-PicGo),如果还是没有你所需要的图床欢迎开发一个插件供大家使用。
|
||||
所以本体内将不会再支持其他第三方图床。需要其他图床支持可以参考目前已有的三方 [插件](https://github.com/PicGo/Awesome-PicGo),如果还是没有你所需要的图床欢迎开发一个插件供大家使用。
|
||||
|
||||
## 6. 一个图床设置多个信息
|
||||
## 6. GitHub image host uploads sometimes succeed and sometimes fail / GitHub 图床有时能上传,有时上传失败
|
||||
|
||||
不能。因为目前的架构只支持一个图床一份信息。
|
||||
|
||||
## 7. GitHub 图床有时能上传,有时上传失败
|
||||
1. The GitHub image host does not allow uploading files with the same name. If you upload a duplicate filename, you will get an error. Enable `Timestamp Rename` to avoid name collisions.
|
||||
2. Due to GitHub network conditions (and the Great Firewall in mainland China), uploads may sometimes succeed and sometimes fail — there is no universal fix. For stability, consider using a paid cloud storage service such as Alibaba Cloud or Tencent Cloud; they are usually inexpensive.
|
||||
|
||||
1. GitHub 图床不支持上传同名文件,如果有同名文件上传,会报错。建议开启 `时间戳重命名` 避免同名文件。
|
||||
2. GitHub 服务器和国内 GFW 的问题会导致有时上传成功,有时上传失败,无解。想要稳定请使用付费云存储,如阿里云、腾讯云等,价格也不会贵。
|
||||
|
||||
## 8. Mac 上无法打开 PicGo 的主窗口界面
|
||||
## 7. Can’t open PicGo’s main window on macOS / Mac 上无法打开 PicGo 的主窗口界面
|
||||
|
||||
On macOS, PicGo is a menu bar app, so it won’t show an icon in the Dock by default. To open the main window, right-click (or two-finger click) the PicGo menu bar icon and choose “Open Main Window”.
|
||||
|
||||
Starting from v2.4.1, PicGo lets you hide the Dock icon (`showDockIcon`) and the menu bar icon (`showMenubarIcon`) separately. If you turn both off (set both to `false`), you won’t be able to find the UI via either the Dock or the menu bar.
|
||||
|
||||
How to recover manually:
|
||||
|
||||
1. Locate and edit PicGo’s config file `data.json`.
|
||||
- If you can still open the settings page: PicGo Settings -> “Open Config File”.
|
||||
- If you can’t find the UI: the default location is usually `~/Library/Application Support/PicGo/data.json` (if you configured a custom path, follow `configPath`).
|
||||
2. Set either field below to `true` (it’s recommended to keep at least one of them `true`). Do not modify other fields.
|
||||
3. Save and restart PicGo.
|
||||
|
||||
PicGo 在 Mac 上是一个顶部栏应用,在 dock 栏是不会有图标的。要打开主窗口,请右键或者双指点按顶部栏 PicGo 图标,选择「打开详细窗口」即可打开主窗口。
|
||||
|
||||
## 9. 上传失败,或者是服务器出错
|
||||
从 v2.4.1 开始,PicGo 支持在 macOS 下分别隐藏 Dock 栏图标(`showDockIcon`)和顶部栏图标(`showMenubarIcon`)。如果你把这两个配置都关闭(都设为 `false`),将会导致你无法通过 Dock 或顶部栏找到 PicGo 主界面。
|
||||
|
||||
1. PicGo 自带的图床都经过测试,上传出错一般都不是 PicGo 自身的原因。如果你用的是 GitHub 图床请参考上面的第 7 点。
|
||||
手动恢复方法:
|
||||
|
||||
1. 找到并编辑 PicGo 的配置文件 `data.json`。
|
||||
- 如果还能打开设置页:PicGo 设置 -> 「打开配置文件」。
|
||||
- 如果已经找不到界面:默认配置文件通常在 `~/Library/Application Support/PicGo/data.json`(如果你曾配置过自定义路径,则以配置里的 `configPath` 为准)。
|
||||
2. 把以下任意一个字段改为 `true`(建议至少保留一个为 `true`),同时不要删改其他字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"settings": {
|
||||
// other settings ...
|
||||
"showDockIcon": true,
|
||||
"showMenubarIcon": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. 保存后重启 PicGo。
|
||||
|
||||
## 8. Upload failed, or server returned an error / 上传失败,或者是服务器出错
|
||||
|
||||
1. PicGo’s built-in image hosts are tested; upload errors are usually not caused by PicGo itself. If you are using the GitHub image host, see FAQ #6.
|
||||
2. Check PicGo logs (PicGo Settings -> Log File -> Open) and look for key information in `[PicGo Error]`.
|
||||
1. Search the error message first — you can often find the root cause via search engines without opening an issue.
|
||||
2. If you see `401`, `403`, or other `40X` status codes, it almost certainly means your configuration is wrong. Double-check for typos, trailing spaces, etc.
|
||||
3. If you see `HttpError`, `RequestError`, `socket hang up`, etc., that indicates a network issue. Please check your network, proxy, and DNS settings.
|
||||
3. Upload failures caused by network issues are often due to incorrect proxy settings. If you enabled a system proxy, it’s recommended to also configure the corresponding HTTP proxy in PicGo. See [#912](https://github.com/Molunerfinn/PicGo/issues/912)
|
||||
|
||||
1. PicGo 自带的图床都经过测试,上传出错一般都不是 PicGo 自身的原因。如果你用的是 GitHub 图床请参考上面的第 6 点。
|
||||
2. 检查 PicGo 的日志(报错日志可以在 PicGo 设置 -> 设置日志文件 -> 点击打开 后找到),看看 `[PicGo Error]` 的报错信息里有什么关键信息
|
||||
1. 先自行搜索 error 里的报错信息,往往你能百度或者谷歌出问题原因,不必开 issue。
|
||||
2. 如果有带有 `401` 、`403` 等 `40X` 状态码字样的,不用怀疑,就是你配置写错了,仔细检查配置,看看是否多了空格之类的。
|
||||
3. 如果带有 `HttpError`、`RequestError` 、 `socket hang up` 等字样的说明这是网络问题,我无法帮你解决网络问题,请检查你自己的网络,是否有代理,DNS 设置是否正常等。
|
||||
3. 通常网络问题引起的上传失败都是因为代理设置不当导致的。如果开启了系统代理,建议同时也在 PicGo 的代理设置中设置对应的HTTP代理。参考 [#912](https://github.com/Molunerfinn/PicGo/issues/912)
|
||||
|
||||
## 10. macOS版本安装完之后没有主界面
|
||||
## 9. Installed on macOS but there is no main UI window / macOS版本安装完之后没有主界面
|
||||
|
||||
Find the PicGo icon in the macOS menu bar, then right-click (two-finger click on trackpad) to open the menu and choose “Open Main Window”.
|
||||
|
||||
请找到PicGo在顶部栏的图标,然后右键(触摸板双指点按,或者鼠标右键),即可找到「打开详细窗口」的菜单。
|
||||
|
||||
## 11. 相册突然无法显示图片 或者 上传后相册不更新 或者 使用Typora+PicGo上传图片成功但是没有写回Typora
|
||||
## 10. Album suddenly can’t show images, or doesn’t refresh after upload, or Typora + PicGo upload succeeds but doesn’t write back / 相册突然无法显示图片 或者 上传后相册不更新 或者 使用Typora+PicGo上传图片成功但是没有写回Typora
|
||||
|
||||
This may be caused by a corrupted album database. Locate `picgo.db` under your PicGo config directory, delete it (backup first if needed), then restart PicGo.
|
||||
|
||||
Also check the log file for errors and open an issue if necessary. Versions >= 2.3.0 have addressed issues caused by a corrupted `picgo.db`, so upgrading is recommended.
|
||||
|
||||
这个原因可能是相册存储文件损坏导致的。可以找到 PicGo 配置文件所在路径下的 `picgo.db` ,将其删掉(删掉前建议备份一遍),再重启 PicGo 试试。
|
||||
注意同时看看日志文件里有没有什么error,必要时可以提issue。2.3.0以上的版本已经解决因为 `picgo.db` 损坏导致的上述问题,建议更新版本。
|
||||
|
||||
## 12. Gitee相关问题
|
||||
## 11. Gitee-related issues / Gitee相关问题
|
||||
|
||||
If you run into upload issues with the Gitee image host, PicGo cannot help because PicGo does not provide an official Gitee uploader. Please open an issue in the repository of the Gitee plugin you are using.
|
||||
|
||||
如果在使用 Gitee 图床的时候遇到上传的问题,由于 PicGo 并没有官方提供 Gitee 上传服务,无法帮你解决,请去你所使用的 Gitee 插件仓库发相关的issue。
|
||||
|
||||
## 13. macOS系统安装完PicGo显示「文件已损坏」或者安装完打开没有反应
|
||||
## 12. On macOS, PicGo shows “App is damaged”, or it doesn’t respond after installation / macOS系统安装完PicGo显示「文件已损坏」或者安装完打开没有反应
|
||||
|
||||
Because PicGo is not signed, it may be blocked by macOS Gatekeeper.
|
||||
|
||||
1. If you see “App is damaged” when opening after installation, do the following:
|
||||
|
||||
Trust the developer (password required):
|
||||
|
||||
```
|
||||
sudo spctl --master-disable
|
||||
```
|
||||
|
||||
Then remove quarantine attributes from PicGo:
|
||||
|
||||
```
|
||||
xattr -cr /Applications/PicGo.app
|
||||
```
|
||||
|
||||
If you see the following message:
|
||||
|
||||
```sh
|
||||
option -r not recognized
|
||||
|
||||
usage: xattr [-slz] file [file ...]
|
||||
xattr -p [-slz] attr_name file [file ...]
|
||||
xattr -w [-sz] attr_name attr_value file [file ...]
|
||||
xattr -d [-s] attr_name file [file ...]
|
||||
xattr -c [-s] file [file ...]
|
||||
|
||||
The first form lists the names of all xattrs on the given file(s).
|
||||
The second form (-p) prints the value of the xattr attr_name.
|
||||
The third form (-w) sets the value of the xattr attr_name to attr_value.
|
||||
The fourth form (-d) deletes the xattr attr_name.
|
||||
The fifth form (-c) deletes (clears) all xattrs.
|
||||
|
||||
options:
|
||||
-h: print this help
|
||||
-s: act on symbolic links themselves rather than their targets
|
||||
-l: print long format (attr_name: attr_value)
|
||||
-z: compress or decompress (if compressed) attribute value in zip format
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```
|
||||
sudo xattr -d com.apple.quarantine /Applications/PicGo.app/
|
||||
```
|
||||
|
||||
2. If PicGo doesn’t respond after installation, troubleshoot in this order:
|
||||
1. PicGo won’t automatically pop up a main window on macOS — it’s designed as a menu bar app. If you can see the PicGo icon in the menu bar, the installation succeeded; click it to open the menu bar window. See FAQ #7.
|
||||
2. If you’re on an Apple Silicon (M1) Mac and previously had the x64 build installed, then switched to the arm64 build and it doesn’t respond, reboot your Mac.
|
||||
|
||||
因为 PicGo 没有签名,所以会被 macOS 的安全检查所拦下。
|
||||
|
||||
@@ -117,16 +241,20 @@ options:
|
||||
```
|
||||
执行命令
|
||||
|
||||
```
|
||||
xattr -c /Applications/PicGo.app/*
|
||||
```
|
||||
|
||||
如果上述命令依然没有效果,可以尝试下面的命令:
|
||||
|
||||
```
|
||||
sudo xattr -d com.apple.quarantine /Applications/PicGo.app/
|
||||
```
|
||||
|
||||
2. 如果安装打开后没有反应,请按下方顺序排查:
|
||||
1. macOS安装好之后,PicGo 是不会弹出主窗口的,因为 PicGo 在 macOS 系统里设计是个顶部栏应用。注意看你顶部栏的图标,如果有 PicGo 的图标,说明安装成功了,点击图标即可打开顶部栏窗口。参考上述[第八点](#8-mac-上无法打开-picgo-的主窗口界面)。
|
||||
1. macOS安装好之后,PicGo 是不会弹出主窗口的,因为 PicGo 在 macOS 系统里设计是个顶部栏应用。注意看你顶部栏的图标,如果有 PicGo 的图标,说明安装成功了,点击图标即可打开顶部栏窗口。参考上述第七点。
|
||||
2. 如果你是 M1 的系统,此前装过 PicGo 的 x64 版本,但是后来更新了 arm64 的版本发现打开后没反应,请重启电脑即可。
|
||||
|
||||
## 13. Are third-party plugins claiming to be “PicGo Official image host” trustworthy? / 所谓「PicGo 官方图床」的第三方插件是否可信
|
||||
|
||||
No. Any third-party plugin that claims to be a “PicGo Official image host” (including, but not limited to, www.picgo.net) is not an official PicGo image host or service. Please do not trust such claims.
|
||||
|
||||
An official PicGo image host (if any) would be built into PicGo out of the box — it would not require you to download and install a “third-party plugin”, and it would not direct you to an unknown website to purchase or configure a so-called “official image host”. If you choose to use third-party image hosts, please prefer community plugins from reputable sources and assess their safety yourself.
|
||||
|
||||
不可信。所有打着「PicGo 官方图床」旗号的第三方插件(包括不限于 www.picgo.net 等)都不是 PicGo 官方提供的图床或服务,请勿轻信。
|
||||
|
||||
PicGo 不会以“第三方插件”的形式要求你另外下载安装所谓的 PicGo 官方图床。如果 PicGo 真的做了官方图床,一定是开箱即用的内置在本体里的。如果你需要使用第三方图床,请优先参考 PicGo 官方维护的插件集合与社区仓库,并自行甄别来源与安全性。
|
||||
|
||||
@@ -1,132 +1,165 @@
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/New%20LOGO-150.png" alt="">
|
||||
<h1>PicGo</h1>
|
||||
<blockquote>图片上传+管理新体验 </blockquote>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/actions">
|
||||
<img src="https://img.shields.io/badge/code%20style-standard-green.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/actions">
|
||||
<img src="https://github.com/Molunerfinn/PicGo/actions/workflows/main.yml/badge.svg" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/releases">
|
||||
<img src="https://img.shields.io/github/downloads/Molunerfinn/PicGo/total.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/releases/latest">
|
||||
<img src="https://img.shields.io/github/release/Molunerfinn/PicGo.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/PicGo/bump-version">
|
||||
<img src="https://img.shields.io/badge/picgo-convention-blue.svg?style=flat-square" alt="">
|
||||
<div align="center" markdown="1">
|
||||
<sup>Special thanks to:</sup>
|
||||
<br>
|
||||
<a href="https://go.warp.dev/picgo">
|
||||
<img alt="Warp sponsorship" width="400" src="https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Github/Sponsor/Warp-Github-LG-03.png">
|
||||
</a>
|
||||
|
||||
### [Warp, the intelligent terminal for developers](https://go.warp.dev/picgo)
|
||||
[Available for macOS, Linux, & Windows](https://go.warp.dev/picgo)<br>
|
||||
|
||||
</div>
|
||||
|
||||
## 应用概述
|
||||
---
|
||||
|
||||
**PicGo: 一个用于快速上传图片并获取图片 URL 链接的工具**
|
||||
[中文](./README_zh-CN.md) | **English**
|
||||
|
||||
PicGo 本体支持如下图床:
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/New%20LOGO-150.png" alt="PicGo Logo">
|
||||
<h1>PicGo</h1>
|
||||
<h3>The Ultimate Image Uploader for Efficient Creators</h3>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Molunerfinn/PicGo/actions">
|
||||
<img src="https://img.shields.io/badge/code%20style-standard-green.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/actions">
|
||||
<img src="https://github.com/Molunerfinn/PicGo/actions/workflows/main.yml/badge.svg" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/releases">
|
||||
<img src="https://img.shields.io/github/downloads/Molunerfinn/PicGo/total.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/releases/latest">
|
||||
<img src="https://img.shields.io/github/release/Molunerfinn/PicGo.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/PicGo/bump-version">
|
||||
<img src="https://img.shields.io/badge/picgo-convention-blue.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
- `七牛图床` v1.0
|
||||
- `腾讯云 COS v4\v5 版本` v1.1 & v1.5.0
|
||||
- `又拍云` v1.2.0
|
||||
- `GitHub` v1.5.0
|
||||
- `SM.MS V2` v2.3.0-beta.0
|
||||
- `阿里云 OSS` v1.6.0
|
||||
- `Imgur` v1.6.0
|
||||
## 📖 Overview
|
||||
|
||||
**本体不再增加默认的图床支持。你可以自行开发第三方图床插件。详见 [PicGo-Core](https://picgo.github.io/PicGo-Core-Doc/)**。
|
||||
**PicGo aims to make image uploading a seamless part of your creative workflow.**
|
||||
|
||||
## 特色功能
|
||||
Whether you’re writing a blog post, taking notes, or authoring developer docs, PicGo helps you upload images in one step and automatically copies the resulting link—so you can stay focused on creating, not uploading.
|
||||
|
||||
- 支持拖拽图片上传
|
||||
- 支持快捷键上传剪贴板里第一张图片
|
||||
- Windows 和 macOS 支持右键图片文件通过菜单上传 (v2.1.0+)
|
||||
- 上传图片后自动复制链接到剪贴板
|
||||
- 支持自定义复制到剪贴板的链接格式
|
||||
- 支持修改快捷键,默认快速上传快捷键:`command+shift+p`(macOS)| `control+shift+p`(Windows\Linux)
|
||||
- 支持插件系统,已有插件支持 Gitee、青云等第三方图床
|
||||
- 更多第三方插件以及使用了 PicGo 底层的应用可以在 [Awesome-PicGo](https://github.com/PicGo/Awesome-PicGo) 找到。欢迎贡献!
|
||||
- 支持通过发送 HTTP 请求调用 PicGo 上传(v2.2.0+)
|
||||
- 更多功能等你自己去发现,同时也会不断开发新功能
|
||||
- 开发进度可以查看 [Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度
|
||||
<!-- - 欢迎加入 [官方讨论区](https://github.com/Molunerfinn/PicGo/discussions) 与我交流 -->
|
||||
### Supported Image hosts
|
||||
|
||||
**如果第一次使用,请参考应用 [使用文档](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)。**
|
||||
PicGo supports mainstream Image hosts out of the box, and can be extended indefinitely through its plugin system:
|
||||
|
||||
## 下载安装
|
||||
- **China cloud vendors**: Qiniu, Tencent Cloud COS, UPYUN, Alibaba Cloud OSS
|
||||
- **International / open platforms**: GitHub, SM.MS, Imgur
|
||||
- **More options via plugins**: AWS S3, Cloudflare R2, MinIO, and more
|
||||
|
||||
| 下载源 | 地址/安装方式 | 平台 | 备注 |
|
||||
|---|---|---|---|
|
||||
| GitHub Release | https://github.com/Molunerfinn/PicGo/releases | All | 国内下载速度可能会慢 |
|
||||
| [腾讯云COS](https://cloud.tencent.com/product/cos) | https://github.com/Molunerfinn/PicGo/releases 附在更新日志结尾 | All | 感谢 [腾讯云COS](https://cloud.tencent.com/product/cos) 提供的赞助支持 |
|
||||
| [山东大学镜像站](https://mirrors.sdu.edu.cn/) | https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo | All | 感谢 [山东大学镜像站](https://mirrors.sdu.edu.cn/) 提供的镜像支持 |
|
||||
| [Scoop](https://scoop.sh/) | `scoop bucket add helbing https://github.com/helbing/scoop-bucket` & `scoop install picgo` | Windows | 感谢 @helbing 的贡献 |
|
||||
| [Chocolatey](https://chocolatey.org/) | `choco install picgo` | Windows | 感谢 @iYato 的贡献 |
|
||||
| [Homebrew](https://brew.sh/) | `brew install picgo --cask` | macOS | 感谢 @womeimingzi11 的贡献 |
|
||||
| [AUR](https://aur.archlinux.org/packages/yay) | `yay -S picgo-appimage` | Arch-Linux | 感谢 @houbaron 的贡献 |
|
||||
> **Note**: PicGo itself will no longer add new third-party Image hosts by default. You can build Image host plugins yourself—see [PicGo-Core](https://picgo.github.io/PicGo-Core-Doc/).
|
||||
|
||||
## 应用截图
|
||||
## ✨ Key Features
|
||||
|
||||
PicGo is built around a fast, low-friction image upload experience:
|
||||
|
||||
### ⚡ Smooth writing flow
|
||||
- **Auto-copy links**: once an upload finishes, the link is copied to your clipboard automatically.
|
||||
- **Flexible formats**: Markdown, HTML, URL, custom templates—paste directly into any editor.
|
||||
- **Zero-Context Switching**: Don't switch windows. Just paste images directly into your favorite editor, and let PicGo handle the upload in the background.
|
||||
- _Enable this workflow via native support or community plugins:_ [Obsidian](https://obsidian.md) \ [VS Code](https://code.visualstudio.com/) \ [Typora](https://typora.io/) \ [Neovim](https://neovim.io/) \ [MarkText](https://marktext.me/) \ [SiYuan](https://b3log.org/siyuan/en/) \ And more...
|
||||
|
||||
### 🚀 Fast uploads
|
||||
- **Multiple ways to upload**: drag & drop, paste from clipboard, hotkeys, and even right-click context menu upload on macOS/Windows.
|
||||
- **Global hotkey**: press `Command+Shift+P` (macOS) / `Ctrl+Shift+P` (Windows/Linux) to open the upload window without leaving your current app. The global key can be customized.
|
||||
|
||||
### 🧩 Powerful plugin ecosystem
|
||||
- **Highly extensible**: plugins already exist for AWS S3, Cloudflare R2, MinIO, and many other Image hosts.
|
||||
- **Even more possibilities**: image compression, watermarking, renaming, Markdown image migration, and more.
|
||||
- Explore plugins: [Awesome-PicGo](https://github.com/PicGo/Awesome-PicGo)
|
||||
|
||||
### 🛠 Developer-friendly
|
||||
- **HTTP API**: upload via HTTP requests (v2.2.0+), making it easy to integrate with other tools.
|
||||
- **Open source**: fully open-source and transparent.
|
||||
- **Great documentation**: detailed docs help you get started quickly. For plugin development, see the [PicGo-Core docs](https://picgo.github.io/PicGo-Core-Doc/).
|
||||
|
||||
> There’s more to discover—development progress is tracked in [Projects](https://github.com/Molunerfinn/PicGo/projects).
|
||||
|
||||
If you’re new to PicGo, start with the [User Guide](https://picgo.github.io/PicGo-Doc/guide/getting-started.html). If you run into issues, check the [FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md) and closed [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed).
|
||||
|
||||
## Download & Install
|
||||
|
||||
| Source | Link / Installation | Platform | Notes |
|
||||
| --------------------------------------------------------- | ----------------------------------------------------------- | ---------- | --------------------------------------- |
|
||||
| GitHub Releases | https://github.com/Molunerfinn/PicGo/releases | All | Downloads may be slow in mainland China |
|
||||
| [Shandong University mirror](https://mirrors.sdu.edu.cn/) | https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo | All | Thanks to the mirror for hosting |
|
||||
| [Scoop](https://scoop.sh/) | `scoop bucket add extras` & `scoop install picgo` | Windows | Thanks to @huangnauh and @Gladtbam |
|
||||
| [Chocolatey](https://chocolatey.org/) | `choco install picgo` | Windows | Thanks to @iYato |
|
||||
| [Homebrew](https://brew.sh/) | `brew install picgo --cask` | macOS | Thanks to @womeimingzi11 |
|
||||
| [AUR](https://aur.archlinux.org/packages/yay) | `yay -S picgo-appimage` | Arch Linux | Thanks to @houbaron |
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 开发说明
|
||||
## Development
|
||||
|
||||
> 目前仅针对 Mac、Windows。Linux 平台并未测试。
|
||||
> Currently tested on macOS and Windows only. Linux has not been fully tested.
|
||||
|
||||
如果你想要学习、开发、修改或自行构建 PicGo,可以依照下面的指示:
|
||||
If you want to learn, contribute, modify, or build PicGo yourself:
|
||||
|
||||
> 如果想学习 Electron-vue 的开发,可以查看我写的系列教程——[Electron-vue 开发实战](https://molunerfinn.com/tags/Electron-vue/)
|
||||
> For an Electron-vue learning series, see: [Electron-vue development](https://molunerfinn.com/tags/Electron-vue/)
|
||||
|
||||
1. 你需要有 Node、Git 环境,了解 npm 的相关知识。
|
||||
2. `git clone https://github.com/Molunerfinn/PicGo.git` 并进入项目。
|
||||
3. `yarn` 下载依赖。注意如果你没有 `yarn`,请去 [官网](https://classic.yarnpkg.com/en/docs/install) 下载安装后再使用。 **用 `npm install` 将导致未知错误!**
|
||||
4. Mac 需要有 Xcode 环境,Windows 需要有 VS 环境。
|
||||
5. 如果需要贡献代码,可以参考[贡献指南](./CONTRIBUTING.md)。
|
||||
1. Install Node.js and Git, and make sure you’re familiar with npm basics.
|
||||
2. Clone the repo: `git clone https://github.com/Molunerfinn/PicGo.git` and enter the directory.
|
||||
3. Install dependencies with `pnpm`. If you don’t have it yet, install it from the [pnpm website](https://pnpm.io/installation) first.
|
||||
4. On macOS you’ll need Xcode; on Windows you’ll need Visual Studio.
|
||||
5. For contributing, see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
### 开发模式
|
||||
### Development mode
|
||||
|
||||
输入 `npm run electron:serve` 进入开发模式,开发模式具有热重载特性。不过需要注意的是,开发模式不稳定,会有进程崩溃的情况。此时需要:
|
||||
Run `pnpm run dev` to start the dev workflow with hot reload. Note: dev mode can be unstable and the process may crash—if that happens:
|
||||
|
||||
```bash
|
||||
ctrl+c # 退出开发模式
|
||||
npm run electron:serve # 重新进入开发模式
|
||||
ctrl+c # stop dev mode
|
||||
pnpm run dev # restart
|
||||
```
|
||||
|
||||
**注:Windows 开发模式运行之后会在底部任务栏的右下角应用区出现 PicGo 的应用图标。**
|
||||
> On Windows, after dev mode starts, PicGo’s tray icon will appear in the bottom-right system tray area.
|
||||
|
||||
### 生产模式
|
||||
### Production build
|
||||
|
||||
如果你需要自行构建,可以 `npm run electron:build` 开始进行构建。构建成功后,会在 `dist_electron` 目录里出现构建成功的相应安装文件。
|
||||
To build release artifacts locally, run `pnpm run build`. After a successful build, the installer files will be generated under `dist`.
|
||||
|
||||
**注意**:如果你的网络环境不太好,可能会出现 `electron-builder` 下载 `electron` 二进制文件失败的情况。这个时候需要在 `npm run electron:build` 之前指定一下 `electron` 的源为国内源:
|
||||
**Note**: If your network is unstable, `electron-builder` may fail to download Electron binaries. You can set an alternative mirror before building:
|
||||
|
||||
```bash
|
||||
export ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"
|
||||
# 在 Windows 上,则可以使用 set ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ (无需引号)
|
||||
npm run electron:build
|
||||
# On Windows: set ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ (no quotes)
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
只需第一次构建的时候指定一下国内源即可。后续构建不需要特地指定。二进制文件下载在 `~/.electron/` 目录下。如果想要更新 `electron` 构建版本,可以删除 `~/.electron/` 目录,然后重新运行上一步,让 `electron-builder `去下载最新的 `electron` 二进制文件。
|
||||
Electron binaries are stored under `~/.electron/`. If you need to refresh them, delete that directory and rebuild.
|
||||
|
||||
## 其他相关
|
||||
## Related Projects
|
||||
|
||||
- [vs-picgo](https://github.com/PicGo/vs-picgo):PicGo 的 VS Code 版。
|
||||
- [flutter-picgo](https://github.com/PicGo/flutter-picgo):PicGo 的手机版 App(支持 Android 和 iOS )。
|
||||
- [PicHoro](https://github.com/Kuingsmile/PicHoro):另一款支持 PicGo 配置的手机版 App(暂时只支持 Android)。
|
||||
- [vs-picgo](https://github.com/PicGo/vs-picgo): PicGo for VS Code.
|
||||
- [flutter-picgo](https://github.com/PicGo/flutter-picgo): mobile app (Android & iOS).
|
||||
- [PicHoro](https://github.com/Kuingsmile/PicHoro): another mobile app compatible with PicGo config (Android only for now).
|
||||
|
||||
## 赞助
|
||||
## Sponsorship
|
||||
|
||||
如果你喜欢 PicGo 并且它对你确实有帮助,欢迎给我打赏一杯咖啡哈~
|
||||
If you like PicGo and it helps your workflow, feel free to buy me a coffee.
|
||||
|
||||
支付宝:
|
||||
Alipay:
|
||||
|
||||

|
||||
|
||||
微信:
|
||||
WeChat Pay:
|
||||
|
||||

|
||||
|
||||
GitHub Sponsors:
|
||||
|
||||
[](https://github.com/sponsors/Molunerfinn)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
||||
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
<div align="center" markdown="1">
|
||||
<sup>Special thanks to:</sup>
|
||||
<br>
|
||||
<a href="https://go.warp.dev/picgo">
|
||||
<img alt="Warp sponsorship" width="400" src="https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Github/Sponsor/Warp-Github-LG-03.png">
|
||||
</a>
|
||||
|
||||
### [Warp, the intelligent terminal for developers](https://go.warp.dev/picgo)
|
||||
[Available for macOS, Linux, & Windows](https://go.warp.dev/picgo)<br>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
**中文** | [English](./README.md)
|
||||
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/New%20LOGO-150.png" alt="PicGo Logo">
|
||||
<h1>PicGo</h1>
|
||||
<h3>高效创作者的最佳图片上传工具</h3>
|
||||
<p>The Ultimate Image Uploader for Efficient Creators</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Molunerfinn/PicGo/actions">
|
||||
<img src="https://img.shields.io/badge/code%20style-standard-green.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/actions">
|
||||
<img src="https://github.com/Molunerfinn/PicGo/actions/workflows/main.yml/badge.svg" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/releases">
|
||||
<img src="https://img.shields.io/github/downloads/Molunerfinn/PicGo/total.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/Molunerfinn/PicGo/releases/latest">
|
||||
<img src="https://img.shields.io/github/release/Molunerfinn/PicGo.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/PicGo/bump-version">
|
||||
<img src="https://img.shields.io/badge/picgo-convention-blue.svg?style=flat-square" alt="">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## 📖 应用概述
|
||||
|
||||
**PicGo 致力于将图片上传无缝集成到你的创作工作流中。**
|
||||
|
||||
无论你是写博客、做笔记还是编写开发文档,PicGo 都能帮你一键上传图片并自动复制链接,让你专注于内容创作本身,而不是繁琐的上传步骤。
|
||||
|
||||
### 核心支持
|
||||
|
||||
PicGo 原生支持主流图床平台,并可通过插件系统无限扩展:
|
||||
|
||||
- **国内云厂商**:七牛云、腾讯云 COS、又拍云、阿里云 OSS
|
||||
- **国际/开源平台**:GitHub、SM.MS、Imgur
|
||||
- **更多支持**:通过插件支持 AWS S3、Cloudflare R2、MinIO 等第三方图床
|
||||
|
||||
> **注意**:PicGo 本体不再增加默认的第三方图床支持。你可以自行开发第三方图床插件。详见 [PicGo-Core](https://picgo.github.io/PicGo-Core-Doc/)。
|
||||
|
||||
## ✨ 特色功能
|
||||
|
||||
PicGo 打造了全方位的上传体验,让“传图”这件事变得前所未有的简单:
|
||||
|
||||
### ⚡️ 无缝写作流
|
||||
- **自动复制链接**:上传成功后,链接会自动复制到你的剪贴板。
|
||||
- **格式随心定义**:支持 Markdown、HTML、URL、自定义等多种格式,粘贴即用,完美适配你的编辑器。
|
||||
- **零上下文切换**:无需切换窗口。在你常用的编辑器里直接粘贴图片,让 PicGo 在后台完成上传。
|
||||
- _通过原生支持或社区插件开启该工作流:_ [Obsidian](https://obsidian.md) \ [VS Code](https://code.visualstudio.com/) \ [Typora](https://typora.io/) \ [Neovim](https://neovim.io/) \ [MarkText](https://marktext.me/) \ [SiYuan](https://b3log.org/siyuan/en/) \ 等等……
|
||||
|
||||
### 🚀 极速上传体验
|
||||
- **多维上传方式**:支持拖拽图片、剪贴板粘贴、快捷键上传,甚至在 macOS/Windows 上支持右键菜单直接上传。
|
||||
- **全局快捷键**:默认 `Command+Shift+P` (macOS) / `Ctrl+Shift+P` (Windows/Linux) 即可唤起上传,无需离开当前窗口。 快捷键可自定义。
|
||||
|
||||
### 🧩 强大的插件生态
|
||||
- **高度可扩展**:PicGo 拥有丰富的插件系统,已有插件支持 AWS S3、Cloudflare R2、MinIO 等第三方图床。
|
||||
- **更多可能**:支持图片压缩、水印、重命名、Markdown 图片迁移等功能插件。
|
||||
- 探索更多插件:[Awesome-PicGo](https://github.com/PicGo/Awesome-PicGo)
|
||||
|
||||
### 🛠 开发者友好
|
||||
- **HTTP API**:支持通过 HTTP 请求调用 PicGo 上传 (v2.2.0+),方便与其他工具集成。
|
||||
- **开源透明**:代码完全开源,安全可靠。
|
||||
- **丰富的文档**:详尽的开发文档助你快速上手。插件开发请参考 [PicGo-Core 文档](https://picgo.github.io/PicGo-Core-Doc/)。
|
||||
|
||||
> 更多功能等你自己去发现,开发进度可以查看 [Projects](https://github.com/Molunerfinn/PicGo/projects)。
|
||||
|
||||
**如果第一次使用,请参考应用 [使用文档](https://picgo.github.io/PicGo-Doc/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)。**
|
||||
|
||||
|
||||
## 下载安装
|
||||
|
||||
| 下载源 | 地址/安装方式 | 平台 | 备注 |
|
||||
| --------------------------------------------- | ----------------------------------------------------------- | ---------- | ----------------------------------------------------------------- |
|
||||
| GitHub Release | https://github.com/Molunerfinn/PicGo/releases | All | 国内下载速度可能会慢 |
|
||||
| [山东大学镜像站](https://mirrors.sdu.edu.cn/) | https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo | All | 感谢 [山东大学镜像站](https://mirrors.sdu.edu.cn/) 提供的镜像支持 |
|
||||
| [Scoop](https://scoop.sh/) | `scoop bucket add extras` & `scoop install picgo` | Windows | 感谢 @huangnauh 和 @Gladtbam 的贡献 |
|
||||
| [Chocolatey](https://chocolatey.org/) | `choco install picgo` | Windows | 感谢 @iYato 的贡献 |
|
||||
| [Homebrew](https://brew.sh/) | `brew install picgo --cask` | macOS | 感谢 @womeimingzi11 的贡献 |
|
||||
| [AUR](https://aur.archlinux.org/packages/yay) | `yay -S picgo-appimage` | Arch-Linux | 感谢 @houbaron 的贡献 |
|
||||
|
||||
## 应用截图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 开发说明
|
||||
|
||||
> 目前仅针对 Mac、Windows。Linux 平台并未测试。
|
||||
|
||||
如果你想要学习、开发、修改或自行构建 PicGo,可以依照下面的指示:
|
||||
|
||||
> 如果想学习 Electron-vue 的开发,可以查看我写的系列教程——[Electron-vue 开发实战](https://molunerfinn.com/tags/Electron-vue/)
|
||||
|
||||
1. 你需要有 Node、Git 环境,了解 npm 的相关知识。
|
||||
2. `git clone https://github.com/Molunerfinn/PicGo.git` 并进入项目。
|
||||
3. `pnpm` 下载依赖。注意如果你没有 `pnpm`,请去 [官网](https://pnpm.io/installation) 下载安装后再使用。 **用 `pnpm install` 将导致未知错误!**
|
||||
4. Mac 需要有 Xcode 环境,Windows 需要有 VS 环境。
|
||||
5. 如果需要贡献代码,可以参考[贡献指南](./CONTRIBUTING.md)。
|
||||
|
||||
### 开发模式
|
||||
|
||||
输入 `pnpm run dev` 进入开发模式,开发模式具有热重载特性。不过需要注意的是,开发模式不稳定,会有进程崩溃的情况。此时需要:
|
||||
|
||||
```bash
|
||||
ctrl+c # 退出开发模式
|
||||
pnpm run dev # 重新进入开发模式
|
||||
```
|
||||
|
||||
**注:Windows 开发模式运行之后会在底部任务栏的右下角应用区出现 PicGo 的应用图标。**
|
||||
|
||||
### 生产模式
|
||||
|
||||
如果你需要自行构建,可以 `pnpm run build` 开始进行构建。构建成功后,会在 `dist` 目录里出现构建成功的相应安装文件。
|
||||
|
||||
**注意**:如果你的网络环境不太好,可能会出现 `electron-builder` 下载 `electron` 二进制文件失败的情况。这个时候需要在 build 之前指定一下 `electron` 的源为国内源:
|
||||
|
||||
```bash
|
||||
export ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"
|
||||
# 在 Windows 上,则可以使用 set ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ (无需引号)
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
只需第一次构建的时候指定一下国内源即可。后续构建不需要特地指定。二进制文件下载在 `~/.electron/` 目录下。如果想要更新 `electron` 构建版本,可以删除 `~/.electron/` 目录,然后重新运行上一步,让 `electron-builder `去下载最新的 `electron` 二进制文件。
|
||||
|
||||
## 其他相关
|
||||
|
||||
- [vs-picgo](https://github.com/PicGo/vs-picgo):PicGo 的 VS Code 版。
|
||||
- [flutter-picgo](https://github.com/PicGo/flutter-picgo):PicGo 的手机版 App(支持 Android 和 iOS )。
|
||||
- [PicHoro](https://github.com/Kuingsmile/PicHoro):另一款支持 PicGo 配置的手机版 App(暂时只支持 Android)。
|
||||
|
||||
## 赞助
|
||||
|
||||
如果你喜欢 PicGo 并且它对你确实有帮助,欢迎给我打赏一杯咖啡哈~
|
||||
|
||||
支付宝:
|
||||
|
||||

|
||||
|
||||
微信:
|
||||
|
||||

|
||||
|
||||
GitHub Sponsors:
|
||||
|
||||
[](https://github.com/sponsors/Molunerfinn)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
||||
|
||||
Copyright (c) 2017 - Now Molunerfinn
|
||||
@@ -1,32 +0,0 @@
|
||||
# Commented sections below can be used to run tests on the CI server
|
||||
# https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing
|
||||
version: 0.1.{build}
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
image: Visual Studio 2017
|
||||
platform:
|
||||
- x64
|
||||
|
||||
cache:
|
||||
- '%APPDATA%\npm-cache'
|
||||
- '%USERPROFILE%\.electron'
|
||||
- '%USERPROFILE%\AppData\Local\Yarn\cache'
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 16 x64
|
||||
- git reset --hard HEAD
|
||||
- yarn
|
||||
- node --version
|
||||
|
||||
build_script:
|
||||
#- yarn test
|
||||
- yarn release
|
||||
- yarn upload-dist
|
||||
|
||||
test: false
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- 允许 JIT (Electron 必须) -->
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
|
||||
<!-- 允许加载未签名的动态库 (插件、原生模块必须) -->
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
|
||||
<!-- 允许执行内存中可写的页 (部分 Electron 版本需要) -->
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
Binary file not shown.
@@ -0,0 +1,129 @@
|
||||
# PicGo 2.4.0 Changelog
|
||||
|
||||
## Features
|
||||
- Add filename display in the gallery (#1050)
|
||||
- Add default placeholders when images/URLs cannot be shown in the gallery or tray window (#1050)
|
||||

|
||||
- Add filename display in the macOS tray window (#1054)
|
||||
- Allow multiple configs per uploader type and choose one for upload (thanks @STDSuperman, #1016)
|
||||

|
||||
- Allow the uploader selection menu to pick a specific config entry for that uploader type
|
||||

|
||||
- Add Dock icon visibility option (#1045)
|
||||
<img width="787" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/bb9492f1-6522-45ce-ae5d-c614901d8b06" />
|
||||
- Add PicGo Repair Toolbox for self-diagnosis
|
||||
<img width="324" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/98cf6e64-7313-4ebe-83d0-aa86e4514109" />
|
||||
- Add option to encode/escape output URLs (#731)
|
||||
<img width="775" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/6e0c13dd-3404-4b07-9c99-412321835b27" />
|
||||
- Support drag-and-drop uploads for arbitrary file formats (#1052)
|
||||
- Tencent COS: add Endpoint and Intelligent Compression settings (thanks @palmcivet @yc910920)
|
||||

|
||||
- Add Markdown-rendered tips for uploader config (see tcyun uploader config)
|
||||

|
||||
- Show the active uploader config name in the upload screen
|
||||

|
||||
- Gallery toolbar: bulk replace image URL host (#875)
|
||||
Note: select images first; you can filter by uploader; e.g., replace `https://www.a.com/...` with `https://www.b.com/...`
|
||||

|
||||
- Add “Startup Mode”: Silent (default) / Main Window; Windows/Linux also support Mini Window (#915)
|
||||
<img width="577" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/7e63bb5c-44b0-480e-824f-3c2edbed4fbb" />
|
||||
- PicGo Server: support multipart form uploads (field `files`, #428, thanks @happy-game)
|
||||
<img width="951" alt="image" src="https://github.com/user-attachments/assets/14244f1d-60f5-487f-bde6-6d0e009645fb" />
|
||||
|
||||
## Bug Fixes
|
||||
- Fix Windows context-menu config script generation (#1019)
|
||||
- Fix custom link URL encoding (#1112)
|
||||
- Fix potential logging dead loop (#1101)
|
||||
- Fix drag-to-tray upload error (#1107)
|
||||
- Fix filename encoding (#1121)
|
||||
- Prevent errors on GitHub uploads with duplicate filenames
|
||||
- Fix beta.2 style issues
|
||||
- Fix slow filename display in the rename window (#1130)
|
||||
- Fix “open config file” actually opening the log file (#1163)
|
||||
- Fix plugin config dialog not reading/saving correctly
|
||||
- Fix inability to add new uploader configs (created as edit) (#1198, #1196)
|
||||
- Fix macOS context menu disappearing (thanks @muwoo, #1179)
|
||||
- Fix macOS tray window flashing after right-click (thanks @QThans, #1217)
|
||||
- Fix uploader config page not scrollable when many fields (#1237)
|
||||
- Fix Tencent COS URL encoding (#1265)
|
||||
- Fix plugin list search not working (#1297)
|
||||
- Fix clipboard filename losing “seconds” (#1293)
|
||||
- Fix macOS tray copy-link failing on click (#1280, #1210)
|
||||
- Fix auto-copy URL toggle not turning off (#1294, thanks @happy-game)
|
||||
- Fix Wayland clipboard image upload issue (#1261, thanks @happy-game)
|
||||
- Fix URL uploads with Chinese characters being encoded in filenames (#1339)
|
||||
|
||||
## Other
|
||||
- COS distribution paused due to malicious traffic charges
|
||||
- Upgrade to Vue3
|
||||
- Refactor parts of the code
|
||||
- Refactor parts of the code (again)
|
||||
- Add more detailed hotkey logs (#1031)
|
||||
- Known issue: some button styles were incorrect (fixed in later betas)
|
||||
- Update README scoop installation section (thanks @wuhang2003)
|
||||
|
||||
----------
|
||||
|
||||
## Features
|
||||
- 新增 相册页新增文件名展示,参考 #1050
|
||||
- 新增 相册中和顶部栏窗口中无法展示的图片或者 url 将会展示默认图片,参考#1050
|
||||

|
||||
- 新增 macOS 顶部栏窗口新增文件名展示,参考 #1054
|
||||
- 新增 同种类型图床支持多份配置,上传时可以指定某一份配置进行上传。感谢 @STDSuperman ,参考 #1016
|
||||

|
||||
- 新增 选择图床的菜单可以支持选择该图床类型某一项具体配置
|
||||

|
||||
- 新增 显示 dock 栏图标选项,参考 #1045
|
||||
<img width="787" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/bb9492f1-6522-45ce-ae5d-c614901d8b06" />
|
||||
- 新增 PicGo 修复工具箱,可以自行排查一些问题
|
||||
<img width="324" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/98cf6e64-7313-4ebe-83d0-aa86e4514109" />
|
||||
- 新增 对输出的 URL 进行 encode 转义的选项,参考 #731
|
||||
<img width="775" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/6e0c13dd-3404-4b07-9c99-412321835b27" />
|
||||
- 新增 支持拖拽任意格式文件上传,参考 #1052
|
||||
- 新增 腾讯云 COS 支持 `Endpoint` 配置 和 `极智压缩` 配置。 感谢 @palmcivet @yc910920 的贡献!
|
||||

|
||||
- 新增 `tips` 配置渲染的支持,可以动态渲染 Uploader config 的 tips。支持 markdown 格式。参考 [tcyun uploader config](https://github.com/PicGo/PicGo-Core/blob/dev/src/plugins/uploader/tcyun.ts#L280)
|
||||

|
||||
- 新增 上传界面展示当前图床使用的配置名
|
||||

|
||||
- 新增 相册页工具栏,目前内置 `批量修改图片 URL HOST 的功能` 。参考 #875
|
||||
注意:需要先选中指定的图片,然后会根据已选中的图片进行修改,你可以通过图床筛选功能只筛选出需要修改的图片
|
||||
例如,你有一批图片都是 `https://www.a.com/...` 打头的 URL,你想把 `www.a.com` 批量修改成 `www.b.com` ,就可以用这个功能
|
||||

|
||||
- 新增 `启动模式`,可以设置启动的时候是否要打开窗口。全平台支持 `静默启动`(默认值) & `打开主窗口`,Windows 和 Linux 额外支持 `打开 Mini 窗口`。参考 #915
|
||||
<img width="577" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/7e63bb5c-44b0-480e-824f-3c2edbed4fbb" />
|
||||
- 新增 PicGo Server 支持表单形式上传图片。参考 #428 ,感谢 @happy-game
|
||||
表单字段为 files。参考截图:
|
||||
<img width="951" alt="image" src="https://github.com/user-attachments/assets/14244f1d-60f5-487f-bde6-6d0e009645fb" />
|
||||
|
||||
## Bug Fixes
|
||||
- 修复 windows 右键菜单配置生成脚本,参考 #1019
|
||||
- 修复 自定义链接 URL encode 问题,参考 #1112
|
||||
- 修复 日志写入可能存在死循环问题,参考 #1101
|
||||
- 修复 拖拽文件到顶部栏图标报错问题,参考 #1107
|
||||
- 修复 文件名 encode 问题。参考 #1121
|
||||
- 修复 GitHub 重名文件上传不再报错
|
||||
- 修复 beta.2 版本部分样式问题
|
||||
- 修复 重命名窗口某些情况下显示文件名过慢的问题,参考 #1130
|
||||
- 修复 打开配置文件打开的是日志文件的 bug,参考 #1163
|
||||
- 修复 插件配置弹窗打开后无法正确读取和保存配置的问题
|
||||
- 修复 无法新增图床配置的问题(新增变成了编辑)。参考 #1198,#1196
|
||||
- 修复 macOS 右键菜单消失问题。感谢 @muwoo 。参考 #1179
|
||||
- 修复 macOS 顶部栏窗口右键之后一闪而过的问题。 感谢 @QThans。 参考 #1217
|
||||
- 修复 图床配置页面配置项过多时无法滚动到底部的问题。参考 #1237
|
||||
- 修复 腾讯云 COS URL encode 的问题。参考 #1265
|
||||
- 修复 插件列表无法搜索的问题。 参考 #1297
|
||||
- 修复 剪贴板文件名丢失 `秒` 的问题。 参考 #1293
|
||||
- 修复 macOS 顶部栏点击图片复制链接失效的问题。 参考 #1280 , #1210
|
||||
- 修复 自动复制 URL 这个开关无法被关闭的问题。参考 #1294 ,感谢 @happy-game
|
||||
- 修复 wayland 里无法使用剪贴板图片的问题。参考 #1261 ,感谢 @happy-game
|
||||
- 修复 直接通过 URL 上传图片的时候,带有汉字的 URL 上传后,文件名被 encode 的问题。 参考 #1339
|
||||
|
||||
## Other
|
||||
- 由于 PicGo 存储的 COS 空间被恶意刷大量流量导致欠费,暂时停止 COS 渠道的 PicGo 分发
|
||||
- 更新至 Vue3
|
||||
- 重构部分代码
|
||||
- 重构部分代码
|
||||
- 补充更详细的快捷键相关日志,参考 #1031
|
||||
- 已知问题:部分按钮样式有点问题,下个版本会修复
|
||||
- 更新 README中的 scoop 下载安装的部分,感谢 @wuhang2003
|
||||
@@ -0,0 +1,31 @@
|
||||
# PicGo 2.4.1 Changelog
|
||||
|
||||
## Features
|
||||
|
||||
- Add `showMenubarIcon` setting to control the visibility of the macOS menu bar icon. See #1222 for details
|
||||
|
||||
## Bug Fixes
|
||||
- Fix: An issue where the macOS menu bar window could not read images from the clipboard. See https://github.com/Molunerfinn/PicGo/issues/1310 for details
|
||||
- Fix: An issue where the macOS Intel build could not be opened. Please take a look at #1363 and #1310 for details
|
||||
|
||||
## Other
|
||||
- Upgraded Electron to v38 and migrated the underlying build framework to Electron-vite
|
||||
- Unified the main window UI on Windows/Linux to match the macOS style
|
||||
<img width="821" height="472" alt="image" src="https://github.com/user-attachments/assets/dbafadeb-7a9f-40e5-bd87-d4012c9a3902" />
|
||||
- Updated ARM64 builds for Windows & Linux, and added Linux deb packages
|
||||
|
||||
----------
|
||||
|
||||
## Features
|
||||
|
||||
- 新增 `showMenubarIcon` 配置项,用于控制 macOS 顶部栏图标的显示与隐藏。参考 #1222
|
||||
|
||||
## Bug Fixes
|
||||
- 修复 macOS 顶部栏窗口无法获取剪贴板图片的问题。参考 https://github.com/Molunerfinn/PicGo/issues/1310
|
||||
- 修复 macOS intel 架构 app 无法打开的问题。参考 #1363, #1310
|
||||
|
||||
## Other
|
||||
- 更新 Electron 版本到 38 以及切换底层开发框架到 Electron-vite
|
||||
- 更新 Windows\Linux 系统现在主窗口的样式跟 macOS 一致了
|
||||
<img width="821" height="472" alt="image" src="https://github.com/user-attachments/assets/dbafadeb-7a9f-40e5-bd87-d4012c9a3902" />
|
||||
- 更新 Windows & Linux 平台的 ARM64 架构构建产物;新增 Linux `deb` 构建产物
|
||||
@@ -0,0 +1,31 @@
|
||||
# PicGo 2.4.2 Changelog
|
||||
|
||||
## Features
|
||||
- New: macOS version with signature && notarization. No more manual handling
|
||||
<img width="260" height="260" alt="image" src="https://github.com/user-attachments/assets/dc1b7114-1f10-40e3-b45e-e3b330712399" />
|
||||
- Add duplicate button for image host config item. And add a double confirm dialog for `duplicate` and `delete` config item action
|
||||
<img width="800" height="450" alt="image" src="https://github.com/user-attachments/assets/238881e3-e993-4cb7-9699-fd2f7441ce02" />
|
||||
- Add: Refactor notifications and add `notificationSound` setting (#1370)
|
||||
|
||||
## Bug Fixes
|
||||
- Fix: An issue that the app icon will be too big under macOS 26. See #1367 for more details
|
||||
- Fix: Clamp tray image titles to two lines or the style will be broken
|
||||
|
||||
## Other
|
||||
- Refactor: Remove config store (#1371)
|
||||
|
||||
----------
|
||||
|
||||
## Features
|
||||
- 新增:macOS 版本签名+公证,现在不再需要手动放行,可以直接下载安装使用
|
||||
<img width="260" height="260" alt="image" src="https://github.com/user-attachments/assets/dc1b7114-1f10-40e3-b45e-e3b330712399" />
|
||||
- 新增:图床配置界面新增复制按钮,可以一键复制已有配置,方便进行多样化操作。同时复制和删除均增加二次确认弹窗
|
||||
<img width="800" height="450" alt="image" src="https://github.com/user-attachments/assets/238881e3-e993-4cb7-9699-fd2f7441ce02" />
|
||||
- 新增:重构通知并新增 `notificationSound` 设置(#1370)
|
||||
|
||||
## Bug Fixes
|
||||
- 修复:在 macOS 26 版本下 app 图标过大的问题。参考 #1367
|
||||
- 修复:托盘图片标题最多显示两行否则样式会被破坏的问题
|
||||
|
||||
## Other
|
||||
- 重构:移除 config store(#1371)
|
||||
@@ -0,0 +1,50 @@
|
||||
# gen_changelog.md
|
||||
|
||||
This guide describes how to generate a consolidated changelog for any PicGo release series (e.g., 2.4.0, 2.5.0) from the GitHub release notes of its betas and finals
|
||||
|
||||
## Source of truth
|
||||
- Use GitHub release pages only (e.g., `https://github.com/Molunerfinn/PicGo/releases/tag/vX.Y.Z-beta.N` and `vX.Y.Z` if present). You can fetch these via MCP GitHub APIs (e.g., `get_release_by_tag`, `list_releases`) instead of manual browsing
|
||||
- Do **not** use `CHANGELOG.md` in the repo
|
||||
- Preserve all text and images exactly as written in the releases
|
||||
- Exception: Remove holiday greetings (e.g., "Happy New Year!", "新年快乐!") from the generated changelog. These can stay in GitHub release notes but should not appear in `changelog/X.Y.Z.md`
|
||||
|
||||
## Structure
|
||||
- Target file: `changelog/X.Y.Z.md` (replace with the series version)
|
||||
- Three top-level sections: `## Features`, `## Bug Fixes`, `## Other`
|
||||
- Do **not** nest content under individual beta version headers; merge all items into these sections
|
||||
- Keep items in chronological order (early → late) within each section, mirroring the beta sequence
|
||||
- Keep inline issue references, thanks, and notes as-is
|
||||
- Remove download-link sections entirely
|
||||
- After the English sections, insert a line with `----------`, then add a full Chinese translation with the same structure/content (including images)
|
||||
|
||||
## Formatting rules
|
||||
- Markdown, plain ASCII
|
||||
- Bullet lists only; no numbered lists required
|
||||
- Indent images under their bullet with two spaces to keep association clear
|
||||
- Keep inline HTML image tags from the releases (e.g., `<img width="...">`) untouched
|
||||
- Keep line breaks and multi-line notes intact
|
||||
- Do not reword or summarize; copy content verbatim except for removing beta headers and download sections
|
||||
- If you need to add items from git commit logs (for the same release series), do not paste raw commit messages. Normalize them to the existing changelog style (e.g., use `Add:`, `Fix:`, `Refactor:` prefixes without emoji)
|
||||
- Chinese translation must mirror the English bullets in order and content (keep images alongside the translated bullets)
|
||||
|
||||
## Regeneration steps
|
||||
1) Collect all release bodies for the target series (e.g., `vX.Y.Z-beta.0…N` and, if present, `vX.Y.Z`) from GitHub releases
|
||||
2) For each release, copy bullets and images into the appropriate section:
|
||||
- Features ↔ “Feature(s)” or “Features” blocks
|
||||
- Bug Fixes ↔ “Bug Fixes” blocks
|
||||
- Other ↔ “Other”, “Notice”, or misc notes that are not features/bugs
|
||||
3) Omit “国内可下载链接” (or any download links) entirely
|
||||
4) Drop beta subheadings; keep only section-level bullets in chronological order
|
||||
5) Ensure images remain adjacent to their bullets with indentation
|
||||
6) After the English sections are complete, insert `----------` on its own line
|
||||
7) Append the Chinese translation, preserving bullet order and images, under `## Features`, `## Bug Fixes`, `## Other` again (same section titles, just Chinese content; no “(Chinese)” suffix)
|
||||
8) Save the result to `changelog/X.Y.Z.md`
|
||||
|
||||
## Quick checklist
|
||||
- [ ] All features present with images kept
|
||||
- [ ] All bug fixes present
|
||||
- [ ] All “Other” notes present
|
||||
- [ ] No beta headers
|
||||
- [ ] No download links
|
||||
- [ ] Chronological ordering preserved
|
||||
- [ ] Chinese translation present with matching bullets/images after `----------`
|
||||
+2
-2
@@ -7,13 +7,13 @@
|
||||
small(v-if="version") {{ version }}
|
||||
h2.desc 图片上传+管理新体验
|
||||
button.download(@click="goLink('https://github.com/Molunerfinn/picgo/releases')") 免费下载
|
||||
button.download(@click="goLink('https://picgo.github.io/PicGo-Doc/zh/guide/')") 查看文档
|
||||
button.download(@click="goLink('https://picgo.github.io/PicGo-Doc/guide/')") 查看文档
|
||||
h3.desc
|
||||
| 基于#[a(href="https://github.com/SimulatedGREG/electron-vue" target="_blank") electron-vue]开发
|
||||
h3.desc
|
||||
| 支持macOS,Windows,Linux
|
||||
h3.desc
|
||||
| 支持#[a(href="https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E6%8F%92%E4%BB%B6%E8%AE%BE%E7%BD%AE%EF%BC%88v2-0%EF%BC%89" target="_blank") 插件系统],让PicGo更强大
|
||||
| 支持#[a(href="https://picgo.github.io/PicGo-Doc/guide/config.html#%E6%8F%92%E4%BB%B6%E8%AE%BE%E7%BD%AE%EF%BC%88v2-0%EF%BC%89" target="_blank") 插件系统],让PicGo更强大
|
||||
#container.container-fluid
|
||||
.row.ex-width
|
||||
img.gallery.col-xs-10.col-xs-offset-1.col-md-offset-2.col-md-8(src="https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/first.png")
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import type { Configuration } from 'electron-builder'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const shouldNotarize = process.env.SKIP_NOTARIZE !== 'true';
|
||||
|
||||
const config: Configuration = {
|
||||
appId: 'com.molunerfinn.picgo',
|
||||
productName: 'PicGo',
|
||||
afterSign: shouldNotarize ? 'scripts/notarize.js' : undefined,
|
||||
// publish: [
|
||||
// {
|
||||
// provider: 'github',
|
||||
// owner: 'Molunerfinn',
|
||||
// repo: 'PicGo',
|
||||
// releaseType: 'draft'
|
||||
// }
|
||||
// ],
|
||||
// temporarily disable auto update feature
|
||||
publish: [],
|
||||
files: [
|
||||
'dist_electron/**/*',
|
||||
'public/**/*',
|
||||
'package.json',
|
||||
'!node_modules/@babel/**/*',
|
||||
"!**/node_modules/typescript{,/**}"
|
||||
],
|
||||
extraResources: [
|
||||
{
|
||||
from: 'public',
|
||||
to: 'public'
|
||||
}
|
||||
],
|
||||
dmg: {
|
||||
contents: [
|
||||
{
|
||||
x: 410,
|
||||
y: 150,
|
||||
type: 'link',
|
||||
path: '/Applications'
|
||||
},
|
||||
{
|
||||
x: 130,
|
||||
y: 150,
|
||||
type: 'file'
|
||||
}
|
||||
]
|
||||
},
|
||||
mac: {
|
||||
icon: 'build/icons/icon.icns',
|
||||
extendInfo: {
|
||||
LSUIElement: 0
|
||||
},
|
||||
target: [
|
||||
{
|
||||
target: 'dmg',
|
||||
arch: ['arm64', 'x64']
|
||||
}
|
||||
],
|
||||
artifactName: 'PicGo-${version}-${arch}.${ext}',
|
||||
hardenedRuntime: true,
|
||||
entitlements: 'build/entitlements.mac.plist',
|
||||
entitlementsInherit: 'build/entitlements.mac.plist',
|
||||
notarize: false
|
||||
},
|
||||
win: {
|
||||
icon: 'build/icons/icon.ico',
|
||||
artifactName: 'PicGo-${version}-${arch}.exe',
|
||||
target: [
|
||||
{
|
||||
target: 'nsis',
|
||||
arch: ['x64']
|
||||
},
|
||||
{
|
||||
target: 'nsis',
|
||||
arch: ['ia32']
|
||||
},
|
||||
{
|
||||
target: 'nsis',
|
||||
arch: ['arm64']
|
||||
}
|
||||
]
|
||||
},
|
||||
nsis: {
|
||||
shortcutName: 'PicGo',
|
||||
oneClick: false,
|
||||
allowToChangeInstallationDirectory: true,
|
||||
buildUniversalInstaller: false,
|
||||
include: 'build/installer.nsh'
|
||||
},
|
||||
linux: {
|
||||
executableName: 'PicGo',
|
||||
icon: 'build/icons/512x512.png',
|
||||
artifactName: 'PicGo-${version}-${arch}.${ext}',
|
||||
target: [
|
||||
{
|
||||
target: 'AppImage',
|
||||
arch: ['x64', 'arm64']
|
||||
},
|
||||
{
|
||||
target: 'deb',
|
||||
arch: ['x64', 'arm64']
|
||||
},
|
||||
{
|
||||
target: 'snap',
|
||||
arch: ['x64'],
|
||||
}
|
||||
],
|
||||
maintainer: 'Molunerfinn',
|
||||
category: 'Utility',
|
||||
publish: []
|
||||
},
|
||||
snap: {
|
||||
publish: []
|
||||
}
|
||||
}
|
||||
|
||||
export default config
|
||||
@@ -0,0 +1,58 @@
|
||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
// temp for webUtils
|
||||
import electronRenderer from '@molunerfinn/vite-plugin-electron-renderer'
|
||||
import { resolve } from 'path'
|
||||
|
||||
const alias = {
|
||||
'@': resolve(__dirname, 'src/renderer'),
|
||||
'~': resolve(__dirname, 'src'),
|
||||
'#': resolve(__dirname, 'src/universal'),
|
||||
root: resolve(__dirname, '.'),
|
||||
apis: resolve(__dirname, 'src/main/apis'),
|
||||
'@core': resolve(__dirname, 'src/main/apis/core')
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
resolve: { alias },
|
||||
build: {
|
||||
outDir: 'dist_electron/main',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, 'src/background.ts')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
resolve: { alias },
|
||||
build: {
|
||||
outDir: 'dist_electron/preload',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, 'src/preload/index.ts')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
renderer: {
|
||||
root: 'src/renderer',
|
||||
publicDir: resolve(__dirname, 'src/renderer/public'),
|
||||
resolve: { alias },
|
||||
plugins: [vue(), electronRenderer()],
|
||||
build: {
|
||||
outDir: 'dist_electron/renderer',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, 'src/renderer/index.html')
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 5173
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,143 @@
|
||||
const globals = require('globals')
|
||||
const path = require('node:path')
|
||||
const eslintJs = require('@eslint/js')
|
||||
const tsPlugin = require('@typescript-eslint/eslint-plugin')
|
||||
const tsParser = require('@typescript-eslint/parser')
|
||||
const importPlugin = require('eslint-plugin-import')
|
||||
const promisePlugin = require('eslint-plugin-promise')
|
||||
const vuePlugin = require('eslint-plugin-vue')
|
||||
const stylistic = require('@stylistic/eslint-plugin')
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
const vueConfigs = vuePlugin.configs['flat/recommended'].map(config => ({
|
||||
...config,
|
||||
languageOptions: {
|
||||
...(config.languageOptions || {}),
|
||||
parserOptions: {
|
||||
...(config.languageOptions?.parserOptions || {}),
|
||||
parser: tsParser,
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
extraFileExtensions: ['.vue']
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
const tsConfigs = tsPlugin.configs['flat/recommended'].map(config => ({
|
||||
...config,
|
||||
files: config.files || ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
|
||||
languageOptions: {
|
||||
...(config.languageOptions || {}),
|
||||
parser: tsParser,
|
||||
parserOptions: {
|
||||
...(config.languageOptions?.parserOptions || {}),
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
extraFileExtensions: ['.vue'],
|
||||
project: path.join(__dirname, 'tsconfig.json'),
|
||||
tsconfigRootDir: __dirname
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
...(config.rules || {}),
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': ['error', {
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrors: 'none'
|
||||
}],
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/no-unsafe-function-type': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'off'
|
||||
}
|
||||
}))
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
ignores: [
|
||||
'dist/**',
|
||||
'dist_electron/**',
|
||||
'build/**',
|
||||
'test/unit/coverage/**',
|
||||
'test/unit/*.js',
|
||||
'test/e2e/*.js',
|
||||
'node_modules/**'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'eslint/base',
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
globals: {
|
||||
__static: 'readonly'
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
import: importPlugin,
|
||||
promise: promisePlugin,
|
||||
'@stylistic': stylistic
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx', '.d.ts', '.vue']
|
||||
}
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
...eslintJs.configs.recommended.rules,
|
||||
...importPlugin.configs.recommended.rules,
|
||||
...promisePlugin.configs.recommended.rules,
|
||||
'import/named': 'off',
|
||||
'import/no-named-as-default-member': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
'promise/catch-or-return': 'off',
|
||||
'promise/always-return': 'off',
|
||||
'no-console': 'off',
|
||||
'no-debugger': isProduction ? 'error' : 'off',
|
||||
'no-async-promise-executor': 'off',
|
||||
'no-empty': ['error', { allowEmptyCatch: true }],
|
||||
'no-unused-vars': 'off',
|
||||
'@stylistic/indent': ['error', 2],
|
||||
'@stylistic/semi': ['error', 'never'],
|
||||
'no-unexpected-multiline': 'error'
|
||||
}
|
||||
},
|
||||
...vueConfigs,
|
||||
{
|
||||
files: ['*.vue', '**/*.vue'],
|
||||
rules: {
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/attribute-hyphenation': 'off'
|
||||
}
|
||||
},
|
||||
...tsConfigs,
|
||||
{
|
||||
files: ['**/*.{ts,tsx,vue}'],
|
||||
rules: {
|
||||
'no-undef': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.d.ts'],
|
||||
rules: {
|
||||
'no-var': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
// 1. 针对所有 JS 文件(或者特定目录)启用 Node 全局变量
|
||||
files: ["**/*.js", "scripts/*.js"],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node, // 注入 process, require, module, __dirname 等
|
||||
...globals.browser // 如果你的项目是前端项目,可能还需要 browser
|
||||
},
|
||||
sourceType: "commonjs" // 如果你的项目代码主要是 CJS,加上这个;如果是 ESM 则设为 "module"
|
||||
}
|
||||
},
|
||||
]
|
||||
+73
-47
@@ -1,88 +1,118 @@
|
||||
{
|
||||
"name": "picgo",
|
||||
"version": "2.4.0-beta.1",
|
||||
"version": "2.4.3",
|
||||
"private": true,
|
||||
"main": "dist_electron/main/index.js",
|
||||
"description": "A powerful & simple image uploader for creators.",
|
||||
"author": {
|
||||
"name": "Molunerfinn",
|
||||
"email": "marksz@teamsz.xyz"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vue-cli-service electron:build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"build": "electron-vite build && electron-builder --config electron-builder.config.ts --publish never",
|
||||
"build:win": "npm run build && electron-builder --config electron-builder.config.ts --win --publish never",
|
||||
"build:mac": "npm run build && electron-builder --config electron-builder.config.ts --mac --publish never",
|
||||
"build:linux": "npm run build && electron-builder --config electron-builder.config.ts --linux --publish never",
|
||||
"build:local": "dotenv -e .env -- electron-vite build && electron-builder --config electron-builder.config.ts --publish never",
|
||||
"lint": "pnpm lint:dpdm && eslint --ext .js,.jsx,.ts,.tsx,.vue src/",
|
||||
"tsc": "tsc --noEmit",
|
||||
"bump": "bump-version",
|
||||
"cz": "git-cz",
|
||||
"dev": "vue-cli-service electron:serve",
|
||||
"electron:build": "vue-cli-service electron:build",
|
||||
"electron:serve": "vue-cli-service electron:serve",
|
||||
"dev": "electron-vite dev",
|
||||
"preview": "electron-vite preview",
|
||||
"gen-i18n": "node ./scripts/gen-i18n-types.js",
|
||||
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postuninstall": "electron-builder install-app-deps",
|
||||
"release": "vue-cli-service electron:build --publish always",
|
||||
"upload-dist": "node ./scripts/upload-dist.js"
|
||||
"upload-dist": "node ./scripts/upload-dist.js",
|
||||
"lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/background.ts",
|
||||
"check": "pnpm run tsc && pnpm run lint",
|
||||
"test": "vitest run src/__tests__",
|
||||
"prepare": "husky",
|
||||
"commitlint": "commitlint --edit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@picgo/i18n": "^1.0.0",
|
||||
"@picgo/store": "^2.0.4",
|
||||
"@picgo/store": "^2.1.0",
|
||||
"@picgo/video-duration": "^1.0.1",
|
||||
"axios": "^0.19.0",
|
||||
"clip-filepaths": "^0.3.0",
|
||||
"compare-versions": "^4.1.3",
|
||||
"core-js": "^3.27.1",
|
||||
"element-plus": "^2.2.28",
|
||||
"dayjs": "^1.11.19",
|
||||
"element-plus": "^2.3.7",
|
||||
"epipebomb": "^1.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"keycode": "^2.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-id": "^0.14.0",
|
||||
"lowdb": "^1.0.0",
|
||||
"mitt": "^3.0.0",
|
||||
"picgo": "^1.5.1",
|
||||
"marked": "^7.0.4",
|
||||
"mime-types": "^3.0.2",
|
||||
"mitt": "^3.0.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"picgo": "^1.8.1",
|
||||
"qrcode.vue": "^3.3.3",
|
||||
"semver": "^7.7.3",
|
||||
"shell-path": "2.1.0",
|
||||
"uuidv4": "^6.2.11",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6",
|
||||
"systeminformation": "^5.27.14",
|
||||
"tunnel": "^0.0.6",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.2",
|
||||
"vue3-lazyload": "^0.3.6",
|
||||
"vue3-photo-preview": "^0.2.9",
|
||||
"write-file-atomic": "^4.0.1"
|
||||
"vue3-photo-preview": "^0.3.0",
|
||||
"write-file-atomic": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-s3": "^3.276.0",
|
||||
"@aws-sdk/lib-storage": "^3.276.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
|
||||
"@picgo/bump-version": "^1.1.2",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@molunerfinn/vite-plugin-electron-renderer": "^0.14.7",
|
||||
"@picgo/bump-version": "^2.0.0",
|
||||
"@stylistic/eslint-plugin": "^5.6.1",
|
||||
"@types/electron-devtools-installer": "^2.2.0",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/lodash": "^4.17.21",
|
||||
"@types/lowdb": "^1.0.9",
|
||||
"@types/node": "^16.10.2",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^20",
|
||||
"@types/request-promise-native": "^1.0.17",
|
||||
"@types/semver": "^7.3.8",
|
||||
"@types/tunnel": "^0.0.3",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@types/write-file-atomic": "^4.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@vue/cli-plugin-babel": "^5.0.8",
|
||||
"@vue/cli-plugin-eslint": "^5.0.8",
|
||||
"@vue/cli-plugin-router": "^5.0.8",
|
||||
"@vue/cli-plugin-typescript": "^5.0.8",
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"@vue/eslint-config-standard": "^8.0.1",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"@vue/runtime-dom": "^3.2.45",
|
||||
"@typescript-eslint/eslint-plugin": "^8.49.0",
|
||||
"@typescript-eslint/parser": "^8.49.0",
|
||||
"@vitejs/plugin-vue": "^6.0.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"commitizen": "^4.3.1",
|
||||
"conventional-changelog": "^3.1.18",
|
||||
"cz-customizable": "^6.2.0",
|
||||
"cz-customizable": "^7.5.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"electron": "^16.0.6",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"dpdm": "^3.13.1",
|
||||
"electron": "^38",
|
||||
"electron-builder": "26.1.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-standard": ">=16.0.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"husky": "^3.1.0",
|
||||
"electron-vite": "^4.0.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-promise": "^7.2.1",
|
||||
"eslint-plugin-vue": "^10.6.2",
|
||||
"globals": "^16.5.0",
|
||||
"husky": "^9.1.7",
|
||||
"postcss": "^8.4.23",
|
||||
"stylus": "^0.54.7",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"typescript": "^4.4.3",
|
||||
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4"
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.2.6",
|
||||
"vitest": "^4.0.16"
|
||||
},
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
@@ -99,11 +129,7 @@
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
"commit-msg": "npm run lint:dpdm && commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "^16.10.2",
|
||||
"vue-cli-plugin-electron-builder/**/electron-builder": "23.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+11557
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
onlyBuiltDependencies:
|
||||
- core-js
|
||||
- ejs
|
||||
- electron
|
||||
- electron-winstaller
|
||||
- esbuild
|
||||
- husky
|
||||
- vue-demi
|
||||
|
||||
# for multi-arch builds, include both x64 and arm64 versions of electron
|
||||
# will be deprecated in future(use different arch machine to build different arch binaries)
|
||||
supportedArchitectures:
|
||||
os:
|
||||
- current
|
||||
cpu:
|
||||
- x64
|
||||
- arm64
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
autoprefixer: {},
|
||||
tailwindcss: {}
|
||||
}
|
||||
}
|
||||
|
||||
+78
-8
@@ -1,4 +1,4 @@
|
||||
LANG_DISPLAY_LABEL: 'English'
|
||||
LANG_DISPLAY_LABEL: "English"
|
||||
ABOUT: About
|
||||
OPEN_MAIN_WINDOW: Open Main Window
|
||||
CHOOSE_DEFAULT_PICBED: Choose Default Picbed
|
||||
@@ -28,7 +28,7 @@ OPEN_TOOLBOX: Open Toolbox
|
||||
|
||||
# ---renderer i18n begin---
|
||||
|
||||
CHOOSE_YOUR_DEFAULT_PICBED: 'Choose ${d} as your default picbed:'
|
||||
CHOOSE_YOUR_DEFAULT_PICBED: "Choose ${d} as your default picbed:"
|
||||
UPLOAD_AREA: Upload Area
|
||||
GALLERY: Gallery
|
||||
PICBEDS_SETTINGS: Picbeds Settings
|
||||
@@ -56,6 +56,8 @@ BATCH_COPY_LINK_SUCCEED: Batch Copy Link Succeed
|
||||
FILE_RENAME: File Rename
|
||||
COPY_FILE_PATH: Copy file path
|
||||
OPEN_FILE_PATH: Open file path
|
||||
SUCCESS: Success
|
||||
FAILED: Failed
|
||||
|
||||
# settings
|
||||
|
||||
@@ -66,6 +68,7 @@ SETTINGS_SET_LOG_FILE: Set Log File
|
||||
SETTINGS_CLICK_TO_SET: Click to Set
|
||||
SETTINGS_CLICK_TO_CHECK: Click to Check
|
||||
SETTINGS_SET_SHORTCUT: Set Shortcut
|
||||
SETTINGS_URL_REWRITE: URL Rewrite
|
||||
SETTINGS_CUSTOM_LINK_FORMAT: Custom Link Format
|
||||
SETTINGS_SET_PROXY_AND_MIRROR: Set Proxy and Mirror
|
||||
SETTINGS_SET_SERVER: Set Server
|
||||
@@ -78,12 +81,13 @@ SETTINGS_LAUNCH_ON_BOOT: Launch On Boot
|
||||
SETTINGS_RENAME_BEFORE_UPLOAD: Rename Before Upload
|
||||
SETTINGS_TIMESTAMP_RENAME: Timestamp Rename
|
||||
SETTINGS_OPEN_UPLOAD_TIPS: Open Upload Tips
|
||||
SETTINGS_NOTIFICATION_SOUND: Play Notification Sound
|
||||
SETTINGS_MINI_WINDOW_ON_TOP: Mini Window On Top
|
||||
SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD: Auto Copy URL After Upload
|
||||
SETTINGS_TIPS_PLACEHOLDER_URL: Use $url to represent url position
|
||||
SETTINGS_TIPS_PLACEHOLDER_FILENAME: Use $fileName to represent file name position
|
||||
SETTINGS_TIPS_PLACEHOLDER_EXTNAME: Use $extName to represent file's ext position
|
||||
SETTINGS_TIPS_SUCH_AS: 'Such as: $url/$fileName'
|
||||
SETTINGS_TIPS_SUCH_AS: "Such as: $url/$fileName"
|
||||
SETTINGS_UPLOAD_PROXY: Upload Proxy
|
||||
SETTINGS_PLUGIN_INSTALL_PROXY: Proxy for Plugin Install
|
||||
SETTINGS_PLUGIN_INSTALL_MIRROR: Mirror for Plugin Install
|
||||
@@ -115,10 +119,56 @@ SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD: Use Builtin Clipboard to Upload
|
||||
SETTINGS_CHOOSE_LANGUAGE: Choose Language
|
||||
UPLOADER_CONFIG_NAME: Configuration Name
|
||||
BUILTIN_CLIPBOARD_TIPS: Use builtin clipboard function to upload instead of using scripts
|
||||
|
||||
# url rewrite
|
||||
|
||||
URL_REWRITE_HELP: Rewrites uploaded image URLs. Rules are evaluated in order; the first matched rule wins.
|
||||
URL_REWRITE_ADD_RULE: Add Rule
|
||||
URL_REWRITE_EDIT_RULE: Edit Rule
|
||||
URL_REWRITE_EMPTY: No rules
|
||||
URL_REWRITE_ORDER: Order
|
||||
URL_REWRITE_MATCH: Match
|
||||
URL_REWRITE_REPLACE: Replace
|
||||
URL_REWRITE_FLAGS: Flags
|
||||
URL_REWRITE_ENABLED: Enabled
|
||||
URL_REWRITE_ACTIONS: Actions
|
||||
URL_REWRITE_MOVE_UP: Up
|
||||
URL_REWRITE_MOVE_DOWN: Down
|
||||
URL_REWRITE_EDIT: Edit
|
||||
URL_REWRITE_DELETE: Delete
|
||||
URL_REWRITE_DELETE_CONFIRM: Delete this rule?
|
||||
URL_REWRITE_MATCH_TIPS: Supports regex (JavaScript RegExp)
|
||||
URL_REWRITE_MATCH_PLACEHOLDER: https://example.com/path
|
||||
URL_REWRITE_REPLACE_TIPS: Replacement string (supports $1, $2, ...)
|
||||
URL_REWRITE_REPLACE_PLACEHOLDER: https://example.org/newpath
|
||||
URL_REWRITE_OPTIONS: Options
|
||||
URL_REWRITE_RULE_ENABLED: Enable this rule
|
||||
URL_REWRITE_FLAG_GLOBAL_LABEL: Global (g)
|
||||
URL_REWRITE_FLAG_GLOBAL_DESC: Replace all occurrences, not just the first one
|
||||
URL_REWRITE_FLAG_IGNORE_CASE_LABEL: Ignore case (i)
|
||||
URL_REWRITE_FLAG_IGNORE_CASE_DESC: Case-insensitive matching (e.g. JPG equals jpg)
|
||||
URL_REWRITE_MATCH_REQUIRED: Match is required
|
||||
URL_REWRITE_REPLACE_REQUIRED: Replace is required
|
||||
URL_REWRITE_INVALID_REGEX: Invalid regex
|
||||
URL_REWRITE_PREVIEW_TITLE: Preview
|
||||
URL_REWRITE_PREVIEW_TIPS: Enter a URL to see how the current rules rewrite it (matched in order; only the first match is applied)
|
||||
URL_REWRITE_PREVIEW_PLACEHOLDER: https://example.com/path/to/image.png
|
||||
URL_REWRITE_PREVIEW_RUN: Preview
|
||||
URL_REWRITE_PREVIEW_OUTPUT: Output URL
|
||||
URL_REWRITE_PREVIEW_INPUT_REQUIRED: Please enter a URL to preview
|
||||
URL_REWRITE_PREVIEW_RULE_INVALID: Invalid rule
|
||||
URL_REWRITE_PREVIEW_MATCHED_RULE: Matched rule
|
||||
URL_REWRITE_PREVIEW_NO_MATCH: No rules matched
|
||||
UPLOADER_CONFIG_PLACEHOLDER: Please Enter Configuration Name
|
||||
SELECTED_SETTING_HINT: Selected
|
||||
SETTINGS_ENCODE_OUTPUT_URL: Encode Output(or Copyed) URL
|
||||
SETTINGS_SHOW_DOCK_ICON: Show Dock icon
|
||||
SETTINGS_SHOW_MENUBAR_ICON: Show Menubar icon
|
||||
SETTINGS_SHOW_MENUBAR_ICON_TIPS: If both "Show Dock icon" and "Show Menubar icon" are turned off, you won't be able to find PicGo's main window. Edit the config file and set showDockIcon or showMenubarIcon to true to recover.
|
||||
SETTINGS_STARTUP_MODE: Startup Mode
|
||||
SETTINGS_STARTUP_MODE_MAIN_WINDOW: Open Main Window
|
||||
SETTINGS_STARTUP_MODE_MINI_WINDOW: Open Mini Window
|
||||
SETTINGS_STARTUP_MODE_HIDE: Silent Startup
|
||||
|
||||
# shortcut-page
|
||||
|
||||
@@ -134,6 +184,22 @@ SHORTCUT_DISABLE: Disable
|
||||
SHORTCUT_EDIT: Edit
|
||||
SHORTCUT_CHANGE_UPLOAD: Change Upload Shortcut
|
||||
|
||||
# gallery-page
|
||||
GALLERY_URL_REWRITE_TITLE: Rewrite Selected Image URLs
|
||||
GALLERY_URL_REWRITE_RESULT_TITLE: Rewrite Image URL Result
|
||||
GALLERY_URL_REWRITE_WARN_NO_SELECTION: You must select at least one picture first
|
||||
|
||||
GALLERY_URL_REWRITE_APPLY_GLOBAL_RULES: Apply global URL rewrite rules
|
||||
GALLERY_URL_REWRITE_GLOBAL_RULES_COUNT: Global rules
|
||||
GALLERY_URL_REWRITE_TEMP_RULE_TIPS: Optional. Leave empty to skip the temporary rule. Temporary rule has higher priority than global rules.
|
||||
GALLERY_URL_REWRITE_TEMP_RULE_REQUIRED: Match and Replace are required for the temporary rule
|
||||
GALLERY_URL_REWRITE_NO_RULES_TO_APPLY: No global rules enabled and no temporary rule provided
|
||||
GALLERY_URL_REWRITE_SAVE_TEMP_RULE_PROMPT: Save the temporary rule to global URL rewrite rules?
|
||||
GALLERY_URL_REWRITE_APPLY_AND_SAVE: Apply and Save
|
||||
GALLERY_URL_REWRITE_APPLY_ONLY: Apply Only
|
||||
GALLERY_URL_REWRITE_NO_CHANGES: No URLs were changed
|
||||
GALLERY_URL_REWRITE_EMPTY_RESULT_WARN: The rewrite result is empty; skipped
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: Wait to Upload
|
||||
@@ -149,7 +215,7 @@ CUSTOM: Custom
|
||||
CLIPBOARD_PICTURE: Clipboard
|
||||
TIPS_DRAG_VALID_PICTURE_OR_URL: Drag valid picture or url to here
|
||||
TIPS_INPUT_URL: Input URL
|
||||
TIPS_HTTP_PREFIX: http:// or https://
|
||||
TIPS_HTTP_PREFIX: Starts with http:// or https://. Multiple URLs supported (one per line)
|
||||
TIPS_INPUT_VALID_URL: Input valid URL
|
||||
|
||||
# plugins
|
||||
@@ -193,10 +259,10 @@ UPDATE_PLUGIN: Update Plugin
|
||||
|
||||
# toolbox
|
||||
TOOLBOX: Toolbox
|
||||
TOOLBOX_TITLE: Troubleshoot PicGo runtime issues
|
||||
TOOLBOX_TITLE: Troubleshoot PicGo runtime issues
|
||||
TOOLBOX_SUB_TITLE: Scan the following items immediately to fix usage issues
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN: Check if the configuration file is damaged
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN: Check if the album file is damaged
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN: Check if the album file is damaged
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD: Check if there is a problem with clipboard picture upload
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_PROXY: Check if the proxy settings are normal
|
||||
TOOLBOX_FIX_DONE_NEED_RELOAD: Repair completed, need to restart to take effect, restart or not
|
||||
@@ -221,18 +287,23 @@ TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: "Please create the folder yourself
|
||||
TIPS_NOTICE: Tips
|
||||
TIPS_WARNING: Warning
|
||||
TIPS_ERROR: Error
|
||||
TIPS_SKIPPED_INVALID_URLS: Skipped ${n} invalid URL(s), see logs for details
|
||||
TIPS_TOO_MANY_URLS_CONFIRM: You are about to upload ${n} URLs at once. This may cause lag. It's recommended to upload in batches. Continue?
|
||||
TIPS_NO_VALID_URLS: No valid URL found
|
||||
TIPS_INSTALL_NODE_AND_RELOAD_PICGO: Please install Node.js and restart PicGo to continue
|
||||
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: Plugin is trying to remove some images from the album gallery, continue?
|
||||
TIPS_PLUGIN_OVERWRITE_GALLERY: Plugin is trying to overwrite the album gallery, continue?
|
||||
TIPS_UPLOAD_NOT_PICTURES: The latest clipboard item is not a picture
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo config file broken, has been restored to default
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo config file broken, has been restored to backup
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: 'Backup file version: ${v}'
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: "Backup file version: ${v}"
|
||||
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: Custom config file parse error, please check the path content
|
||||
TIPS_SHORTCUT_MODIFIED_SUCCEED: Shortcut modified successfully
|
||||
TIPS_SHORTCUT_MODIFIED_CONFLICT: Shortcut conflict, please reset
|
||||
TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED: Custom link style modified successfully
|
||||
TIPS_FIND_NEW_VERSION: Find new version ${v},update many new features, do you want to download the latest version?
|
||||
TIPS_DELETE_UPLOADER_CONFIG: Are you sure you want to delete this config?
|
||||
TIPS_COPY_UPLOADER_CONFIG: Are you sure you want to copy this config?
|
||||
|
||||
# privacy
|
||||
PRIVACY: >
|
||||
@@ -261,7 +332,6 @@ PRIVACY: >
|
||||
b) In accordance with the relevant provisions of the law, or the requirements of administrative or judicial institutions, disclose to third parties or administrative or judicial institutions;
|
||||
|
||||
|
||||
c) If you violate relevant Chinese laws, regulations or relevant rules, you need to disclose it to a third party;
|
||||
|
||||
|
||||
4. Information Security
|
||||
|
||||
+73
-3
@@ -56,6 +56,8 @@ BATCH_COPY_LINK_SUCCEED: 批量复制链接成功
|
||||
FILE_RENAME: 文件改名
|
||||
COPY_FILE_PATH: 复制文件路径
|
||||
OPEN_FILE_PATH: 打开文件路径
|
||||
SUCCESS: 成功
|
||||
FAILED: 失败
|
||||
|
||||
# settings
|
||||
|
||||
@@ -66,6 +68,7 @@ SETTINGS_SET_LOG_FILE: 设置日志文件
|
||||
SETTINGS_CLICK_TO_SET: 点击设置
|
||||
SETTINGS_CLICK_TO_CHECK: 点击检查
|
||||
SETTINGS_SET_SHORTCUT: 设置快捷键
|
||||
SETTINGS_URL_REWRITE: URL 重写
|
||||
SETTINGS_CUSTOM_LINK_FORMAT: 自定义链接格式
|
||||
SETTINGS_SET_PROXY_AND_MIRROR: 设置代理和镜像地址
|
||||
SETTINGS_SET_SERVER: 设置Server
|
||||
@@ -78,6 +81,7 @@ SETTINGS_LAUNCH_ON_BOOT: 开机自启
|
||||
SETTINGS_RENAME_BEFORE_UPLOAD: 上传前重命名
|
||||
SETTINGS_TIMESTAMP_RENAME: 时间戳重命名
|
||||
SETTINGS_OPEN_UPLOAD_TIPS: 开启上传提示
|
||||
SETTINGS_NOTIFICATION_SOUND: 开启通知提示音
|
||||
SETTINGS_MINI_WINDOW_ON_TOP: Mini窗口置顶
|
||||
SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD: 上传后自动复制URL
|
||||
SETTINGS_TIPS_PLACEHOLDER_URL: 用占位符 $url 来表示url的位置
|
||||
@@ -115,10 +119,56 @@ SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD: 使用内置剪贴板上传
|
||||
SETTINGS_CHOOSE_LANGUAGE: 选择语言
|
||||
BUILTIN_CLIPBOARD_TIPS: 使用内置剪贴板函数而不是调用脚本获取剪贴板图片
|
||||
UPLOADER_CONFIG_NAME: 图床配置名
|
||||
|
||||
# url rewrite
|
||||
|
||||
URL_REWRITE_HELP: 用于重写上传后的图片 URL。规则按顺序匹配,命中的第一条匹配生效。
|
||||
URL_REWRITE_ADD_RULE: 新增规则
|
||||
URL_REWRITE_EDIT_RULE: 编辑规则
|
||||
URL_REWRITE_EMPTY: 暂无规则
|
||||
URL_REWRITE_ORDER: 顺序
|
||||
URL_REWRITE_MATCH: 匹配
|
||||
URL_REWRITE_REPLACE: 替换
|
||||
URL_REWRITE_FLAGS: 标志
|
||||
URL_REWRITE_ENABLED: 启用
|
||||
URL_REWRITE_ACTIONS: 操作
|
||||
URL_REWRITE_MOVE_UP: 上移
|
||||
URL_REWRITE_MOVE_DOWN: 下移
|
||||
URL_REWRITE_EDIT: 编辑
|
||||
URL_REWRITE_DELETE: 删除
|
||||
URL_REWRITE_DELETE_CONFIRM: 确定删除该规则?
|
||||
URL_REWRITE_MATCH_TIPS: 支持正则(JavaScript RegExp)
|
||||
URL_REWRITE_MATCH_PLACEHOLDER: https://example.com/path
|
||||
URL_REWRITE_REPLACE_TIPS: 替换内容(支持 $1、$2...)
|
||||
URL_REWRITE_REPLACE_PLACEHOLDER: https://example.org/newpath
|
||||
URL_REWRITE_OPTIONS: 选项
|
||||
URL_REWRITE_RULE_ENABLED: 启用该规则
|
||||
URL_REWRITE_FLAG_GLOBAL_LABEL: 全局(g)
|
||||
URL_REWRITE_FLAG_GLOBAL_DESC: 替换所有找到的内容,而不仅仅是第一个
|
||||
URL_REWRITE_FLAG_IGNORE_CASE_LABEL: 忽略大小写(i)
|
||||
URL_REWRITE_FLAG_IGNORE_CASE_DESC: 匹配时不区分字母大小写(例如 JPG 等同于 jpg)
|
||||
URL_REWRITE_MATCH_REQUIRED: 匹配规则不能为空
|
||||
URL_REWRITE_REPLACE_REQUIRED: 替换内容不能为空
|
||||
URL_REWRITE_INVALID_REGEX: 正则表达式不合法
|
||||
URL_REWRITE_PREVIEW_TITLE: 预览
|
||||
URL_REWRITE_PREVIEW_TIPS: 输入一个 URL,查看当前规则的重写结果(按顺序匹配,仅第一条命中的规则生效)
|
||||
URL_REWRITE_PREVIEW_PLACEHOLDER: https://example.com/path/to/image.png
|
||||
URL_REWRITE_PREVIEW_RUN: 预览
|
||||
URL_REWRITE_PREVIEW_OUTPUT: 输出 URL
|
||||
URL_REWRITE_PREVIEW_INPUT_REQUIRED: 请输入要预览的 URL
|
||||
URL_REWRITE_PREVIEW_RULE_INVALID: 规则无效
|
||||
URL_REWRITE_PREVIEW_MATCHED_RULE: 命中规则
|
||||
URL_REWRITE_PREVIEW_NO_MATCH: 没有规则匹配
|
||||
UPLOADER_CONFIG_PLACEHOLDER: 请输入配置名称
|
||||
SELECTED_SETTING_HINT: 已选中
|
||||
SETTINGS_ENCODE_OUTPUT_URL: 输出(复制) URL 时进行转义
|
||||
SETTINGS_SHOW_DOCK_ICON: 显示 Dock 栏图标
|
||||
SETTINGS_SHOW_MENUBAR_ICON: 显示顶部栏图标
|
||||
SETTINGS_SHOW_MENUBAR_ICON_TIPS: 若“显示 Dock 栏图标”和“显示顶部栏图标”都关闭,将会无法找到 PicGo 主界面。需要手动修改配置文件里的 showDockIcon 或 showMenubarIcon 为 true 才能恢复。
|
||||
SETTINGS_STARTUP_MODE: 启动模式
|
||||
SETTINGS_STARTUP_MODE_MAIN_WINDOW: 打开主窗口
|
||||
SETTINGS_STARTUP_MODE_MINI_WINDOW: 打开 Mini 窗口
|
||||
SETTINGS_STARTUP_MODE_HIDE: 静默启动
|
||||
|
||||
# shortcut-page
|
||||
|
||||
@@ -134,6 +184,22 @@ SHORTCUT_DISABLE: 禁用
|
||||
SHORTCUT_EDIT: 编辑
|
||||
SHORTCUT_CHANGE_UPLOAD: 修改上传快捷键
|
||||
|
||||
# gallery-page
|
||||
GALLERY_URL_REWRITE_TITLE: 重写选中图片 URL
|
||||
GALLERY_URL_REWRITE_RESULT_TITLE: 重写图片 URL 结果
|
||||
GALLERY_URL_REWRITE_WARN_NO_SELECTION: 你必须先选中至少一张图片
|
||||
|
||||
GALLERY_URL_REWRITE_APPLY_GLOBAL_RULES: 应用全局 URL 重写规则
|
||||
GALLERY_URL_REWRITE_GLOBAL_RULES_COUNT: 全局规则数量
|
||||
GALLERY_URL_REWRITE_TEMP_RULE_TIPS: 可选,留空则不使用临时规则(临时规则优先级高于全局规则)。
|
||||
GALLERY_URL_REWRITE_TEMP_RULE_REQUIRED: 临时规则需要同时填写「匹配」和「替换」
|
||||
GALLERY_URL_REWRITE_NO_RULES_TO_APPLY: 没有可用的全局规则,且未填写临时规则
|
||||
GALLERY_URL_REWRITE_SAVE_TEMP_RULE_PROMPT: 是否将临时规则写入全局 URL 重写规则列表?
|
||||
GALLERY_URL_REWRITE_APPLY_AND_SAVE: 应用并写入
|
||||
GALLERY_URL_REWRITE_APPLY_ONLY: 仅应用
|
||||
GALLERY_URL_REWRITE_NO_CHANGES: 没有任何 URL 被修改
|
||||
GALLERY_URL_REWRITE_EMPTY_RESULT_WARN: 重写结果为空,已跳过
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: 等待上传
|
||||
@@ -149,7 +215,7 @@ CUSTOM: 自定义
|
||||
CLIPBOARD_PICTURE: 剪贴板图片
|
||||
TIPS_DRAG_VALID_PICTURE_OR_URL: 请拖入合法的图片文件或者图片URL地址
|
||||
TIPS_INPUT_URL: 请输入URL
|
||||
TIPS_HTTP_PREFIX: http://或者https://开头
|
||||
TIPS_HTTP_PREFIX: http:// 或者 https:// 开头,支持上传多条 URL(请换行输入)
|
||||
TIPS_INPUT_VALID_URL: 请输入合法的URL
|
||||
|
||||
# plugins
|
||||
@@ -221,18 +287,23 @@ TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: 请自行创建文件夹:${path}
|
||||
TIPS_NOTICE: 注意
|
||||
TIPS_WARNING: 警告
|
||||
TIPS_ERROR: 发生错误
|
||||
TIPS_SKIPPED_INVALID_URLS: 已跳过 ${n} 条非法 URL,请查看日志了解详情
|
||||
TIPS_TOO_MANY_URLS_CONFIRM: 你将一次上传 ${n} 条 URL,可能会引起卡顿,建议分批上传。是否继续?
|
||||
TIPS_NO_VALID_URLS: 未检测到合法的 URL
|
||||
TIPS_INSTALL_NODE_AND_RELOAD_PICGO: 请安装Node.js并重启PicGo再继续操作
|
||||
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: 有插件正在试图删除一些相册图片,是否继续
|
||||
TIPS_PLUGIN_OVERWRITE_GALLERY: 有插件正在试图覆盖相册列表,是否继续
|
||||
TIPS_UPLOAD_NOT_PICTURES: 剪贴板最新的一条记录不是图片
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo 配置文件损坏,已经恢复为默认配置
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo 配置文件损坏,已经恢复为备份配置
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: '备份文件版本: ${v}'
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: "备份文件版本: ${v}"
|
||||
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: 自定义文件解析出错,请检查路径内容是否正确
|
||||
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷键已经修改成功
|
||||
TIPS_SHORTCUT_MODIFIED_CONFLICT: 快捷键冲突,请重新设置
|
||||
TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED: 自定义链接格式已经修改成功
|
||||
TIPS_FIND_NEW_VERSION: 发现新版本${v},更新了很多功能,是否去下载最新的版本?
|
||||
TIPS_DELETE_UPLOADER_CONFIG: 是否要删除这个配置?
|
||||
TIPS_COPY_UPLOADER_CONFIG: 是否要复制这个配置?
|
||||
|
||||
# privacy
|
||||
PRIVACY: >
|
||||
@@ -261,7 +332,6 @@ PRIVACY: >
|
||||
b)根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露;
|
||||
|
||||
|
||||
c)如您出现违反中国有关法律、法规或者相关规则的情况,需要向第三方披露;
|
||||
|
||||
|
||||
4.信息安全
|
||||
|
||||
+79
-9
@@ -56,6 +56,8 @@ BATCH_COPY_LINK_SUCCEED: 批量複製連結成功
|
||||
FILE_RENAME: 檔案改名
|
||||
COPY_FILE_PATH: 複製檔案路徑
|
||||
OPEN_FILE_PATH: 打開檔案路徑
|
||||
SUCCESS: 成功
|
||||
FAILED: 失敗
|
||||
|
||||
# settings
|
||||
|
||||
@@ -66,6 +68,7 @@ SETTINGS_SET_LOG_FILE: 設定記錄檔案
|
||||
SETTINGS_CLICK_TO_SET: 點擊設定
|
||||
SETTINGS_CLICK_TO_CHECK: 點擊檢查
|
||||
SETTINGS_SET_SHORTCUT: 設定快捷鍵
|
||||
SETTINGS_URL_REWRITE: URL 重寫
|
||||
SETTINGS_CUSTOM_LINK_FORMAT: 自訂連結格式
|
||||
SETTINGS_SET_PROXY_AND_MIRROR: 設定PROXY和鏡像地址
|
||||
SETTINGS_SET_SERVER: 設定Server
|
||||
@@ -78,6 +81,7 @@ SETTINGS_LAUNCH_ON_BOOT: 開機時啟動
|
||||
SETTINGS_RENAME_BEFORE_UPLOAD: 上傳前重新命名
|
||||
SETTINGS_TIMESTAMP_RENAME: 以時間戳命名
|
||||
SETTINGS_OPEN_UPLOAD_TIPS: 開啟上傳提示
|
||||
SETTINGS_NOTIFICATION_SOUND: 開啟通知提示音
|
||||
SETTINGS_MINI_WINDOW_ON_TOP: Mini視窗置頂
|
||||
SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD: 上傳後自動複製URL
|
||||
SETTINGS_TIPS_PLACEHOLDER_URL: 用佔位符 $url 來表示URL的位置
|
||||
@@ -115,10 +119,56 @@ SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD: 使用內建剪貼簿上傳
|
||||
SETTINGS_CHOOSE_LANGUAGE: 選擇語言
|
||||
BUILTIN_CLIPBOARD_TIPS: 使用內建剪貼簿函數而不是調用腳本取得剪貼簿內的照片
|
||||
UPLOADER_CONFIG_NAME: 圖床配置名
|
||||
|
||||
# url rewrite
|
||||
|
||||
URL_REWRITE_HELP: 用於重寫上傳後的圖片 URL。規則按順序匹配,命中的第一條匹配生效。
|
||||
URL_REWRITE_ADD_RULE: 新增規則
|
||||
URL_REWRITE_EDIT_RULE: 編輯規則
|
||||
URL_REWRITE_EMPTY: 暫無規則
|
||||
URL_REWRITE_ORDER: 順序
|
||||
URL_REWRITE_MATCH: 匹配
|
||||
URL_REWRITE_REPLACE: 替換
|
||||
URL_REWRITE_FLAGS: 標誌
|
||||
URL_REWRITE_ENABLED: 啟用
|
||||
URL_REWRITE_ACTIONS: 操作
|
||||
URL_REWRITE_MOVE_UP: 上移
|
||||
URL_REWRITE_MOVE_DOWN: 下移
|
||||
URL_REWRITE_EDIT: 編輯
|
||||
URL_REWRITE_DELETE: 刪除
|
||||
URL_REWRITE_DELETE_CONFIRM: 確定刪除該規則?
|
||||
URL_REWRITE_MATCH_TIPS: 支援正則(JavaScript RegExp)
|
||||
URL_REWRITE_MATCH_PLACEHOLDER: https://example.com/path
|
||||
URL_REWRITE_REPLACE_TIPS: 替換內容(支援 $1、$2...)
|
||||
URL_REWRITE_REPLACE_PLACEHOLDER: https://example.org/newpath
|
||||
URL_REWRITE_OPTIONS: 選項
|
||||
URL_REWRITE_RULE_ENABLED: 啟用該規則
|
||||
URL_REWRITE_FLAG_GLOBAL_LABEL: 全域(g)
|
||||
URL_REWRITE_FLAG_GLOBAL_DESC: 替換所有找到的內容,而不僅僅是第一個
|
||||
URL_REWRITE_FLAG_IGNORE_CASE_LABEL: 忽略大小寫(i)
|
||||
URL_REWRITE_FLAG_IGNORE_CASE_DESC: 匹配時不區分字母大小寫(例如 JPG 等同於 jpg)
|
||||
URL_REWRITE_MATCH_REQUIRED: 匹配規則不能為空
|
||||
URL_REWRITE_REPLACE_REQUIRED: 替換內容不能為空
|
||||
URL_REWRITE_INVALID_REGEX: 正則表達式不合法
|
||||
URL_REWRITE_PREVIEW_TITLE: 預覽
|
||||
URL_REWRITE_PREVIEW_TIPS: 輸入一個 URL,查看目前規則的重寫結果(按順序匹配,僅第一條命中的規則生效)
|
||||
URL_REWRITE_PREVIEW_PLACEHOLDER: https://example.com/path/to/image.png
|
||||
URL_REWRITE_PREVIEW_RUN: 預覽
|
||||
URL_REWRITE_PREVIEW_OUTPUT: 輸出 URL
|
||||
URL_REWRITE_PREVIEW_INPUT_REQUIRED: 請輸入要預覽的 URL
|
||||
URL_REWRITE_PREVIEW_RULE_INVALID: 規則無效
|
||||
URL_REWRITE_PREVIEW_MATCHED_RULE: 命中規則
|
||||
URL_REWRITE_PREVIEW_NO_MATCH: 沒有規則匹配
|
||||
UPLOADER_CONFIG_PLACEHOLDER: 請輸入配置名稱
|
||||
SELECTED_SETTING_HINT: 已選中
|
||||
SETTINGS_ENCODE_OUTPUT_URL: 輸出(複製) URL 時進行轉義
|
||||
SETTINGS_SHOW_DOCK_ICON: 顯示 Dock 欄圖示
|
||||
SETTINGS_SHOW_MENUBAR_ICON: 顯示頂部欄圖示
|
||||
SETTINGS_SHOW_MENUBAR_ICON_TIPS: 若「顯示 Dock 欄圖示」與「顯示頂部欄圖示」都關閉,將會無法找到 PicGo 主介面。需要手動修改配置檔案裡的 showDockIcon 或 showMenubarIcon 為 true 才能恢復。
|
||||
SETTINGS_STARTUP_MODE: 啟動模式
|
||||
SETTINGS_STARTUP_MODE_MAIN_WINDOW: 打開主視窗
|
||||
SETTINGS_STARTUP_MODE_MINI_WINDOW: 打開 Mini 視窗
|
||||
SETTINGS_STARTUP_MODE_HIDE: 靜默啟動
|
||||
|
||||
# shortcut-page
|
||||
|
||||
@@ -134,6 +184,22 @@ SHORTCUT_DISABLE: 禁用
|
||||
SHORTCUT_EDIT: 編輯
|
||||
SHORTCUT_CHANGE_UPLOAD: 修改上傳快捷鍵
|
||||
|
||||
# gallery-page
|
||||
GALLERY_URL_REWRITE_TITLE: 重寫選中圖片 URL
|
||||
GALLERY_URL_REWRITE_RESULT_TITLE: 重寫圖片 URL 結果
|
||||
GALLERY_URL_REWRITE_WARN_NO_SELECTION: 你必須先選中至少一張圖片
|
||||
|
||||
GALLERY_URL_REWRITE_APPLY_GLOBAL_RULES: 套用全域 URL 重寫規則
|
||||
GALLERY_URL_REWRITE_GLOBAL_RULES_COUNT: 全域規則數量
|
||||
GALLERY_URL_REWRITE_TEMP_RULE_TIPS: 可選,留空則不使用臨時規則(臨時規則優先級高於全域規則)。
|
||||
GALLERY_URL_REWRITE_TEMP_RULE_REQUIRED: 臨時規則需要同時填寫「匹配」和「替換」
|
||||
GALLERY_URL_REWRITE_NO_RULES_TO_APPLY: 沒有可用的全域規則,且未填寫臨時規則
|
||||
GALLERY_URL_REWRITE_SAVE_TEMP_RULE_PROMPT: 是否將臨時規則寫入全域 URL 重寫規則列表?
|
||||
GALLERY_URL_REWRITE_APPLY_AND_SAVE: 套用並寫入
|
||||
GALLERY_URL_REWRITE_APPLY_ONLY: 僅套用
|
||||
GALLERY_URL_REWRITE_NO_CHANGES: 沒有任何 URL 被修改
|
||||
GALLERY_URL_REWRITE_EMPTY_RESULT_WARN: 重寫結果為空,已跳過
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: 等待上傳
|
||||
@@ -149,7 +215,7 @@ CUSTOM: 自訂
|
||||
CLIPBOARD_PICTURE: 剪貼簿圖片
|
||||
TIPS_DRAG_VALID_PICTURE_OR_URL: 請拖入合法的圖片檔案或者圖片URL地址
|
||||
TIPS_INPUT_URL: 請輸入URL
|
||||
TIPS_HTTP_PREFIX: http://或者https://開頭
|
||||
TIPS_HTTP_PREFIX: http:// 或者 https:// 開頭,支援上傳多條 URL(請換行輸入)
|
||||
TIPS_INPUT_VALID_URL: 請輸入合法的URL
|
||||
|
||||
# plugins
|
||||
@@ -193,27 +259,27 @@ UPDATE_PLUGIN: 更新插件
|
||||
|
||||
# toolbox
|
||||
TOOLBOX: 工具箱
|
||||
TOOLBOX_TITLE: 排查 PicGo 執行時問題
|
||||
TOOLBOX_TITLE: 排查 PicGo 執行時問題
|
||||
TOOLBOX_SUB_TITLE: 立即掃描以下項目,修復使用問題
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN: 檢查配置文件是否損壞
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN: 檢查相冊文件是否損壞
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD: 檢查剪貼板圖片上傳是否存在問題
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_PROXY: 檢查代理設置是否正常
|
||||
TOOLBOX_FIX_DONE_NEED_RELOAD: 修復完成,需要重啓生效,是否重啓
|
||||
TOOLBOX_CANT_AUTO_FIX: 無法自動修復,請自行修復以下問題
|
||||
TOOLBOX_CANT_AUTO_FIX: 無法自動修復,請自行修復以下問題
|
||||
TOOLBOX_START_SCAN: 開始掃描
|
||||
TOOLBOX_RE_SCAN: 重新掃描
|
||||
TOOLBOX_START_FIX: 開始修復
|
||||
TOOLBOX_SUCCESS_TIPS: 恭喜你,沒有檢查出問題
|
||||
TOOLBOX_CHECK_CONFIG_FILE_PATH_TIPS: 配置文件路徑是:${path}
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN_TIPS: 配置文件已損壞
|
||||
TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS: 相冊文件路徑是:${path}
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN_TIPS: 配置文件已損壞
|
||||
TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS: 相冊文件路徑是:${path}
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN_TIPS: 相冊文件已損壞
|
||||
TOOLBOX_CHECK_PROXY_SUCCESS_TIPS: 代理設置正常
|
||||
TOOLBOX_CHECK_PROXY_SUCCESS_TIPS: 代理設置正常
|
||||
TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS: 無代理設置
|
||||
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT: 代理設置不正確
|
||||
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING: 代理設置不可用
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_TIPS: 剪貼板圖片臨時文件夾路徑是:${path}
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_TIPS: 剪貼板圖片臨時文件夾路徑是:${path}
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS: 剪貼板圖片臨時文件夾不存在:${path}
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: 請自行創建文件夾:${path}
|
||||
|
||||
@@ -221,18 +287,23 @@ TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: 請自行創建文件夾:${path}
|
||||
TIPS_NOTICE: 注意
|
||||
TIPS_WARNING: 警告
|
||||
TIPS_ERROR: 發生錯誤
|
||||
TIPS_SKIPPED_INVALID_URLS: 已跳過 ${n} 條非法 URL,請查看日誌了解詳情
|
||||
TIPS_TOO_MANY_URLS_CONFIRM: 你將一次上傳 ${n} 條 URL,可能會引起卡頓,建議分批上傳。是否繼續?
|
||||
TIPS_NO_VALID_URLS: 未偵測到合法的 URL
|
||||
TIPS_INSTALL_NODE_AND_RELOAD_PICGO: 請安裝Node.js並重新啟動PicGo再繼續操作
|
||||
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: 有插件正在試圖刪除一些相簿圖片,是否繼續?
|
||||
TIPS_PLUGIN_OVERWRITE_GALLERY: 有插件正在試圖覆蓋相簿列表,是否繼續?
|
||||
TIPS_UPLOAD_NOT_PICTURES: 剪貼簿最新的一條記錄不是圖片
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo 設定檔案已損壞,已經恢復為預設設定
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo 設定檔案已損壞,已經恢復為備份設定
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: '備份檔案版本: ${v}'
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: "備份檔案版本: ${v}"
|
||||
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: 自訂設定檔案解析出錯,請檢查路徑內容是否正確
|
||||
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷鍵已經修改成功
|
||||
TIPS_SHORTCUT_MODIFIED_CONFLICT: 快捷鍵衝突,請重新設定
|
||||
TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED: 自訂連結格式已經修改成功
|
||||
TIPS_FIND_NEW_VERSION: 發現新版本${v},更新了很多功能,是否去下載最新的版本?
|
||||
TIPS_DELETE_UPLOADER_CONFIG: 是否要刪除這個配置?
|
||||
TIPS_COPY_UPLOADER_CONFIG: 是否要複製這個配置?
|
||||
|
||||
# privacy
|
||||
PRIVACY: >
|
||||
@@ -261,7 +332,6 @@ PRIVACY: >
|
||||
b)根據法律的有關規定,或者行政或司法機構的要求,向第三方或者行政、司法機構披露;
|
||||
|
||||
|
||||
c)如您出現違反中國有關法律、法規或者相關規則的情況,需要向第三方披露;
|
||||
|
||||
|
||||
4.信息安全
|
||||
|
||||
+12
-12
@@ -17,18 +17,18 @@ if [ "$XDG_SESSION_TYPE" = "x11" ]; then
|
||||
echo $filePath
|
||||
fi
|
||||
elif [ "$XDG_SESSION_TYPE" = "wayland" ]; then
|
||||
command -v wl-copy >/dev/null 2>&1 || { echo >&1 "no wl-clipboard"; exit 1; }
|
||||
filePath=`wl-copy -o 2>/dev/null | grep ^file:// | cut -c8-`
|
||||
if [ ! -n "$filePath" ] ;then
|
||||
if
|
||||
wl-copy -t image/png -o >/dev/null 2>&1
|
||||
then
|
||||
wl-copy -t image/png image/png -o >$1 2>/dev/null
|
||||
echo $1
|
||||
else
|
||||
echo "no image"
|
||||
fi
|
||||
command -v wl-paste >/dev/null 2>&1 || { echo >&1 "no wl-clipboard"; exit 1; }
|
||||
isImage=`wl-paste --list-types | grep image`
|
||||
if [ -n "$isImage" ]; then
|
||||
wl-paste --type image/png > $1 2>/dev/null
|
||||
echo $1
|
||||
else
|
||||
echo $filePath
|
||||
echo "no image"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# fallback for unsupported session types
|
||||
echo >&2 "Error: Unsupported session type '$XDG_SESSION_TYPE'."
|
||||
echo >&2 "Solution: The variable of XDG_SESSION_TYPE must set as 'x11' or 'wayland'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+39
-23
@@ -1,45 +1,61 @@
|
||||
// different platform has different format
|
||||
|
||||
// macos
|
||||
// macos (dmg, x64 + arm64)
|
||||
const darwin = [{
|
||||
appNameWithPrefix: 'PicGo-',
|
||||
ext: '.dmg',
|
||||
arch: '-arm64',
|
||||
appNameWithPrefix: 'PicGo',
|
||||
ext: 'dmg',
|
||||
arch: 'arm64',
|
||||
'version-file': 'latest-mac.yml'
|
||||
}, {
|
||||
appNameWithPrefix: 'PicGo-',
|
||||
ext: '.dmg',
|
||||
arch: '-x64',
|
||||
appNameWithPrefix: 'PicGo',
|
||||
ext: 'dmg',
|
||||
arch: 'x64',
|
||||
'version-file': 'latest-mac.yml'
|
||||
}]
|
||||
|
||||
// linux (AppImage, deb, snap)
|
||||
const linux = [{
|
||||
appNameWithPrefix: 'PicGo-',
|
||||
ext: '.AppImage',
|
||||
arch: '',
|
||||
appNameWithPrefix: 'PicGo',
|
||||
ext: 'AppImage',
|
||||
arch: 'arm64',
|
||||
'version-file': 'latest-linux-arm64.yml'
|
||||
}, {
|
||||
appNameWithPrefix: 'PicGo',
|
||||
ext: 'AppImage',
|
||||
arch: 'x86_64',
|
||||
'version-file': 'latest-linux.yml'
|
||||
}, {
|
||||
appNameWithPrefix: 'picgo_',
|
||||
ext: '.snap',
|
||||
arch: '_amd64',
|
||||
appNameWithPrefix: 'PicGo',
|
||||
ext: 'deb',
|
||||
arch: 'arm64',
|
||||
'version-file': 'latest-linux-arm64.yml'
|
||||
}, {
|
||||
appNameWithPrefix: 'PicGo',
|
||||
ext: 'deb',
|
||||
arch: 'amd64',
|
||||
'version-file': 'latest-linux.yml'
|
||||
}, {
|
||||
appNameWithPrefix: 'PicGo',
|
||||
ext: 'snap',
|
||||
arch: 'amd64',
|
||||
'version-file': 'latest-linux.yml'
|
||||
}]
|
||||
|
||||
// windows
|
||||
// windows (nsis, x64 + ia32 + arm64)
|
||||
const win32 = [{
|
||||
appNameWithPrefix: 'PicGo-Setup-',
|
||||
ext: '.exe',
|
||||
arch: '-ia32',
|
||||
appNameWithPrefix: 'PicGo',
|
||||
ext: 'exe',
|
||||
arch: 'ia32',
|
||||
'version-file': 'latest.yml'
|
||||
}, {
|
||||
appNameWithPrefix: 'PicGo-Setup-',
|
||||
ext: '.exe',
|
||||
arch: '-x64',
|
||||
appNameWithPrefix: 'PicGo',
|
||||
ext: 'exe',
|
||||
arch: 'x64',
|
||||
'version-file': 'latest.yml'
|
||||
}, {
|
||||
appNameWithPrefix: 'PicGo-Setup-',
|
||||
ext: '.exe',
|
||||
arch: '', // 32 & 64
|
||||
appNameWithPrefix: 'PicGo',
|
||||
ext: 'exe',
|
||||
arch: 'arm64',
|
||||
'version-file': 'latest.yml'
|
||||
}]
|
||||
|
||||
|
||||
+33
-17
@@ -1,23 +1,39 @@
|
||||
const pkg = require('../package.json')
|
||||
const version = pkg.version
|
||||
// TODO: use the same name format
|
||||
const generateURL = (platform, ext, prefix = 'PicGo-') => {
|
||||
return `https://picgo-release.molunerfinn.com/${version}/${prefix}${version}${platform}${ext}`
|
||||
}
|
||||
|
||||
const platformExtList = [
|
||||
['-arm64', '.dmg', 'PicGo-'],
|
||||
['-x64', '.dmg', 'PicGo-'],
|
||||
['', '.AppImage', 'PicGo-'],
|
||||
['-ia32', '.exe', 'PicGo-Setup-'],
|
||||
['-x64', '.exe', 'PicGo-Setup-'],
|
||||
['', '.exe', 'PicGo-Setup-'],
|
||||
['_amd64', '.snap', 'picgo_']
|
||||
const generateURL = (arch, ext, prefix = 'PicGo-') => `https://release.picgo.app/${version}/${prefix}${version}${arch}${ext}`
|
||||
|
||||
const windows = [
|
||||
{ label: '32 bit', arch: '-ia32', ext: '.exe' },
|
||||
{ label: '64 bit', arch: '-x64', ext: '.exe' },
|
||||
{ label: 'ARM64', arch: '-arm64', ext: '.exe' }
|
||||
]
|
||||
|
||||
const links = platformExtList.map(([arch, ext, prefix]) => {
|
||||
const markdownLink = `[${prefix}${version}${arch}${ext}](${generateURL(arch, ext, prefix)})`
|
||||
return markdownLink
|
||||
})
|
||||
const macos = [
|
||||
{ label: 'Intel', arch: '-x64', ext: '.dmg' },
|
||||
{ label: 'Apple Silicon', arch: '-arm64', ext: '.dmg' }
|
||||
]
|
||||
|
||||
console.log(links.join('\n'))
|
||||
const linux = {
|
||||
AppImage: [
|
||||
{ label: '64 bit', arch: '-x64', ext: '.AppImage' },
|
||||
{ label: 'ARM64', arch: '-arm64', ext: '.AppImage' }
|
||||
],
|
||||
Deb: [
|
||||
{ label: '64 bit', arch: '-x64', ext: '.deb' },
|
||||
{ label: 'ARM64', arch: '-arm64', ext: '.deb' }
|
||||
],
|
||||
Snap: [
|
||||
{ label: '64 bit', arch: '-x64', ext: '.snap' }
|
||||
]
|
||||
}
|
||||
|
||||
const renderLine = (items) => items.map(({ label, arch, ext }) => `[${label}](${generateURL(arch, ext)})`).join(' | ')
|
||||
|
||||
const sections = [
|
||||
`### Windows\n- ${renderLine(windows)}`,
|
||||
`### macOS\n- ${renderLine(macos)}`,
|
||||
`### Linux\n- AppImage: ${renderLine(linux.AppImage)}\n- Deb: ${renderLine(linux.Deb)}\n- Snap: ${renderLine(linux.Snap)}`
|
||||
]
|
||||
|
||||
console.log(sections.join('\n\n'))
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
require('dotenv').config()
|
||||
|
||||
const { notarize } = require('@electron/notarize')
|
||||
const { APPLE_ID, APPLE_TEAM_ID, APPLE_APP_SPECIFIC_PASSWORD } = process.env
|
||||
const APP_BUNDLE_ID = 'com.molunerfinn.picgo'
|
||||
|
||||
async function main(context) {
|
||||
const { electronPlatformName, appOutDir, packager } = context
|
||||
|
||||
if (
|
||||
electronPlatformName !== 'darwin' ||
|
||||
!APPLE_ID ||
|
||||
!APPLE_APP_SPECIFIC_PASSWORD ||
|
||||
!APPLE_TEAM_ID
|
||||
) {
|
||||
console.log('Skip notarization.')
|
||||
return
|
||||
}
|
||||
|
||||
const appName = packager.appInfo.productFilename
|
||||
const appPath = `${appOutDir}/${appName}.app`
|
||||
|
||||
const now = Date.now()
|
||||
|
||||
console.log('Starting Apple notarization for', appPath)
|
||||
|
||||
await notarize({
|
||||
appPath,
|
||||
appBundleId: APP_BUNDLE_ID,
|
||||
appleId: APPLE_ID,
|
||||
appleIdPassword: APPLE_APP_SPECIFIC_PASSWORD,
|
||||
teamId: APPLE_TEAM_ID
|
||||
})
|
||||
|
||||
console.log('Finished Apple notarization for', appPath, `in ${(Date.now() - now) / 1000}s`)
|
||||
}
|
||||
|
||||
|
||||
module.exports = main
|
||||
+55
-22
@@ -9,20 +9,24 @@ const pkg = require('../package.json')
|
||||
const configList = require('./config')
|
||||
const mime = require('mime-types')
|
||||
const path = require('path')
|
||||
const distPath = path.join(__dirname, '../dist_electron')
|
||||
const distPath = path.join(__dirname, '../dist')
|
||||
const S3Client = require('@aws-sdk/client-s3').S3Client
|
||||
const Upload = require('@aws-sdk/lib-storage').Upload
|
||||
// const BUCKET = 'picgo-1251750343'
|
||||
// const COS_SECRET_ID = process.env.PICGO_ENV_COS_SECRET_ID
|
||||
// const COS_SECRET_KEY = process.env.PICGO_ENV_COS_SECRET_KEY
|
||||
|
||||
const S3_BUCKET = 'picgo'
|
||||
const S3_BUCKET = 'release'
|
||||
const S3_LEGACY_BUCKET = 'picgo'
|
||||
// const AREA = 'ap-chengdu'
|
||||
const VERSION = pkg.version
|
||||
const FILE_PATH = `${VERSION}/`
|
||||
const S3_SECRET_ID = process.env.PICGO_ENV_S3_SECRET_ID
|
||||
const S3_SECRET_KEY = process.env.PICGO_ENV_S3_SECRET_KEY
|
||||
const S3_ACCOUNT_ID = process.env.PICGO_ENV_S3_ACCOUNT_ID
|
||||
const S3_LEGACY_SECRET_ID = process.env.PICGO_ENV_S3_LEGACY_SECRET_ID
|
||||
const S3_LEGACY_SECRET_KEY = process.env.PICGO_ENV_S3_LEGACY_SECRET_KEY
|
||||
const S3_LEGACY_ACCOUNT_ID = process.env.PICGO_ENV_S3_LEGACY_ACCOUNT_ID
|
||||
|
||||
const S3Options = {
|
||||
credentials: {
|
||||
@@ -34,6 +38,17 @@ const S3Options = {
|
||||
region: 'auto'
|
||||
}
|
||||
|
||||
// for legacy release file fetch
|
||||
const S3LegacyOptions = {
|
||||
credentials: {
|
||||
accessKeyId: S3_LEGACY_SECRET_ID,
|
||||
secretAccessKey: S3_LEGACY_SECRET_KEY
|
||||
},
|
||||
endpoint: `https://${S3_LEGACY_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
||||
sslEnabled: true,
|
||||
region: 'auto'
|
||||
}
|
||||
|
||||
// https://cloud.tencent.com/document/product/436/7778#signature
|
||||
// /**
|
||||
// * @param {string} fileName
|
||||
@@ -117,35 +132,39 @@ const uploadDist = async () => {
|
||||
try {
|
||||
const platform = process.platform
|
||||
if (configList[platform]) {
|
||||
let versionFileHasUploaded = false
|
||||
const uploadedVersionFiles = new Set()
|
||||
for (const [index, config] of configList[platform].entries()) {
|
||||
const fileName = `${config.appNameWithPrefix}${VERSION}${config.arch}${config.ext}`
|
||||
const fileName = `${config.appNameWithPrefix}-${VERSION}-${config.arch}.${config.ext}`
|
||||
const filePath = path.join(distPath, fileName)
|
||||
const versionFilePath = path.join(distPath, config['version-file'])
|
||||
let versionFileName = config['version-file']
|
||||
if (VERSION.toLocaleLowerCase().includes('beta')) {
|
||||
versionFileName = versionFileName.replace('.yml', '.beta.yml')
|
||||
}
|
||||
|
||||
console.log('[PicGo Dist] Preparing to upload', fileName)
|
||||
const client = new S3Client(S3Options)
|
||||
const uploadDistToS3 = new Upload({
|
||||
client,
|
||||
params: {
|
||||
Bucket: S3_BUCKET,
|
||||
Key: `${FILE_PATH}${fileName}`,
|
||||
Body: fs.createReadStream(filePath),
|
||||
ContentType: 'application/octet-stream'
|
||||
}
|
||||
})
|
||||
// upload dist file
|
||||
console.log('[PicGo Dist] Uploading...', fileName, `${index + 1}/${configList[platform].length}`)
|
||||
uploadDistToS3.on('httpUploadProgress', progress => {
|
||||
console.log(`[PicGo Dist] Uploading... ${progress.loaded}/${progress.total}`)
|
||||
})
|
||||
await uploadDistToS3.done()
|
||||
if (fs.existsSync(filePath)) {
|
||||
const uploadDistToS3 = new Upload({
|
||||
client,
|
||||
params: {
|
||||
Bucket: S3_BUCKET,
|
||||
Key: `${FILE_PATH}${fileName}`,
|
||||
Body: fs.createReadStream(filePath),
|
||||
ContentType: 'application/octet-stream'
|
||||
}
|
||||
})
|
||||
// upload dist file
|
||||
console.log('[PicGo Dist] Uploading...', fileName, `${index + 1}/${configList[platform].length}`)
|
||||
uploadDistToS3.on('httpUploadProgress', progress => {
|
||||
console.log(`[PicGo Dist] Uploading... ${progress.loaded}/${progress.total}`)
|
||||
})
|
||||
await uploadDistToS3.done()
|
||||
} else {
|
||||
console.warn('[PicGo Dist] File not found:', fileName)
|
||||
}
|
||||
|
||||
// upload version file
|
||||
if (!versionFileHasUploaded) {
|
||||
if (!uploadedVersionFiles.has(versionFileName) && fs.existsSync(versionFilePath)) {
|
||||
const uploadVersionFileToS3 = new Upload({
|
||||
client,
|
||||
params: {
|
||||
@@ -155,9 +174,23 @@ const uploadDist = async () => {
|
||||
ContentType: mime.lookup(versionFileName)
|
||||
}
|
||||
})
|
||||
// upload to legacy bucket as well
|
||||
// will be deprecated in 2.5.0
|
||||
const legacyClient = new S3Client(S3LegacyOptions)
|
||||
const uploadVersionFileToLegacyS3 = new Upload({
|
||||
client: legacyClient,
|
||||
params: {
|
||||
Bucket: S3_LEGACY_BUCKET,
|
||||
Key: `${versionFileName}`,
|
||||
Body: fs.createReadStream(versionFilePath),
|
||||
ContentType: mime.lookup(versionFileName)
|
||||
}
|
||||
})
|
||||
console.log('[PicGo Version File] Uploading...', versionFileName)
|
||||
await uploadVersionFileToS3.done()
|
||||
versionFileHasUploaded = true
|
||||
await uploadVersionFileToLegacyS3.done()
|
||||
uploadedVersionFiles.add(versionFileName)
|
||||
console.log('[PicGo Version File] Upload successfully')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { extractHttpUrlsFromText, parseNewlineSeparatedUrls } from '../../../universal/utils/common'
|
||||
|
||||
describe('universal/utils/common', () => {
|
||||
describe('parseNewlineSeparatedUrls', () => {
|
||||
it('parses newline-separated urls, trims lines, ignores empty lines, and de-duplicates', () => {
|
||||
const input = [
|
||||
'https://a.example/1.png',
|
||||
'',
|
||||
' https://a.example/2.png ',
|
||||
'https://a.example/1.png',
|
||||
'not-a-url'
|
||||
].join('\n')
|
||||
|
||||
const { urls, invalidLines } = parseNewlineSeparatedUrls(input)
|
||||
|
||||
expect(urls).toEqual([
|
||||
'https://a.example/1.png',
|
||||
'https://a.example/2.png'
|
||||
])
|
||||
expect(invalidLines).toEqual(['not-a-url'])
|
||||
})
|
||||
|
||||
it('ignores uri-list comment lines when source is uri-list', () => {
|
||||
const input = [
|
||||
'# comment',
|
||||
'https://a.example/1.png',
|
||||
'# another comment',
|
||||
'https://a.example/2.png'
|
||||
].join('\n')
|
||||
|
||||
const { urls, invalidLines } = parseNewlineSeparatedUrls(input, { source: 'uri-list' })
|
||||
|
||||
expect(urls).toEqual([
|
||||
'https://a.example/1.png',
|
||||
'https://a.example/2.png'
|
||||
])
|
||||
expect(invalidLines).toEqual([])
|
||||
})
|
||||
|
||||
it('supports NUL-separated drag payloads by normalizing to newlines', () => {
|
||||
const input = `https://a.example/1.png\u0000https://a.example/2.png`
|
||||
const { urls, invalidLines } = parseNewlineSeparatedUrls(input)
|
||||
|
||||
expect(urls).toEqual([
|
||||
'https://a.example/1.png',
|
||||
'https://a.example/2.png'
|
||||
])
|
||||
expect(invalidLines).toEqual([])
|
||||
})
|
||||
|
||||
it('splits concatenated urls in a single line as a fallback', () => {
|
||||
const input = 'https://a.example/1.pnghttps://b.example/2.webphttps://c.example/3.png'
|
||||
const { urls, invalidLines } = parseNewlineSeparatedUrls(input)
|
||||
|
||||
expect(urls).toEqual([
|
||||
'https://a.example/1.png',
|
||||
'https://b.example/2.webp',
|
||||
'https://c.example/3.png'
|
||||
])
|
||||
expect(invalidLines).toEqual([])
|
||||
})
|
||||
|
||||
it('does not split embedded urls in query parameters', () => {
|
||||
const input = 'https://example.com/?url=https://a.example/1.png'
|
||||
const { urls, invalidLines } = parseNewlineSeparatedUrls(input)
|
||||
|
||||
expect(urls).toEqual(['https://example.com/?url=https://a.example/1.png'])
|
||||
expect(invalidLines).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('extractHttpUrlsFromText', () => {
|
||||
it('extracts urls from mixed text, strips trailing punctuation, and de-duplicates', () => {
|
||||
const input = [
|
||||
'hello',
|
||||
'https://a.example/1.png)',
|
||||
'https://b.example/2.webp]',
|
||||
'https://a.example/1.png'
|
||||
].join(' ')
|
||||
|
||||
expect(extractHttpUrlsFromText(input)).toEqual([
|
||||
'https://a.example/1.png',
|
||||
'https://b.example/2.webp'
|
||||
])
|
||||
})
|
||||
|
||||
it('returns empty array when no urls are present', () => {
|
||||
expect(extractHttpUrlsFromText('no urls here')).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { initStaticPath } from '~/main/utils/env'
|
||||
import { bootstrap } from '~/main/lifeCycle'
|
||||
|
||||
initStaticPath()
|
||||
bootstrap.launchApp()
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,14 +10,13 @@ import axios from 'axios'
|
||||
import windowManager from '../window/windowManager'
|
||||
import { showNotification } from '~/main/utils/common'
|
||||
import { isDev } from '~/universal/utils/common'
|
||||
import { STORE_PATH } from '~/main/utils/env'
|
||||
|
||||
// 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_URL = isDev ? 'http://localhost:8181/remote-notice.json' : 'https://release.picgo.app/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 {
|
||||
@@ -117,7 +116,7 @@ class RemoteNoticeHandler {
|
||||
body: action.data?.content || '',
|
||||
clickToCopy: !!action.data?.copyToClipboard,
|
||||
copyContent: action.data?.copyToClipboard || '',
|
||||
clickFn () {
|
||||
callback () {
|
||||
if (action.data?.url) {
|
||||
shell.openExternal(action.data.url)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { SHORTKEY_COMMAND_UPLOAD } from '../../core/bus/constants'
|
||||
import { uploadClipboardFiles } from '../uploader/apis'
|
||||
|
||||
export const BuiltinShortKeyMap: Record<string, FN> = {
|
||||
[SHORTKEY_COMMAND_UPLOAD]: uploadClipboardFiles
|
||||
}
|
||||
@@ -4,10 +4,11 @@ import {
|
||||
} from 'electron'
|
||||
import logger from '@core/picgo/logger'
|
||||
import GuiApi from '../../gui'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import shortKeyService from './shortKeyService'
|
||||
import picgo from '@core/picgo'
|
||||
import { BuiltinShortKeyMap } from './builtin'
|
||||
import { SHORTKEY_BUILTIN_PREFIX } from '../../core/bus/constants'
|
||||
|
||||
class ShortKeyHandler {
|
||||
private isInModifiedMode: boolean = false
|
||||
@@ -23,16 +24,20 @@ class ShortKeyHandler {
|
||||
}
|
||||
|
||||
private initBuiltInShortKey () {
|
||||
const commands = db.get('settings.shortKey') as IShortKeyConfigs
|
||||
const commands = picgo.getConfig<IShortKeyConfigs>('settings.shortKey') || {}
|
||||
Object.keys(commands)
|
||||
.filter(item => item.includes('picgo:'))
|
||||
.forEach(command => {
|
||||
const config = commands[command]
|
||||
// if disabled, don't register #534
|
||||
logger.info(`register builtin shortKey command: [${command}] - [${config.key}]`)
|
||||
if (config.enable) {
|
||||
logger.info(`register builtin shortKey command: [${command}] - [${config.key}] successfully`)
|
||||
globalShortcut.register(config.key, () => {
|
||||
this.handler(command)
|
||||
})
|
||||
} else {
|
||||
logger.warn(`builtin shortKey command: [${command}] - [${config.key}] register failed, it's disabled`)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -51,8 +56,8 @@ class ShortKeyHandler {
|
||||
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
|
||||
for (const cmd of commands) {
|
||||
const command = `${item}:${cmd.name}`
|
||||
if (db.has(`settings.shortKey[${command}]`)) {
|
||||
const commandConfig = db.get(`settings.shortKey.${command}`) as IShortKeyConfig
|
||||
const commandConfig = picgo.getConfig<IShortKeyConfig | undefined>(`settings.shortKey.${command}`)
|
||||
if (commandConfig) {
|
||||
// if disabled, don't register #534
|
||||
if (commandConfig.enable) {
|
||||
this.registerShortKey(commandConfig, command, cmd.handle, false)
|
||||
@@ -76,7 +81,7 @@ class ShortKeyHandler {
|
||||
} else {
|
||||
logger.warn(`${command} do not provide a key to bind`)
|
||||
}
|
||||
// if the configfile already had this command
|
||||
// if the config file already had this command
|
||||
// then writeFlag -> false
|
||||
if (writeFlag) {
|
||||
picgo.saveConfig({
|
||||
@@ -130,17 +135,25 @@ class ShortKeyHandler {
|
||||
|
||||
private async handler (command: string) {
|
||||
if (this.isInModifiedMode) {
|
||||
logger.warn(`in modified mode, ignore shortKey command: [${command}]`)
|
||||
return
|
||||
}
|
||||
if (command.includes('picgo:')) {
|
||||
bus.emit(command)
|
||||
if (command.includes(SHORTKEY_BUILTIN_PREFIX)) {
|
||||
const handler = BuiltinShortKeyMap[command]
|
||||
if (handler) {
|
||||
logger.info(`get builtin shortKey handler for command: [${command}]`)
|
||||
return handler()
|
||||
} else {
|
||||
logger.warn(`can't find builtin shortKey handler for command: [${command}]`)
|
||||
}
|
||||
} else if (command.includes('picgo-plugin-')) {
|
||||
const handler = shortKeyService.getShortKeyHandler(command)
|
||||
if (handler) {
|
||||
logger.info(`get plugin shortKey handler for command: [${command}]`)
|
||||
return handler(picgo, GuiApi.getInstance())
|
||||
}
|
||||
} else {
|
||||
logger.warn(`can not find command: ${command}`)
|
||||
logger.warn(`can not find command: [${command}]`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,8 +167,8 @@ class ShortKeyHandler {
|
||||
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
|
||||
for (const cmd of commands) {
|
||||
const command = `${pluginName}:${cmd.name}`
|
||||
if (db.has(`settings.shortKey[${command}]`)) {
|
||||
const commandConfig = db.get(`settings.shortKey[${command}]`) as IShortKeyConfig
|
||||
const commandConfig = picgo.getConfig<IShortKeyConfig | undefined>(`settings.shortKey.${command}`)
|
||||
if (commandConfig) {
|
||||
this.registerShortKey(commandConfig, command, cmd.handle, false)
|
||||
} else {
|
||||
this.registerShortKey(cmd, command, cmd.handle, true)
|
||||
@@ -165,7 +178,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
|
||||
unregisterPluginShortKey (pluginName: string) {
|
||||
const commands = db.get('settings.shortKey') as IShortKeyConfigs
|
||||
const commands = picgo.getConfig<IShortKeyConfigs>('settings.shortKey') || {}
|
||||
const keyList = Object.keys(commands)
|
||||
.filter(command => command.includes(pluginName))
|
||||
.map(command => {
|
||||
@@ -177,7 +190,7 @@ class ShortKeyHandler {
|
||||
keyList.forEach(item => {
|
||||
globalShortcut.unregister(item.key)
|
||||
shortKeyService.unregisterCommand(item.command)
|
||||
db.unset('settings.shortKey', item.command)
|
||||
picgo.removeConfig('settings.shortKey', item.command)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import fs from 'fs-extra'
|
||||
import {
|
||||
app,
|
||||
Menu,
|
||||
Tray,
|
||||
dialog,
|
||||
clipboard,
|
||||
Notification,
|
||||
nativeTheme
|
||||
} from 'electron'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { IPasteStyle, IWindowList } from '#/types/enum'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import pkg from 'root/package.json'
|
||||
import { ensureFilePath, handleCopyUrl } from '~/main/utils/common'
|
||||
import { ensureFilePath, getClipboardFilePathList, handleCopyUrl, showNotification } from '~/main/utils/common'
|
||||
import { privacyManager } from '~/main/utils/privacyManager'
|
||||
// import { T } from '#/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
import { isMacOSVersionGreaterThanOrEqualTo } from '~/main/utils/getMacOSVersion'
|
||||
import { buildPicBedListMenu } from '~/main/events/remotes/menu'
|
||||
import { buildPicBedListMenu } from '~/main/events/remotes/picBedListMenu'
|
||||
import { isLinux, isMacOS } from '~/universal/utils/common'
|
||||
import { getStaticPath } from '#/utils/staticPath'
|
||||
let contextMenu: Menu | null
|
||||
let menu: Menu | null
|
||||
let tray: Tray | null
|
||||
@@ -59,10 +59,10 @@ export function createContextMenu () {
|
||||
{
|
||||
label: T('OPEN_UPDATE_HELPER'),
|
||||
type: 'checkbox',
|
||||
checked: db.get('settings.showUpdateTip'),
|
||||
checked: picgo.getConfig<boolean>('settings.showUpdateTip') !== false,
|
||||
click () {
|
||||
const value = db.get('settings.showUpdateTip')
|
||||
db.set('settings.showUpdateTip', !value)
|
||||
const value = picgo.getConfig<boolean>('settings.showUpdateTip') !== false
|
||||
picgo.saveConfig({ 'settings.showUpdateTip': !value })
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -108,10 +108,10 @@ export function createContextMenu () {
|
||||
{
|
||||
label: T('OPEN_UPDATE_HELPER'),
|
||||
type: 'checkbox',
|
||||
checked: db.get('settings.showUpdateTip'),
|
||||
checked: picgo.getConfig<boolean>('settings.showUpdateTip') !== false,
|
||||
click () {
|
||||
const value = db.get('settings.showUpdateTip')
|
||||
db.set('settings.showUpdateTip', !value)
|
||||
const value = picgo.getConfig<boolean>('settings.showUpdateTip') !== false
|
||||
picgo.saveConfig({ 'settings.showUpdateTip': !value })
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -138,9 +138,11 @@ export function createContextMenu () {
|
||||
const getTrayIcon = () => {
|
||||
if (process.platform === 'darwin') {
|
||||
const isMacOSGreaterThan11 = isMacOSVersionGreaterThanOrEqualTo('11')
|
||||
return isMacOSGreaterThan11 ? `${__static}/menubar-newdarwinTemplate.png` : `${__static}/menubar.png`
|
||||
return isMacOSGreaterThan11
|
||||
? getStaticPath('menubar-newdarwinTemplate.png')
|
||||
: getStaticPath('menubar.png')
|
||||
} else {
|
||||
return `${__static}/menubar-nodarwin.png`
|
||||
return getStaticPath('menubar-nodarwin.png')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,36 +156,27 @@ export function createTray () {
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
|
||||
}
|
||||
createContextMenu()
|
||||
tray!.popUpContextMenu(contextMenu!)
|
||||
setTimeout(() => {
|
||||
tray!.popUpContextMenu(contextMenu!)
|
||||
}, 0)
|
||||
})
|
||||
tray.on('click', (event, bounds) => {
|
||||
if (process.platform === 'darwin') {
|
||||
toggleWindow(bounds)
|
||||
setTimeout(async () => {
|
||||
const img = clipboard.readImage()
|
||||
const obj: ImgInfo[] = []
|
||||
if (!img.isEmpty()) {
|
||||
// 从剪贴板来的图片默认转为png
|
||||
// https://github.com/electron/electron/issues/9035
|
||||
const imgPath = clipboard.read('public.file-url')
|
||||
if (imgPath) {
|
||||
const imgPathList = getClipboardFilePathList()
|
||||
if (imgPathList.length > 0) {
|
||||
for (const imgPath of imgPathList) {
|
||||
const decodePath = ensureFilePath(imgPath)
|
||||
if (decodePath === imgPath) {
|
||||
obj.push({
|
||||
imgUrl: imgPath
|
||||
})
|
||||
} else {
|
||||
if (decodePath !== '') {
|
||||
// 带有中文的路径,无法直接被img.src所使用,会被转义
|
||||
const base64 = await fs.readFile(decodePath.replace('file://', ''), { encoding: 'base64' })
|
||||
obj.push({
|
||||
imgUrl: `data:image/png;base64,${base64}`
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
obj.push({
|
||||
imgUrl: decodePath
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const img = clipboard.readImage()
|
||||
if (!img.isEmpty()) {
|
||||
const imgUrl = img.toDataURL()
|
||||
// console.log(imgUrl)
|
||||
obj.push({
|
||||
width: img.getSize().width,
|
||||
height: img.getSize().height,
|
||||
@@ -191,7 +184,9 @@ export function createTray () {
|
||||
})
|
||||
}
|
||||
}
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('clipboardFiles', obj)
|
||||
windowManager
|
||||
.get(IWindowList.TRAY_WINDOW)!
|
||||
.webContents.send('clipboardFiles', obj)
|
||||
}, 0)
|
||||
} else {
|
||||
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
@@ -208,9 +203,9 @@ export function createTray () {
|
||||
|
||||
tray.on('drag-enter', () => {
|
||||
if (nativeTheme.shouldUseDarkColors) {
|
||||
tray!.setImage(`${__static}/upload-dark.png`)
|
||||
tray!.setImage(getStaticPath('upload-dark.png'))
|
||||
} else {
|
||||
tray!.setImage(`${__static}/upload.png`)
|
||||
tray!.setImage(getStaticPath('upload.png'))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -221,8 +216,8 @@ export function createTray () {
|
||||
|
||||
// drop-files only be supported in macOS
|
||||
// so the tray window must be available
|
||||
tray.on('drop-files', async (event: Event, files: string[]) => {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
tray.on('drop-files', async (event, files: string[]) => {
|
||||
const pasteStyle = picgo.getConfig<IPasteStyle>('settings.pasteStyle') || 'markdown'
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
|
||||
const imgs = await uploader
|
||||
.setWebContents(trayWindow.webContents)
|
||||
@@ -230,14 +225,15 @@ export function createTray () {
|
||||
if (imgs !== false) {
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: imgs[i].imgUrl!
|
||||
// icon: files[i]
|
||||
})
|
||||
pasteText.push(
|
||||
pasteTemplate(pasteStyle, imgs[i], picgo.getConfig<string>('settings.customLink'))
|
||||
)
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
showNotification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: imgs[i].imgUrl!
|
||||
// icon: files[i]
|
||||
})
|
||||
}, i * 100)
|
||||
await GalleryDB.getInstance().insert(imgs[i])
|
||||
}
|
||||
@@ -246,21 +242,53 @@ export function createTray () {
|
||||
}
|
||||
})
|
||||
// toggleWindow()
|
||||
} else if (process.platform === 'linux') {
|
||||
// click事件在Ubuntu上无法触发,Unity不支持(在Mac和Windows上可以触发)
|
||||
// 需要使用 setContextMenu 设置菜单
|
||||
} else if (isLinux) {
|
||||
// click事件在Ubuntu上无法触发,Unity不支持(在Mac和Windows上可以触发)
|
||||
// 需要使用 setContextMenu 设置菜单
|
||||
createContextMenu()
|
||||
tray!.setContextMenu(contextMenu)
|
||||
}
|
||||
}
|
||||
|
||||
const destroyTray = () => {
|
||||
if (tray) {
|
||||
tray.removeAllListeners()
|
||||
tray.destroy()
|
||||
tray = null
|
||||
}
|
||||
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS only: show/hide the menubar (tray) icon.
|
||||
* For other platforms this keeps the existing behavior (always show tray).
|
||||
*/
|
||||
export function handleMenubarIcon (visible?: boolean) {
|
||||
if (!isMacOS) {
|
||||
if (!tray) {
|
||||
createTray()
|
||||
}
|
||||
return
|
||||
}
|
||||
const shouldShow = visible !== undefined ? visible : (picgo.getConfig<boolean>('settings.showMenubarIcon') !== false)
|
||||
if (shouldShow) {
|
||||
if (!tray) {
|
||||
createTray()
|
||||
}
|
||||
} else {
|
||||
destroyTray()
|
||||
}
|
||||
}
|
||||
|
||||
export function handleDockIcon () {
|
||||
if (process.platform === 'darwin') {
|
||||
if (db.get('settings.showDockIcon') !== false) {
|
||||
app.dock.show()
|
||||
app.dock.setMenu(createContextMenu())
|
||||
if (isMacOS) {
|
||||
if (picgo.getConfig<boolean>('settings.showDockIcon') !== false) {
|
||||
app.dock?.show()
|
||||
app.dock?.setMenu(createContextMenu())
|
||||
} else {
|
||||
app.dock.hide()
|
||||
app.dock?.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,25 +298,35 @@ export function createMenu () {
|
||||
return menu
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
const template = [{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
|
||||
{ label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
|
||||
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
|
||||
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
|
||||
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' },
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'CmdOrCtrl+Q',
|
||||
click () {
|
||||
app.quit()
|
||||
const template = [
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
|
||||
{
|
||||
label: 'Redo',
|
||||
accelerator: 'Shift+CmdOrCtrl+Z',
|
||||
selector: 'redo:'
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
|
||||
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
|
||||
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
|
||||
{
|
||||
label: 'Select All',
|
||||
accelerator: 'CmdOrCtrl+A',
|
||||
selector: 'selectAll:'
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'CmdOrCtrl+Q',
|
||||
click () {
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
]
|
||||
// @ts-ignore
|
||||
menu = Menu.buildFromTemplate(template)
|
||||
Menu.setApplicationMenu(menu)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import {
|
||||
Notification,
|
||||
WebContents
|
||||
} from 'electron'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { IPasteStyle, IRPCActionType, IWindowList } from '#/types/enum'
|
||||
import uploader from '.'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
import { handleUrlEncode } from '#/utils/common'
|
||||
import { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import { handleCopyUrl, handleUrlEncodeWithSetting, showNotification } from '~/main/utils/common'
|
||||
import { T } from '~/main/i18n/index'
|
||||
import logger from '@core/picgo/logger'
|
||||
import picgo from '@core/picgo'
|
||||
// import dayjs from 'dayjs'
|
||||
|
||||
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
const useBuiltinClipboard = !!db.get('settings.useBuiltinClipboard')
|
||||
const useBuiltinClipboard = !!picgo.getConfig<boolean>('settings.useBuiltinClipboard')
|
||||
const win = windowManager.getAvailableWindow()
|
||||
if (useBuiltinClipboard) {
|
||||
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard()
|
||||
@@ -22,35 +22,33 @@ const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
}
|
||||
|
||||
export const uploadClipboardFiles = async (): Promise<string> => {
|
||||
logger.info('upload clipboard file')
|
||||
const img = await handleClipboardUploading()
|
||||
console.log(img)
|
||||
if (img !== false) {
|
||||
if (img.length > 0) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: img[0].imgUrl!
|
||||
// icon: img[0].imgUrl
|
||||
})
|
||||
const pasteStyle = picgo.getConfig<IPasteStyle>('settings.pasteStyle') || 'markdown'
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0], picgo.getConfig<string>('settings.customLink')))
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
showNotification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: img[0].imgUrl!
|
||||
// icon: img[0].imgUrl
|
||||
})
|
||||
}, 100)
|
||||
await GalleryDB.getInstance().insert(img[0])
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', img)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
return handleUrlEncode(img[0].imgUrl as string)
|
||||
return handleUrlEncodeWithSetting(img[0].imgUrl as string)
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
showNotification({
|
||||
title: T('UPLOAD_FAILED'),
|
||||
body: T('TIPS_UPLOAD_NOT_PICTURES')
|
||||
})
|
||||
notification.show()
|
||||
return ''
|
||||
}
|
||||
} else {
|
||||
@@ -58,31 +56,30 @@ export const uploadClipboardFiles = async (): Promise<string> => {
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadChoosedFiles = async (webContents: WebContents, files: IFileWithPath[]): Promise<string[]> => {
|
||||
export const uploadSelectedFiles = async (webContents: WebContents, files: IFileWithPath[]): Promise<string[]> => {
|
||||
const input = files.map(item => item.path)
|
||||
const imgs = await uploader.setWebContents(webContents).upload(input)
|
||||
const result = []
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
const pasteStyle = picgo.getConfig<IPasteStyle>('settings.pasteStyle') || 'markdown'
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: imgs[i].imgUrl!
|
||||
// icon: files[i].path
|
||||
})
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], picgo.getConfig<string>('settings.customLink')))
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
showNotification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: imgs[i].imgUrl!
|
||||
// icon: files[i].path
|
||||
})
|
||||
}, i * 100)
|
||||
await GalleryDB.getInstance().insert(imgs[i])
|
||||
result.push(handleUrlEncode(imgs[i].imgUrl!))
|
||||
result.push(handleUrlEncodeWithSetting(imgs[i].imgUrl!))
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)?.webContents?.send('uploadFiles', imgs)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
Notification,
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
WebContents,
|
||||
@@ -7,13 +6,12 @@ import {
|
||||
} from 'electron'
|
||||
import dayjs from 'dayjs'
|
||||
import picgo from '@core/picgo'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import util from 'util'
|
||||
import { IPicGo } from 'picgo'
|
||||
import { showNotification, calcDurationRange, getClipboardFilePath } from '~/main/utils/common'
|
||||
import { RENAME_FILE_NAME, TALKING_DATA_EVENT } from '~/universal/events/constants'
|
||||
import type { IPicGo } from 'picgo'
|
||||
import { showNotification, getClipboardFilePathList } from '~/main/utils/common'
|
||||
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME } from '~/universal/events/constants'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { T } from '~/main/i18n'
|
||||
import fse from 'fs-extra'
|
||||
@@ -21,19 +19,14 @@ import path from 'path'
|
||||
import { privacyManager } from '~/main/utils/privacyManager'
|
||||
import writeFile from 'write-file-atomic'
|
||||
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static'
|
||||
|
||||
const waitForShow = (webcontent: WebContents) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
webcontent.on('did-finish-load', () => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
import { cleanupFormUploaderFiles } from '~/main/utils/cleanupFormUploaderFiles'
|
||||
import { IpcMainEvent } from 'electron/main'
|
||||
import { dataReportManager } from '~/main/utils/dataReport'
|
||||
|
||||
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
|
||||
return new Promise((resolve) => {
|
||||
const windowId = window.id
|
||||
ipcMain.once(`${RENAME_FILE_NAME}${id}`, (evt: Event, newName: string) => {
|
||||
ipcMain.once(`${RENAME_FILE_NAME}${id}`, (evt: IpcMainEvent, newName: string) => {
|
||||
resolve(newName)
|
||||
window.close()
|
||||
})
|
||||
@@ -45,20 +38,6 @@ const waitForRename = (window: BrowserWindow, id: number): Promise<string|null>
|
||||
})
|
||||
}
|
||||
|
||||
const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) => {
|
||||
const data: ITalkingDataOptions = {
|
||||
EventId: 'upload',
|
||||
Label: options.type,
|
||||
MapKv: {
|
||||
by: options.fromClipboard ? 'clipboard' : 'files', // 上传剪贴板图片还是选择的文文件
|
||||
count: options.count, // 上传的数量
|
||||
duration: calcDurationRange(options.duration || 0), // 上传耗时
|
||||
type: options.type
|
||||
}
|
||||
}
|
||||
webContents.send(TALKING_DATA_EVENT, data)
|
||||
}
|
||||
|
||||
class Uploader {
|
||||
private webContents: WebContents | null = null
|
||||
// private uploading: boolean = false
|
||||
@@ -67,40 +46,44 @@ class Uploader {
|
||||
}
|
||||
|
||||
init () {
|
||||
picgo.on('notification', (message: Electron.NotificationConstructorOptions | undefined) => {
|
||||
const notification = new Notification(message)
|
||||
notification.show()
|
||||
picgo.on('notification', (message: IShowNotificationOption | undefined) => {
|
||||
if (!message) return
|
||||
showNotification(message)
|
||||
})
|
||||
|
||||
picgo.on('uploadProgress', (progress: any) => {
|
||||
this.webContents?.send('uploadProgress', progress)
|
||||
})
|
||||
picgo.on('beforeTransform', () => {
|
||||
if (db.get('settings.uploadNotification')) {
|
||||
const notification = new Notification({
|
||||
if (picgo.getConfig<boolean>('settings.uploadNotification')) {
|
||||
showNotification({
|
||||
title: T('UPLOAD_PROGRESS'),
|
||||
body: T('UPLOADING')
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
})
|
||||
picgo.helper.beforeUploadPlugins.register('renameFn', {
|
||||
handle: async (ctx: IPicGo) => {
|
||||
const rename = db.get('settings.rename')
|
||||
const autoRename = db.get('settings.autoRename')
|
||||
const rename = picgo.getConfig<boolean>('settings.rename')
|
||||
const autoRename = picgo.getConfig<boolean>('settings.autoRename')
|
||||
if (autoRename || rename) {
|
||||
await Promise.all(ctx.output.map(async (item, index) => {
|
||||
let name: undefined | string | null
|
||||
let fileName: string | undefined
|
||||
if (autoRename) {
|
||||
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS') + item.extname
|
||||
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmssSSS') + item.extname
|
||||
} else {
|
||||
fileName = item.fileName
|
||||
}
|
||||
if (rename) {
|
||||
const window = windowManager.create(IWindowList.RENAME_WINDOW)!
|
||||
await waitForShow(window.webContents)
|
||||
window.webContents.send(RENAME_FILE_NAME, fileName, item.fileName, window.webContents.id)
|
||||
logger.info('wait for rename window ready...')
|
||||
ipcMain.on(GET_RENAME_FILE_NAME, (evt) => {
|
||||
if (evt.sender.id === window.webContents.id) {
|
||||
logger.info('rename window ready, wait for rename...')
|
||||
window.webContents.send(RENAME_FILE_NAME, fileName, item.fileName, window.webContents.id)
|
||||
}
|
||||
})
|
||||
name = await waitForRename(window, window.webContents.id)
|
||||
}
|
||||
item.fileName = name || fileName
|
||||
@@ -121,20 +104,20 @@ class Uploader {
|
||||
async uploadWithBuildInClipboard (): Promise<ImgInfo[]|false> {
|
||||
let filePath = ''
|
||||
try {
|
||||
const imgPath = getClipboardFilePath()
|
||||
if (!imgPath) {
|
||||
const imgPath = getClipboardFilePathList()
|
||||
if (!imgPath.length) {
|
||||
const nativeImage = clipboard.readImage()
|
||||
if (nativeImage.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
const buffer = nativeImage.toPNG()
|
||||
const baseDir = picgo.baseDir
|
||||
const fileName = `${dayjs().format('YYYYMMDDHHmmSSS')}.png`
|
||||
const fileName = `${dayjs().format('YYYYMMDDHHmmssSSS')}.png`
|
||||
filePath = path.join(baseDir, CLIPBOARD_IMAGE_FOLDER, fileName)
|
||||
await writeFile(filePath, buffer)
|
||||
return await this.upload([filePath])
|
||||
} else {
|
||||
return await this.upload([imgPath])
|
||||
return await this.upload(imgPath)
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
@@ -156,12 +139,11 @@ class Uploader {
|
||||
const output = await picgo.upload(img)
|
||||
if (Array.isArray(output) && output.some((item: ImgInfo) => item.imgUrl)) {
|
||||
if (this.webContents) {
|
||||
handleTalkingData(this.webContents, {
|
||||
dataReportManager.reportUploadData(this.webContents, {
|
||||
fromClipboard: !img,
|
||||
type: db.get('picBed.uploader') || db.get('picBed.current') || 'smms',
|
||||
count: img ? img.length : 1,
|
||||
duration: Date.now() - startTime
|
||||
} as IAnalyticsData)
|
||||
duration: Date.now() - startTime,
|
||||
outputList: output
|
||||
})
|
||||
}
|
||||
return output.filter(item => item.imgUrl)
|
||||
} else {
|
||||
@@ -177,6 +159,9 @@ class Uploader {
|
||||
})
|
||||
}, 500)
|
||||
return false
|
||||
} finally {
|
||||
ipcMain.removeAllListeners(GET_RENAME_FILE_NAME)
|
||||
cleanupFormUploaderFiles(img)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
import { buildRendererUrl } from '~/main/utils/env'
|
||||
|
||||
export const TRAY_WINDOW_URL = isDevelopment
|
||||
? (process.env.WEBPACK_DEV_SERVER_URL as string)
|
||||
: 'picgo://./index.html'
|
||||
export const TRAY_WINDOW_URL = buildRendererUrl()
|
||||
|
||||
export const SETTING_WINDOW_URL = isDevelopment
|
||||
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#main-page/upload`
|
||||
: 'picgo://./index.html#main-page/upload'
|
||||
export const SETTING_WINDOW_URL = buildRendererUrl('main-page/upload')
|
||||
|
||||
export const MINI_WINDOW_URL = isDevelopment
|
||||
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page`
|
||||
: 'picgo://./index.html#mini-page'
|
||||
export const MINI_WINDOW_URL = buildRendererUrl('mini-page')
|
||||
|
||||
export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development'
|
||||
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page`
|
||||
: 'picgo://./index.html#rename-page'
|
||||
export const RENAME_WINDOW_URL = buildRendererUrl('rename-page')
|
||||
|
||||
export const TOOLBOX_WINDOW_URL = process.env.NODE_ENV === 'development'
|
||||
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#toolbox-page`
|
||||
: 'picgo://./index.html#toolbox-page'
|
||||
export const TOOLBOX_WINDOW_URL = buildRendererUrl('toolbox-page')
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// import path from 'path'
|
||||
import {
|
||||
SETTING_WINDOW_URL,
|
||||
TRAY_WINDOW_URL,
|
||||
@@ -5,18 +6,27 @@ import {
|
||||
RENAME_WINDOW_URL,
|
||||
TOOLBOX_WINDOW_URL
|
||||
} from './constants'
|
||||
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
|
||||
import { IStartupMode, 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 { T } from '~/main/i18n'
|
||||
import { isLinux } from '~/universal/utils/common'
|
||||
import { getStaticPath } from '#/utils/staticPath'
|
||||
import picgo from '@core/picgo'
|
||||
// import { URLSearchParams } from 'url'
|
||||
|
||||
const windowList = new Map<IWindowList, IWindowListItem>()
|
||||
|
||||
const defaultWebPreferences = {
|
||||
// preload: path.join(__dirname, '../preload/index.js'),
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
nodeIntegrationInWorker: true,
|
||||
backgroundThrottling: false
|
||||
}
|
||||
|
||||
const handleWindowParams = (windowURL: string) => {
|
||||
// const [baseURL, hash = ''] = windowURL.split('#')
|
||||
// const search = new URLSearchParams()
|
||||
@@ -26,6 +36,21 @@ const handleWindowParams = (windowURL: string) => {
|
||||
return windowURL
|
||||
}
|
||||
|
||||
export const isWindowShouldShowOnStartup = (currentWindow: IWindowList) => {
|
||||
const startupMode = picgo.getConfig<IStartupMode | undefined>('settings.startupMode') || (isLinux ? IStartupMode.SHOW_MINI_WINDOW : IStartupMode.HIDE)
|
||||
switch (currentWindow) {
|
||||
case IWindowList.MINI_WINDOW: {
|
||||
return startupMode === IStartupMode.SHOW_MINI_WINDOW
|
||||
}
|
||||
case IWindowList.SETTING_WINDOW: {
|
||||
return startupMode === IStartupMode.SHOW_MAIN_WINDOW
|
||||
}
|
||||
default: {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
isValid: process.platform !== 'linux',
|
||||
multiple: false,
|
||||
@@ -40,10 +65,7 @@ windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
transparent: true,
|
||||
vibrancy: 'ultra-dark',
|
||||
webPreferences: {
|
||||
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
|
||||
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
|
||||
nodeIntegrationInWorker: true,
|
||||
backgroundThrottling: false,
|
||||
...defaultWebPreferences,
|
||||
webSecurity: false
|
||||
}
|
||||
}
|
||||
@@ -60,7 +82,7 @@ windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options () {
|
||||
const showDockIcon = db.get('settings.showDockIcon') !== false
|
||||
const showDockIcon = picgo.getConfig<boolean>('settings.showDockIcon') !== false
|
||||
const options: IBrowserWindowOptions = {
|
||||
height: 450,
|
||||
width: 800,
|
||||
@@ -70,32 +92,23 @@ windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
title: 'PicGo',
|
||||
vibrancy: 'ultra-dark',
|
||||
transparent: true,
|
||||
skipTaskbar: !showDockIcon,
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
|
||||
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
|
||||
nodeIntegrationInWorker: true,
|
||||
...defaultWebPreferences,
|
||||
webSecurity: false
|
||||
}
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
options.show = false
|
||||
options.frame = false
|
||||
options.backgroundColor = '#3f3c37'
|
||||
options.transparent = false
|
||||
options.icon = `${__static}/logo.png`
|
||||
options.icon = getStaticPath('logo.png')
|
||||
options.skipTaskbar = false
|
||||
}
|
||||
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)
|
||||
@@ -117,22 +130,19 @@ windowList.set(IWindowList.MINI_WINDOW, {
|
||||
const obj: IBrowserWindowOptions = {
|
||||
height: 64,
|
||||
width: 64,
|
||||
show: process.platform === 'linux',
|
||||
show: isLinux,
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
skipTaskbar: true,
|
||||
resizable: false,
|
||||
transparent: process.platform !== 'linux',
|
||||
icon: `${__static}/logo.png`,
|
||||
icon: getStaticPath('logo.png'),
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
|
||||
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
|
||||
nodeIntegrationInWorker: true
|
||||
...defaultWebPreferences
|
||||
}
|
||||
}
|
||||
|
||||
if (db.get('settings.miniWindowOntop')) {
|
||||
if (picgo.getConfig<boolean>('settings.miniWindowOnTop')) {
|
||||
obj.alwaysOnTop = true
|
||||
}
|
||||
return obj
|
||||
@@ -152,19 +162,14 @@ windowList.set(IWindowList.RENAME_WINDOW, {
|
||||
show: true,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
vibrancy: 'ultra-dark',
|
||||
backgroundColor: 'rgba(26,40,42,0.9)',
|
||||
webPreferences: {
|
||||
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
|
||||
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
|
||||
nodeIntegrationInWorker: true,
|
||||
backgroundThrottling: false
|
||||
...defaultWebPreferences
|
||||
}
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
options.show = true
|
||||
options.backgroundColor = '#3f3c37'
|
||||
options.autoHideMenuBar = true
|
||||
options.transparent = false
|
||||
}
|
||||
return options
|
||||
},
|
||||
@@ -199,21 +204,16 @@ windowList.set(IWindowList.TOOLBOX_WINDOW, {
|
||||
center: true,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
title: `PicGo ${T('TOOLBOX')}`,
|
||||
vibrancy: 'ultra-dark',
|
||||
icon: `${__static}/logo.png`,
|
||||
backgroundColor: 'rgba(26,40,42,0.9)',
|
||||
title: `PicGo-${T('TOOLBOX')}`,
|
||||
icon: getStaticPath('logo.png'),
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
|
||||
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
|
||||
nodeIntegrationInWorker: true,
|
||||
...defaultWebPreferences,
|
||||
webSecurity: false
|
||||
}
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
options.backgroundColor = '#3f3c37'
|
||||
options.autoHideMenuBar = true
|
||||
options.transparent = false
|
||||
}
|
||||
return options
|
||||
},
|
||||
|
||||
@@ -23,6 +23,10 @@ class WindowManager implements IWindowManager {
|
||||
this.windowIdMap.set(window.id, name)
|
||||
}
|
||||
windowConfig.callback(window, this)
|
||||
// https://github.com/electron/electron/issues/1594
|
||||
window.on('page-title-updated', (evt) => {
|
||||
evt.preventDefault()
|
||||
})
|
||||
window.on('close', () => {
|
||||
this.deleteById(id)
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
UPLOAD_WITH_CLIPBOARD_FILES,
|
||||
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
|
||||
GET_WINDOW_ID,
|
||||
GET_WINDOW_ID_REPONSE,
|
||||
GET_WINDOW_ID_RESPONSE,
|
||||
GET_SETTING_WINDOW_ID,
|
||||
GET_SETTING_WINDOW_ID_RESPONSE
|
||||
} from './constants'
|
||||
@@ -56,7 +56,7 @@ export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
|
||||
// miniWindow or settingWindow or trayWindow
|
||||
export const getWindowId = (): Promise<number> => {
|
||||
return new Promise((resolve) => {
|
||||
bus.once(GET_WINDOW_ID_REPONSE, (id: number) => {
|
||||
bus.once(GET_WINDOW_ID_RESPONSE, (id: number) => {
|
||||
resolve(id)
|
||||
})
|
||||
bus.emit(GET_WINDOW_ID)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const GET_WINDOW_ID = 'GET_WINDOW_ID' // get a current window
|
||||
export const GET_WINDOW_ID_REPONSE = 'GET_WINDOW_ID_REPONSE'
|
||||
export const GET_WINDOW_ID_RESPONSE = 'GET_WINDOW_ID_RESPONSE'
|
||||
export const GET_SETTING_WINDOW_ID = 'GET_SETTING_WINDOW_ID' // get setting window
|
||||
export const GET_SETTING_WINDOW_ID_RESPONSE = 'GET_SETTING_WINDOW_ID_RESPONSE'
|
||||
export const UPLOAD_WITH_FILES = 'UPLOAD_WITH_FILES'
|
||||
@@ -7,3 +7,7 @@ export const UPLOAD_WITH_FILES_RESPONSE = 'UPLOAD_WITH_FILES_RESPONSE'
|
||||
export const UPLOAD_WITH_CLIPBOARD_FILES = 'UPLOAD_WITH_CLIPBOARD_FILES'
|
||||
export const UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE = 'UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE'
|
||||
export const CREATE_APP_MENU = 'CREATE_APP_MENU'
|
||||
|
||||
// shortkey command
|
||||
export const SHORTKEY_BUILTIN_PREFIX = 'picgo:'
|
||||
export const SHORTKEY_COMMAND_UPLOAD = `${SHORTKEY_BUILTIN_PREFIX}upload`
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import fs from 'fs-extra'
|
||||
import writeFile from 'write-file-atomic'
|
||||
import path from 'path'
|
||||
import { app as APP } from 'electron'
|
||||
import { getLogger } from '@core/utils/localLogger'
|
||||
import dayjs from 'dayjs'
|
||||
import { T } from '~/main/i18n'
|
||||
const STORE_PATH = APP.getPath('userData')
|
||||
import { FORM_IMAGE_FOLDER } from '~/universal/utils/static'
|
||||
import { STORE_PATH } from '~/main/utils/env'
|
||||
const configFilePath = path.join(STORE_PATH, 'data.json')
|
||||
const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json')
|
||||
export const defaultConfigPath = configFilePath
|
||||
@@ -131,9 +131,19 @@ function getGalleryDBPath (): {
|
||||
}
|
||||
}
|
||||
|
||||
function getFormImageFolderPath (): string {
|
||||
const STORE_PATH = dbPathDir()
|
||||
const formImagesPath = path.join(STORE_PATH, FORM_IMAGE_FOLDER)
|
||||
if (!fs.existsSync(formImagesPath)) {
|
||||
fs.mkdirSync(formImagesPath, { recursive: true })
|
||||
}
|
||||
return formImagesPath
|
||||
}
|
||||
|
||||
export {
|
||||
dbChecker,
|
||||
dbPathChecker,
|
||||
dbPathDir,
|
||||
getGalleryDBPath
|
||||
getGalleryDBPath,
|
||||
getFormImageFolderPath
|
||||
}
|
||||
|
||||
@@ -1,85 +1,14 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { dbPathChecker, dbPathDir, getGalleryDBPath } from './dbChecker'
|
||||
import { DBStore, JSONStore } from '@picgo/store'
|
||||
import { T } from '~/main/i18n'
|
||||
import { DBStore } from '@picgo/store'
|
||||
import { getGalleryDBPath } from './dbChecker'
|
||||
|
||||
const STORE_PATH = dbPathDir()
|
||||
const DB_PATH: string = getGalleryDBPath().dbPath
|
||||
fs.ensureDirSync(path.dirname(DB_PATH))
|
||||
|
||||
if (!fs.pathExistsSync(STORE_PATH)) {
|
||||
fs.mkdirpSync(STORE_PATH)
|
||||
}
|
||||
const CONFIG_PATH: string = dbPathChecker()
|
||||
export const DB_PATH: string = getGalleryDBPath().dbPath
|
||||
|
||||
class ConfigStore {
|
||||
private db: JSONStore
|
||||
constructor () {
|
||||
this.db = new JSONStore(CONFIG_PATH)
|
||||
|
||||
if (!this.db.has('picBed')) {
|
||||
this.db.set('picBed', {
|
||||
current: 'smms', // deprecated
|
||||
uploader: 'smms',
|
||||
smms: {
|
||||
token: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!this.db.has('settings.shortKey')) {
|
||||
this.db.set('settings.shortKey[picgo:upload]', {
|
||||
enable: true,
|
||||
key: 'CommandOrControl+Shift+P',
|
||||
name: 'upload',
|
||||
label: T('QUICK_UPLOAD')
|
||||
})
|
||||
}
|
||||
this.read()
|
||||
}
|
||||
|
||||
flush () {
|
||||
this.db = new JSONStore(CONFIG_PATH)
|
||||
}
|
||||
|
||||
read () {
|
||||
this.db.read()
|
||||
return this.db
|
||||
}
|
||||
|
||||
get (key = ''): any {
|
||||
if (key === '') {
|
||||
return this.db.read()
|
||||
}
|
||||
return this.db.get(key)
|
||||
}
|
||||
|
||||
set (key: string, value: any): void {
|
||||
return this.db.set(key, value)
|
||||
}
|
||||
|
||||
has (key: string) {
|
||||
return this.db.has(key)
|
||||
}
|
||||
|
||||
unset (key: string, value: any): boolean {
|
||||
return this.db.unset(key, value)
|
||||
}
|
||||
|
||||
getConfigPath () {
|
||||
return CONFIG_PATH
|
||||
}
|
||||
}
|
||||
|
||||
const db = new ConfigStore()
|
||||
|
||||
export default db
|
||||
|
||||
// v2.3.0 add gallery db
|
||||
class GalleryDB {
|
||||
private static instance: DBStore
|
||||
private constructor () {
|
||||
console.log('init gallery db')
|
||||
}
|
||||
private constructor () {}
|
||||
|
||||
public static getInstance (): DBStore {
|
||||
if (!GalleryDB.instance) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { dbChecker, dbPathChecker } from 'apis/core/datastore/dbChecker'
|
||||
import pkg from 'root/package.json'
|
||||
import { PicGo } from 'picgo'
|
||||
import db from 'apis/core/datastore'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
const CONFIG_PATH = dbPathChecker()
|
||||
|
||||
@@ -17,18 +15,18 @@ picgo.saveConfig({
|
||||
global.PICGO_GUI_VERSION = pkg.version
|
||||
picgo.GUI_VERSION = global.PICGO_GUI_VERSION
|
||||
|
||||
const originPicGoSaveConfig = picgo.saveConfig.bind(picgo)
|
||||
// const originPicGoSaveConfig = picgo.saveConfig.bind(picgo)
|
||||
|
||||
function flushDB () {
|
||||
db.flush()
|
||||
}
|
||||
// function flushDB () {
|
||||
// db.flush()
|
||||
// }
|
||||
|
||||
const debounced = debounce(flushDB, 1000)
|
||||
// const debounced = debounce(flushDB, 1000)
|
||||
|
||||
picgo.saveConfig = (config: IStringKeyMap) => {
|
||||
originPicGoSaveConfig(config)
|
||||
// flush electron's db
|
||||
debounced()
|
||||
}
|
||||
// picgo.saveConfig = (config: IStringKeyMap) => {
|
||||
// originPicGoSaveConfig(config)
|
||||
// // flush electron's db
|
||||
// debounced()
|
||||
// }
|
||||
|
||||
export default picgo
|
||||
|
||||
+32
-24
@@ -1,14 +1,14 @@
|
||||
import {
|
||||
dialog,
|
||||
BrowserWindow,
|
||||
Notification,
|
||||
ipcMain
|
||||
} from 'electron'
|
||||
import db, { GalleryDB } from 'apis/core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import { GalleryDB } from 'apis/core/datastore'
|
||||
import { dbPathChecker, defaultConfigPath, getGalleryDBPath } from 'apis/core/datastore/dbChecker'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
import { handleCopyUrl, showNotification as showMainNotification } from '~/main/utils/common'
|
||||
import {
|
||||
getWindowId,
|
||||
getSettingWindowId
|
||||
@@ -18,15 +18,14 @@ import {
|
||||
} from '~/universal/events/constants'
|
||||
import { DBStore } from '@picgo/store'
|
||||
import { T } from '~/main/i18n'
|
||||
import { IPasteStyle, IRPCActionType } from '~/universal/types/enum'
|
||||
|
||||
// Cross-process support may be required in the future
|
||||
class GuiApi implements IGuiApi {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
private static instance: GuiApi
|
||||
private windowId: number = -1
|
||||
private settingWindowId: number = -1
|
||||
private constructor () {
|
||||
console.log('init guiapi')
|
||||
console.log('init gui api')
|
||||
}
|
||||
|
||||
public static getInstance (): GuiApi {
|
||||
@@ -50,7 +49,7 @@ class GuiApi implements IGuiApi {
|
||||
})
|
||||
}
|
||||
|
||||
private getWebcontentsByWindowId (id: number) {
|
||||
private getWebContentsByWindowId (id: number) {
|
||||
return BrowserWindow.fromId(id)?.webContents
|
||||
}
|
||||
|
||||
@@ -59,9 +58,9 @@ class GuiApi implements IGuiApi {
|
||||
placeholder: ''
|
||||
}) {
|
||||
await this.showSettingWindow()
|
||||
this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
|
||||
this.getWebContentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
|
||||
return new Promise<string>((resolve) => {
|
||||
ipcMain.once(SHOW_INPUT_BOX, (event: Event, value: string) => {
|
||||
ipcMain.once(SHOW_INPUT_BOX, (event, value: string) => {
|
||||
resolve(value)
|
||||
})
|
||||
})
|
||||
@@ -75,26 +74,25 @@ class GuiApi implements IGuiApi {
|
||||
|
||||
async upload (input: IUploadOption) {
|
||||
this.windowId = await getWindowId()
|
||||
const webContents = this.getWebcontentsByWindowId(this.windowId)
|
||||
const webContents = this.getWebContentsByWindowId(this.windowId)
|
||||
const imgs = await uploader.setWebContents(webContents!).upload(input)
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
const pasteStyle = picgo.getConfig<IPasteStyle>('settings.pasteStyle') || 'markdown'
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: imgs[i].imgUrl as string
|
||||
// icon: imgs[i].imgUrl
|
||||
})
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], picgo.getConfig<string>('settings.customLink')))
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
showMainNotification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: imgs[i].imgUrl as string
|
||||
// icon: imgs[i].imgUrl
|
||||
})
|
||||
}, i * 100)
|
||||
await GalleryDB.getInstance().insert(imgs[i])
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
webContents?.send('uploadFiles', imgs)
|
||||
webContents?.send('updateGallery')
|
||||
webContents?.send(IRPCActionType.UPDATE_GALLERY)
|
||||
return imgs
|
||||
}
|
||||
return []
|
||||
@@ -104,11 +102,7 @@ class GuiApi implements IGuiApi {
|
||||
title: '',
|
||||
body: ''
|
||||
}) {
|
||||
const notification = new Notification({
|
||||
title: options.title,
|
||||
body: options.body
|
||||
})
|
||||
notification.show()
|
||||
showMainNotification(options)
|
||||
}
|
||||
|
||||
showMessageBox (options: IShowMessageBoxOption = {
|
||||
@@ -131,6 +125,20 @@ class GuiApi implements IGuiApi {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* v2.4.0+
|
||||
* @param options
|
||||
*/
|
||||
async showConfigDialog<T extends IStringKeyMap> (options: IPicGoPluginShowConfigDialogOption) {
|
||||
await this.showSettingWindow()
|
||||
this.getWebContentsByWindowId(this.settingWindowId)?.send(IRPCActionType.OPEN_CONFIG_DIALOG, options)
|
||||
return new Promise<T | false>((resolve) => {
|
||||
ipcMain.once(IRPCActionType.OPEN_CONFIG_DIALOG, (event, value: T | false) => {
|
||||
resolve(value)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* get picgo config/data path
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import bus from '@core/bus'
|
||||
import {
|
||||
uploadClipboardFiles,
|
||||
uploadChoosedFiles
|
||||
uploadSelectedFiles
|
||||
} from 'apis/app/uploader/apis'
|
||||
import {
|
||||
createMenu
|
||||
@@ -14,14 +14,13 @@ import {
|
||||
UPLOAD_WITH_CLIPBOARD_FILES,
|
||||
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
|
||||
GET_WINDOW_ID,
|
||||
GET_WINDOW_ID_REPONSE,
|
||||
GET_WINDOW_ID_RESPONSE,
|
||||
GET_SETTING_WINDOW_ID,
|
||||
GET_SETTING_WINDOW_ID_RESPONSE,
|
||||
CREATE_APP_MENU
|
||||
} from '@core/bus/constants'
|
||||
function initEventCenter () {
|
||||
const eventList: any = {
|
||||
'picgo:upload': uploadClipboardFiles,
|
||||
[UPLOAD_WITH_CLIPBOARD_FILES]: busCallUploadClipboardFiles,
|
||||
[UPLOAD_WITH_FILES]: busCallUploadFiles,
|
||||
[GET_WINDOW_ID]: busCallGetWindowId,
|
||||
@@ -40,13 +39,13 @@ async function busCallUploadClipboardFiles () {
|
||||
|
||||
async function busCallUploadFiles (pathList: IFileWithPath[]) {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
const urls = await uploadChoosedFiles(win.webContents, pathList)
|
||||
const urls = await uploadSelectedFiles(win.webContents, pathList)
|
||||
bus.emit(UPLOAD_WITH_FILES_RESPONSE, urls)
|
||||
}
|
||||
|
||||
function busCallGetWindowId () {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
bus.emit(GET_WINDOW_ID_REPONSE, win.id)
|
||||
bus.emit(GET_WINDOW_ID_RESPONSE, win.id)
|
||||
}
|
||||
|
||||
function busCallGetSettingWindowId () {
|
||||
|
||||
+30
-26
@@ -2,15 +2,16 @@ import {
|
||||
app,
|
||||
ipcMain,
|
||||
shell,
|
||||
Notification,
|
||||
IpcMainEvent,
|
||||
BrowserWindow
|
||||
} from 'electron'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { IPasteStyle, IRPCActionType, IWindowList } from '#/types/enum'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import server from '~/main/server'
|
||||
import getPicBeds from '~/main/utils/getPicBeds'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
@@ -27,19 +28,19 @@ import {
|
||||
OPEN_URL,
|
||||
SHOW_PLUGIN_PAGE_MENU,
|
||||
SET_MINI_WINDOW_POS,
|
||||
GET_PICBEDS
|
||||
GET_PICBEDS,
|
||||
LOG_INVALID_URL_LINES
|
||||
} from '#/events/constants'
|
||||
import {
|
||||
uploadClipboardFiles,
|
||||
uploadChoosedFiles
|
||||
uploadSelectedFiles
|
||||
} from '~/main/apis/app/uploader/apis'
|
||||
import picgoCoreIPC from './picgoCoreIPC'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
import { handleCopyUrl, showNotification } from '~/main/utils/common'
|
||||
import { buildMainPageMenu, buildMiniPageMenu, buildPluginPageMenu, buildPicBedListMenu } from './remotes/menu'
|
||||
import path from 'path'
|
||||
import { T } from '~/main/i18n'
|
||||
|
||||
const STORE_PATH = app.getPath('userData')
|
||||
import { STORE_PATH } from '~/main/utils/env'
|
||||
|
||||
export default {
|
||||
listen () {
|
||||
@@ -50,19 +51,18 @@ export default {
|
||||
// macOS use builtin clipboard is OK
|
||||
const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard()
|
||||
if (img !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
const pasteStyle = picgo.getConfig<IPasteStyle>('settings.pasteStyle') || 'markdown'
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0], picgo.getConfig<string>('settings.customLink')))
|
||||
showNotification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: img[0].imgUrl!
|
||||
// icon: file[0]
|
||||
// icon: img[0].imgUrl
|
||||
})
|
||||
notification.show()
|
||||
await GalleryDB.getInstance().insert(img[0])
|
||||
trayWindow.webContents.send('clipboardFiles', [])
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
}
|
||||
trayWindow.webContents.send('uploadFiles')
|
||||
@@ -74,50 +74,45 @@ export default {
|
||||
})
|
||||
|
||||
ipcMain.on('uploadChoosedFiles', async (evt: IpcMainEvent, files: IFileWithPath[]) => {
|
||||
return uploadChoosedFiles(evt.sender, files)
|
||||
return uploadSelectedFiles(evt.sender, files)
|
||||
})
|
||||
|
||||
ipcMain.on('updateShortKey', (evt: IpcMainEvent, item: IShortKeyConfig, oldKey: string, from: string) => {
|
||||
const result = shortKeyHandler.updateShortKey(item, oldKey, from)
|
||||
evt.sender.send('updateShortKeyResponse', result)
|
||||
if (result) {
|
||||
const notification = new Notification({
|
||||
showNotification({
|
||||
title: T('OPERATION_SUCCEED'),
|
||||
body: T('TIPS_SHORTCUT_MODIFIED_SUCCEED')
|
||||
})
|
||||
notification.show()
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
showNotification({
|
||||
title: T('OPERATION_FAILED'),
|
||||
body: T('TIPS_SHORTCUT_MODIFIED_CONFLICT')
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('bindOrUnbindShortKey', (evt: IpcMainEvent, item: IShortKeyConfig, from: string) => {
|
||||
const result = shortKeyHandler.bindOrUnbindShortKey(item, from)
|
||||
if (result) {
|
||||
const notification = new Notification({
|
||||
showNotification({
|
||||
title: T('OPERATION_SUCCEED'),
|
||||
body: T('TIPS_SHORTCUT_MODIFIED_SUCCEED')
|
||||
})
|
||||
notification.show()
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
showNotification({
|
||||
title: T('OPERATION_FAILED'),
|
||||
body: T('TIPS_SHORTCUT_MODIFIED_CONFLICT')
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('updateCustomLink', () => {
|
||||
const notification = new Notification({
|
||||
showNotification({
|
||||
title: T('OPERATION_SUCCEED'),
|
||||
body: T('TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED')
|
||||
})
|
||||
notification.show()
|
||||
})
|
||||
|
||||
ipcMain.on('autoStart', (evt: IpcMainEvent, val: boolean) => {
|
||||
@@ -137,7 +132,7 @@ export default {
|
||||
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
|
||||
if (db.get('settings.miniWindowOntop')) {
|
||||
if (picgo.getConfig<boolean>('settings.miniWindowOnTop')) {
|
||||
miniWindow.setAlwaysOnTop(true)
|
||||
}
|
||||
|
||||
@@ -167,7 +162,9 @@ export default {
|
||||
server.restart()
|
||||
})
|
||||
ipcMain.on(OPEN_DEVTOOLS, (event: IpcMainEvent) => {
|
||||
event.sender.openDevTools()
|
||||
event.sender.openDevTools({
|
||||
mode: 'detach'
|
||||
})
|
||||
})
|
||||
// menu & window methods
|
||||
ipcMain.on(SHOW_MINI_PAGE_MENU, () => {
|
||||
@@ -221,6 +218,13 @@ export default {
|
||||
const window = BrowserWindow.getFocusedWindow()
|
||||
window?.setBounds(pos)
|
||||
})
|
||||
|
||||
ipcMain.on(LOG_INVALID_URL_LINES, (_evt: IpcMainEvent, lines: string[]) => {
|
||||
if (!Array.isArray(lines) || !lines.length) return
|
||||
lines.forEach((line, index) => {
|
||||
logger.warn(`[Batch URL Upload] invalid url line #${index + 1}: ${line}`)
|
||||
})
|
||||
})
|
||||
},
|
||||
dispose () {}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ipcMain,
|
||||
clipboard
|
||||
} from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import { IPasteStyle, IPicGoHelperType, IWindowList } from '#/types/enum'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
import picgo from '@core/picgo'
|
||||
@@ -29,7 +30,8 @@ import {
|
||||
OPEN_WINDOW,
|
||||
GET_LANGUAGE_LIST,
|
||||
SET_CURRENT_LANGUAGE,
|
||||
GET_CURRENT_LANGUAGE
|
||||
GET_CURRENT_LANGUAGE,
|
||||
GET_PICBED_CONFIG
|
||||
} from '#/events/constants'
|
||||
|
||||
import { GalleryDB } from 'apis/core/datastore'
|
||||
@@ -38,11 +40,7 @@ import pasteTemplate from '../utils/pasteTemplate'
|
||||
import { i18nManager, T } from '~/main/i18n'
|
||||
import { rpcServer } from './rpc'
|
||||
|
||||
// eslint-disable-next-line
|
||||
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
|
||||
// const PluginHandler = requireFunc('picgo/lib/PluginHandler').default
|
||||
const STORE_PATH = path.dirname(dbPathChecker())
|
||||
// const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
|
||||
|
||||
interface GuiMenuItem {
|
||||
label: string
|
||||
@@ -83,7 +81,7 @@ const getPluginList = (): IPicGoPlugin[] => {
|
||||
for (const i in pluginList) {
|
||||
const plugin = picgo.pluginLoader.getPlugin(pluginList[i])!
|
||||
const pluginPath = path.join(STORE_PATH, `/node_modules/${pluginList[i]}`)
|
||||
const pluginPKG = requireFunc(path.join(pluginPath, 'package.json'))
|
||||
const pluginPKG = fs.readJSONSync(path.join(pluginPath, 'package.json'), 'utf-8')
|
||||
const uploaderName = plugin.uploader || ''
|
||||
const transformerName = plugin.transformer || ''
|
||||
let menu: Omit<IGuiMenuItem, 'handle'>[] = []
|
||||
@@ -223,14 +221,14 @@ const handleNPMError = (): IDispose => {
|
||||
}
|
||||
|
||||
const handleGetPicBedConfig = () => {
|
||||
ipcMain.on('getPicBedConfig', (event: IpcMainEvent, type: string) => {
|
||||
ipcMain.on(GET_PICBED_CONFIG, (event: IpcMainEvent, type: string) => {
|
||||
const name = picgo.helper.uploader.get(type)?.name || type
|
||||
if (picgo.helper.uploader.get(type)?.config) {
|
||||
const _config = picgo.helper.uploader.get(type)!.config!(picgo)
|
||||
const config = handleConfigWithFunction(_config)
|
||||
event.sender.send('getPicBedConfig', config, name)
|
||||
event.sender.send(GET_PICBED_CONFIG, config, name)
|
||||
} else {
|
||||
event.sender.send('getPicBedConfig', [], name)
|
||||
event.sender.send(GET_PICBED_CONFIG, [], name)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -259,8 +257,9 @@ const handleRemoveFiles = () => {
|
||||
}
|
||||
|
||||
const handlePicGoSaveConfig = () => {
|
||||
ipcMain.on(PICGO_SAVE_CONFIG, (event: IpcMainEvent, data: IObj) => {
|
||||
ipcMain.handle(PICGO_SAVE_CONFIG, (_event, data: IObj) => {
|
||||
picgo.saveConfig(data)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { Menu, BrowserWindow, app, dialog } from 'electron'
|
||||
import getPicBeds from '~/main/utils/getPicBeds'
|
||||
import picgo from '@core/picgo'
|
||||
import {
|
||||
uploadClipboardFiles
|
||||
@@ -13,7 +12,7 @@ import { PICGO_CONFIG_PLUGIN, PICGO_HANDLE_PLUGIN_DONE, PICGO_HANDLE_PLUGIN_ING,
|
||||
import picgoCoreIPC from '~/main/events/picgoCoreIPC'
|
||||
import { PicGo as PicGoCore } from 'picgo'
|
||||
import { T } from '~/main/i18n'
|
||||
import { changeCurrentUploader } from '~/main/utils/handleUploaderConfig'
|
||||
import { buildPicBedListMenu } from './picBedListMenu'
|
||||
|
||||
interface GuiMenuItem {
|
||||
label: string
|
||||
@@ -105,7 +104,9 @@ const buildMainPageMenu = (win: BrowserWindow) => {
|
||||
{
|
||||
label: T('SHOW_DEVTOOLS'),
|
||||
click () {
|
||||
win?.webContents?.openDevTools()
|
||||
win?.webContents?.openDevTools({
|
||||
mode: 'detach'
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -119,61 +120,6 @@ const buildMainPageMenu = (win: BrowserWindow) => {
|
||||
return Menu.buildFromTemplate(template)
|
||||
}
|
||||
|
||||
const buildPicBedListMenu = () => {
|
||||
const picBeds = getPicBeds()
|
||||
const currentPicBed = picgo.getConfig('picBed.uploader')
|
||||
const currentPicBedName = picBeds.find(item => item.type === currentPicBed)?.name
|
||||
const picBedConfigList = picgo.getConfig<IUploaderConfig>('uploader')
|
||||
const currentPicBedMenuItem = [{
|
||||
label: `${T('CURRENT_PICBED')} - ${currentPicBedName}`,
|
||||
enabled: false
|
||||
}, {
|
||||
type: 'separator'
|
||||
}]
|
||||
let submenu = picBeds.filter(item => item.visible).map(item => {
|
||||
const configList = picBedConfigList?.[item.type]?.configList
|
||||
const defaultId = picBedConfigList?.[item.type]?.defaultId
|
||||
const hasSubmenu = !!configList
|
||||
return {
|
||||
label: item.name,
|
||||
type: !hasSubmenu ? 'checkbox' : undefined,
|
||||
checked: !hasSubmenu ? (currentPicBed === item.type) : undefined,
|
||||
submenu: hasSubmenu
|
||||
? configList.map((config) => {
|
||||
return {
|
||||
label: config._configName || 'Default',
|
||||
// if only one config, use checkbox, or radio will checked as default
|
||||
// see: https://github.com/electron/electron/issues/21292
|
||||
type: 'checkbox',
|
||||
checked: config._id === defaultId && (item.type === currentPicBed),
|
||||
click: function () {
|
||||
changeCurrentUploader(item.type, config, config._id)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
: undefined,
|
||||
click: !hasSubmenu
|
||||
? function () {
|
||||
picgo.saveConfig({
|
||||
'picBed.current': item.type,
|
||||
'picBed.uploader': item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
submenu = currentPicBedMenuItem.concat(submenu)
|
||||
// @ts-ignore
|
||||
return Menu.buildFromTemplate(submenu)
|
||||
}
|
||||
|
||||
// TODO: separate to single file
|
||||
|
||||
const handleRestoreState = (item: string, name: string): void => {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { Menu } from 'electron'
|
||||
import getPicBeds from '~/main/utils/getPicBeds'
|
||||
import picgo from '@core/picgo'
|
||||
import { T } from '~/main/i18n'
|
||||
import { changeCurrentUploader } from '~/main/utils/handleUploaderConfig'
|
||||
|
||||
export const buildPicBedListMenu = () => {
|
||||
const picBeds = getPicBeds()
|
||||
const currentPicBed = picgo.getConfig('picBed.uploader')
|
||||
const currentPicBedName = picBeds.find(item => item.type === currentPicBed)?.name
|
||||
const picBedConfigList = picgo.getConfig<IUploaderConfig>('uploader')
|
||||
const currentPicBedMenuItem = [{
|
||||
label: `${T('CURRENT_PICBED')} - ${currentPicBedName}`,
|
||||
enabled: false
|
||||
}, {
|
||||
type: 'separator'
|
||||
}]
|
||||
let submenu = picBeds.filter(item => item.visible).map(item => {
|
||||
const configList = picBedConfigList?.[item.type]?.configList
|
||||
const defaultId = picBedConfigList?.[item.type]?.defaultId
|
||||
const hasSubmenu = !!configList
|
||||
return {
|
||||
label: item.name,
|
||||
type: !hasSubmenu ? 'checkbox' : undefined,
|
||||
checked: !hasSubmenu ? (currentPicBed === item.type) : undefined,
|
||||
submenu: hasSubmenu
|
||||
? configList.map((config) => {
|
||||
return {
|
||||
label: config._configName || 'Default',
|
||||
// if only one config, use checkbox, or radio will checked as default
|
||||
// see: https://github.com/electron/electron/issues/21292
|
||||
type: 'checkbox',
|
||||
checked: config._id === defaultId && (item.type === currentPicBed),
|
||||
click: function () {
|
||||
changeCurrentUploader(item.type, config, config._id)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
: undefined,
|
||||
click: !hasSubmenu
|
||||
? function () {
|
||||
picgo.saveConfig({
|
||||
'picBed.current': item.type,
|
||||
'picBed.uploader': item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
submenu = currentPicBedMenuItem.concat(submenu)
|
||||
// @ts-ignore
|
||||
return Menu.buildFromTemplate(submenu)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { configRouter } from './routes/config'
|
||||
import { versionRouter } from './routes/version'
|
||||
import { toolboxRouter } from './routes/toolbox'
|
||||
import { systemRouter } from './routes/system'
|
||||
import { galleryToolboxRouter } from './routes/galleryToolbox'
|
||||
|
||||
class RPCServer implements IRPCServer {
|
||||
private routes: IRPCRoutes = new Map()
|
||||
@@ -53,6 +54,7 @@ rpcServer.use(configRouter.routes())
|
||||
rpcServer.use(versionRouter.routes())
|
||||
rpcServer.use(toolboxRouter.routes())
|
||||
rpcServer.use(systemRouter.routes())
|
||||
rpcServer.use(galleryToolboxRouter.routes())
|
||||
|
||||
export {
|
||||
rpcServer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { RPCRouter } from '../router'
|
||||
import { deleteUploaderConfig, getUploaderConfigList, selectUploaderConfig, updateUploaderConfig } from '~/main/utils/handleUploaderConfig'
|
||||
import { copyUploaderConfig, deleteUploaderConfig, getUploaderConfigList, selectUploaderConfig, updateUploaderConfig } from '~/main/utils/handleUploaderConfig'
|
||||
|
||||
const configRouter = new RPCRouter()
|
||||
|
||||
@@ -15,6 +15,11 @@ configRouter
|
||||
const config = deleteUploaderConfig(type, id)
|
||||
return config
|
||||
})
|
||||
.add(IRPCActionType.COPY_UPLOADER_CONFIG, async (args) => {
|
||||
const [type, id] = args as ICopyUploaderConfigArgs
|
||||
const config = copyUploaderConfig(type, id)
|
||||
return config
|
||||
})
|
||||
.add(IRPCActionType.SELECT_UPLOADER, async (args) => {
|
||||
const [type, id] = args as ISelectUploaderConfigArgs
|
||||
selectUploaderConfig(type, id)
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
import { logger } from '@picgo/i18n'
|
||||
import { PicGoUtils, type IPicGo } from 'picgo'
|
||||
import { T } from '~/main/i18n'
|
||||
|
||||
interface IUrlRewriteRule {
|
||||
match: string
|
||||
replace: string
|
||||
enable?: boolean
|
||||
global?: boolean
|
||||
ignoreCase?: boolean
|
||||
}
|
||||
|
||||
interface IUrlRewriteDialogResult {
|
||||
applyGlobalRules?: boolean
|
||||
match?: string
|
||||
replace?: string
|
||||
global?: boolean
|
||||
ignoreCase?: boolean
|
||||
}
|
||||
|
||||
function normalizeRules (value: unknown): IUrlRewriteRule[] {
|
||||
if (!Array.isArray(value)) return []
|
||||
return value.map(item => {
|
||||
const raw = (item ?? {}) as Partial<Record<keyof IUrlRewriteRule, unknown>>
|
||||
return {
|
||||
match: String(raw.match ?? ''),
|
||||
replace: String(raw.replace ?? ''),
|
||||
enable: raw.enable === false ? false : true,
|
||||
global: raw.global === true,
|
||||
ignoreCase: raw.ignoreCase === true
|
||||
}
|
||||
}).filter(rule => rule.match.length > 0)
|
||||
}
|
||||
|
||||
function buildFlags (rule: Pick<IUrlRewriteRule, 'global' | 'ignoreCase'>): string {
|
||||
return `${rule.global ? 'g' : ''}${rule.ignoreCase ? 'i' : ''}`
|
||||
}
|
||||
|
||||
function validateRuleOrThrow (rule: IUrlRewriteRule) {
|
||||
if (!rule.match.trim() || !rule.replace.trim()) {
|
||||
throw new Error(T('GALLERY_URL_REWRITE_TEMP_RULE_REQUIRED'))
|
||||
}
|
||||
try {
|
||||
new RegExp(rule.match, buildFlags(rule))
|
||||
} catch (error) {
|
||||
const message = `Invalid URL rewrite regex pattern "${rule.match}": ${error instanceof Error ? error.message : String(error)}`
|
||||
logger.error(message)
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
function applyFirstMatchRewrite (ctx: IPicGo, imgItem: ImgInfo, rules: IUrlRewriteRule[]): ImgInfo {
|
||||
const imgInfo = {
|
||||
imgUrl: imgItem.imgUrl,
|
||||
originImgUrl: imgItem.originImgUrl
|
||||
}
|
||||
PicGoUtils.applyUrlRewriteToImgInfo(imgInfo, rules, {
|
||||
log: {
|
||||
error: (...args: Parameters<IPicGo['log']['error']>) => ctx.log.error(...args),
|
||||
warn: () => ctx.log.warn(T('GALLERY_URL_REWRITE_EMPTY_RESULT_WARN'))
|
||||
}
|
||||
})
|
||||
if (imgInfo.imgUrl === '') return imgItem
|
||||
return imgInfo
|
||||
}
|
||||
|
||||
export const galleryMenu = () => {
|
||||
return [{
|
||||
label: T('GALLERY_URL_REWRITE_TITLE'),
|
||||
async handle (ctx: IPicGo, guiApi: IGuiApi, selectedList: ImgInfo[] = []) {
|
||||
if (!selectedList.length) {
|
||||
guiApi.showNotification({
|
||||
title: T('GALLERY_URL_REWRITE_TITLE'),
|
||||
body: T('GALLERY_URL_REWRITE_WARN_NO_SELECTION')
|
||||
})
|
||||
logger.warn(T('GALLERY_URL_REWRITE_WARN_NO_SELECTION'))
|
||||
return
|
||||
}
|
||||
|
||||
const globalRules = normalizeRules(ctx.getConfig('settings.urlRewrite.rules'))
|
||||
|
||||
const config: IPicGoPluginConfig[] = [
|
||||
{
|
||||
alias: T('GALLERY_URL_REWRITE_APPLY_GLOBAL_RULES'),
|
||||
name: 'applyGlobalRules',
|
||||
type: 'confirm',
|
||||
default: globalRules.length > 0,
|
||||
required: false,
|
||||
confirmText: T('SETTINGS_OPEN'),
|
||||
cancelText: T('SETTINGS_CLOSE'),
|
||||
tips: `${T('GALLERY_URL_REWRITE_GLOBAL_RULES_COUNT')}: ${globalRules.length}`
|
||||
},
|
||||
{
|
||||
alias: T('URL_REWRITE_MATCH'),
|
||||
name: 'match',
|
||||
type: 'input',
|
||||
message: T('URL_REWRITE_MATCH_PLACEHOLDER'),
|
||||
default: '',
|
||||
required: false,
|
||||
tips: `${T('GALLERY_URL_REWRITE_TEMP_RULE_TIPS')}\n\n${T('URL_REWRITE_MATCH_TIPS')}`
|
||||
},
|
||||
{
|
||||
alias: T('URL_REWRITE_REPLACE'),
|
||||
name: 'replace',
|
||||
type: 'input',
|
||||
message: T('URL_REWRITE_REPLACE_PLACEHOLDER'),
|
||||
default: '',
|
||||
required: false,
|
||||
tips: T('URL_REWRITE_REPLACE_TIPS')
|
||||
},
|
||||
{
|
||||
alias: T('URL_REWRITE_FLAG_GLOBAL_LABEL'),
|
||||
name: 'global',
|
||||
type: 'confirm',
|
||||
default: false,
|
||||
required: false,
|
||||
confirmText: 'g',
|
||||
cancelText: '-',
|
||||
tips: T('URL_REWRITE_FLAG_GLOBAL_DESC')
|
||||
},
|
||||
{
|
||||
alias: T('URL_REWRITE_FLAG_IGNORE_CASE_LABEL'),
|
||||
name: 'ignoreCase',
|
||||
type: 'confirm',
|
||||
default: false,
|
||||
required: false,
|
||||
confirmText: 'i',
|
||||
cancelText: '-',
|
||||
tips: T('URL_REWRITE_FLAG_IGNORE_CASE_DESC')
|
||||
}
|
||||
]
|
||||
const options: IPicGoPluginShowConfigDialogOption = {
|
||||
title: T('GALLERY_URL_REWRITE_TITLE'),
|
||||
config
|
||||
}
|
||||
const res = await guiApi.showConfigDialog<IUrlRewriteDialogResult>(options)
|
||||
if (!res) return
|
||||
|
||||
const applyGlobalRules = res.applyGlobalRules === true
|
||||
const tempMatch = String(res.match ?? '').trim()
|
||||
const tempReplace = String(res.replace ?? '').trim()
|
||||
|
||||
const hasTempRuleInput = tempMatch.length > 0 || tempReplace.length > 0
|
||||
let tempRule: IUrlRewriteRule | null = null
|
||||
if (hasTempRuleInput) {
|
||||
tempRule = {
|
||||
match: tempMatch,
|
||||
replace: tempReplace,
|
||||
enable: true,
|
||||
global: res.global === true,
|
||||
ignoreCase: res.ignoreCase === true
|
||||
}
|
||||
}
|
||||
|
||||
if (tempRule) {
|
||||
try {
|
||||
validateRuleOrThrow(tempRule)
|
||||
} catch (e: any) {
|
||||
guiApi.showNotification({
|
||||
title: T('GALLERY_URL_REWRITE_TITLE'),
|
||||
body: e.message
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!applyGlobalRules && !tempRule) {
|
||||
guiApi.showNotification({
|
||||
title: T('GALLERY_URL_REWRITE_TITLE'),
|
||||
body: T('GALLERY_URL_REWRITE_NO_RULES_TO_APPLY')
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let shouldSaveTempRule = false
|
||||
if (tempRule) {
|
||||
const saveRes = await guiApi.showMessageBox({
|
||||
title: T('GALLERY_URL_REWRITE_TITLE'),
|
||||
message: T('GALLERY_URL_REWRITE_SAVE_TEMP_RULE_PROMPT'),
|
||||
type: 'info',
|
||||
buttons: [
|
||||
T('GALLERY_URL_REWRITE_APPLY_AND_SAVE'),
|
||||
T('GALLERY_URL_REWRITE_APPLY_ONLY'),
|
||||
T('CANCEL')
|
||||
]
|
||||
})
|
||||
if (saveRes.result === 2) return
|
||||
shouldSaveTempRule = saveRes.result === 0
|
||||
}
|
||||
|
||||
const rulesToApply: IUrlRewriteRule[] = [
|
||||
...(tempRule ? [tempRule] : []),
|
||||
...(applyGlobalRules ? globalRules : [])
|
||||
]
|
||||
|
||||
const changedList = selectedList.map((item) => {
|
||||
const current = item.imgUrl || ''
|
||||
if (!current) return false
|
||||
const next = applyFirstMatchRewrite(ctx, item, rulesToApply)
|
||||
if (next.imgUrl === current) return false
|
||||
return {
|
||||
id: item.id,
|
||||
imgUrl: next.imgUrl,
|
||||
originImgUrl: next.originImgUrl
|
||||
} as ImgInfo
|
||||
}).filter(Boolean) as ImgInfo[]
|
||||
|
||||
if (changedList.length === 0) {
|
||||
guiApi.showNotification({
|
||||
title: T('GALLERY_URL_REWRITE_RESULT_TITLE'),
|
||||
body: T('GALLERY_URL_REWRITE_NO_CHANGES')
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (shouldSaveTempRule && tempRule) {
|
||||
const nextGlobalRules = [...globalRules, tempRule]
|
||||
ctx.saveConfig({
|
||||
'settings.urlRewrite.rules': nextGlobalRules
|
||||
})
|
||||
}
|
||||
|
||||
const updateRes = await guiApi.galleryDB.updateMany(changedList)
|
||||
guiApi.showNotification({
|
||||
title: T('GALLERY_URL_REWRITE_RESULT_TITLE'),
|
||||
body: `${T('SUCCESS')}: ${updateRes.success} ${T('FAILED')}: ${updateRes.total - updateRes.success}`
|
||||
})
|
||||
}
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { galleryMenu as changeURLGalleryMenu } from './changeURL'
|
||||
export const builtInGalleryToolboxMenu = () => {
|
||||
const menuList = [...changeURLGalleryMenu()]
|
||||
|
||||
return menuList
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { IRPCActionType, IWindowList } from '~/universal/types/enum'
|
||||
import { RPCRouter } from '../../router'
|
||||
import windowManager from '~/main/apis/app/window/windowManager'
|
||||
import { galleryMenuListManager } from './menuListManager'
|
||||
|
||||
const galleryToolboxRouter = new RPCRouter()
|
||||
|
||||
galleryToolboxRouter.add(IRPCActionType.GET_GALLERY_MENU_LIST, async (args) => {
|
||||
const [selectedList] = args as IGetGalleryMenuListArgs
|
||||
const win = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const menu = galleryMenuListManager.getMenu(selectedList)
|
||||
|
||||
menu.popup({
|
||||
window: win
|
||||
})
|
||||
})
|
||||
|
||||
export {
|
||||
galleryToolboxRouter
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Menu, MenuItemConstructorOptions } from 'electron'
|
||||
import { builtInGalleryToolboxMenu } from './builtIn'
|
||||
import picgo from '@core/picgo'
|
||||
import GuiApi from 'apis/gui'
|
||||
import windowManager from '~/main/apis/app/window/windowManager'
|
||||
import { IRPCActionType, IWindowList } from '~/universal/types/enum'
|
||||
import logger from '~/main/apis/core/picgo/logger'
|
||||
|
||||
class GalleryMenuListManager {
|
||||
private menuList: MenuItemConstructorOptions[] = []
|
||||
private menu: Menu | null = null
|
||||
|
||||
private getBuiltInMenuList (selectedList: IGalleryItem[]): MenuItemConstructorOptions[] {
|
||||
const builtInMenu = builtInGalleryToolboxMenu().map(item => {
|
||||
return {
|
||||
label: item.label,
|
||||
async click () {
|
||||
try {
|
||||
await item.handle(picgo, GuiApi.getInstance(), selectedList)
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
} finally {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)?.webContents.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) as MenuItemConstructorOptions[]
|
||||
this.menuList = [...builtInMenu]
|
||||
|
||||
return this.menuList
|
||||
}
|
||||
|
||||
private getMenuItemList (selectedList: IGalleryItem[]) {
|
||||
// current only support built-in menu
|
||||
return this.getBuiltInMenuList(selectedList)
|
||||
}
|
||||
|
||||
public getMenu (selectedList: IGalleryItem[]): Menu {
|
||||
this.menu = Menu.buildFromTemplate(this.getMenuItemList(selectedList))
|
||||
return this.menu
|
||||
}
|
||||
}
|
||||
|
||||
const galleryMenuListManager = new GalleryMenuListManager()
|
||||
|
||||
export {
|
||||
galleryMenuListManager
|
||||
}
|
||||
@@ -2,6 +2,9 @@ import { IRPCActionType, IWindowList } from '~/universal/types/enum'
|
||||
import { RPCRouter } from '../router'
|
||||
import { app, clipboard, shell } from 'electron'
|
||||
import windowManager from '~/main/apis/app/window/windowManager'
|
||||
import { handleMenubarIcon } from '~/main/apis/app/system'
|
||||
import { PICGO_NOTIFICATION_CLICKED } from '~/universal/events/constants'
|
||||
import { showNotification } from '~/main/utils/common'
|
||||
|
||||
const systemRouter = new RPCRouter()
|
||||
|
||||
@@ -20,7 +23,7 @@ systemRouter
|
||||
})
|
||||
.add(IRPCActionType.SHOW_DOCK_ICON, async (args) => {
|
||||
const [visible] = args as IShowDockIconArgs
|
||||
app.dock[visible ? 'show' : 'hide']()
|
||||
app.dock?.[visible ? 'show' : 'hide']()
|
||||
const win = windowManager.get(IWindowList.SETTING_WINDOW)
|
||||
if (!visible) {
|
||||
win?.show()
|
||||
@@ -30,6 +33,28 @@ systemRouter
|
||||
win?.setSkipTaskbar(false)
|
||||
}
|
||||
})
|
||||
.add(IRPCActionType.SHOW_MENUBAR_ICON, async (args) => {
|
||||
const [visible] = args as IShowMenubarIconArgs
|
||||
handleMenubarIcon(visible)
|
||||
})
|
||||
.add(IRPCActionType.SHOW_NOTIFICATION, async (args, event) => {
|
||||
const [title, body, id] = args as IShowNotificationArgs
|
||||
|
||||
const options: IPrivateShowNotificationOption = {
|
||||
title,
|
||||
body
|
||||
}
|
||||
|
||||
if (id) {
|
||||
options.callback = () => {
|
||||
if (!event.sender.isDestroyed()) {
|
||||
event.sender.send(PICGO_NOTIFICATION_CLICKED, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showNotification(options)
|
||||
})
|
||||
|
||||
export {
|
||||
systemRouter
|
||||
|
||||
@@ -11,7 +11,7 @@ const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_
|
||||
const defaultClipboardImagePath = path.join(defaultConfigPath, CLIPBOARD_IMAGE_FOLDER)
|
||||
|
||||
export const checkClipboardUploadMap: IToolboxCheckerMap<
|
||||
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
|
||||
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
|
||||
> = {
|
||||
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async (event) => {
|
||||
sendToolboxRes(event, {
|
||||
@@ -51,7 +51,7 @@ IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
|
||||
}
|
||||
|
||||
export const fixClipboardUploadMap: IToolboxFixMap<
|
||||
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
|
||||
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
|
||||
> = {
|
||||
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async () => {
|
||||
const configFilePath = dbPathChecker()
|
||||
|
||||
@@ -2,8 +2,8 @@ import fs from 'fs-extra'
|
||||
import { IpcMainEvent } from 'electron'
|
||||
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
|
||||
import { sendToolboxResWithType } from './utils'
|
||||
import { dbPathChecker } from '~/main/apis/core/datastore/dbChecker'
|
||||
import { GalleryDB, DB_PATH } from '~/main/apis/core/datastore'
|
||||
import { dbPathChecker, getGalleryDBPath } from '~/main/apis/core/datastore/dbChecker'
|
||||
import { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import path from 'path'
|
||||
import { T } from '~/main/i18n'
|
||||
|
||||
@@ -40,20 +40,21 @@ IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.LOADING
|
||||
})
|
||||
const { dbPath } = getGalleryDBPath()
|
||||
const galleryDB = GalleryDB.getInstance()
|
||||
if (galleryDB.errorList.length === 0) {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: T('TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS', {
|
||||
path: DB_PATH
|
||||
path: dbPath
|
||||
}),
|
||||
value: path.dirname(DB_PATH)
|
||||
value: path.dirname(dbPath)
|
||||
})
|
||||
} else {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: T('TOOLBOX_CHECK_GALLERY_FILE_BROKEN_TIPS'),
|
||||
value: path.dirname(DB_PATH)
|
||||
value: path.dirname(dbPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -75,7 +76,7 @@ IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
|
||||
},
|
||||
[IToolboxItemType.IS_GALLERY_FILE_BROKEN]: async () => {
|
||||
try {
|
||||
fs.unlinkSync(DB_PATH)
|
||||
fs.unlinkSync(getGalleryDBPath().dbPath)
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ const getProxy = (proxyStr: string): AxiosRequestConfig['proxy'] | false => {
|
||||
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
|
||||
|
||||
export const checkProxyMap: IToolboxCheckerMap<
|
||||
IToolboxItemType.HAS_PROBLEM_WITH_PROXY
|
||||
IToolboxItemType.HAS_PROBLEM_WITH_PROXY
|
||||
> = {
|
||||
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async (event) => {
|
||||
sendToolboxRes(event, {
|
||||
|
||||
@@ -2,19 +2,20 @@ import yaml from 'js-yaml'
|
||||
import { ObjectAdapter, I18n } from '@picgo/i18n'
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { getStaticPath } from '#/utils/staticPath'
|
||||
import { builtinI18nList } from '#/i18n'
|
||||
|
||||
class I18nManager {
|
||||
private i18n: I18n | null = null
|
||||
private builtinI18nFolder = path.join(__static, 'i18n')
|
||||
private outterI18nFolder = ''
|
||||
private builtinI18nFolder = getStaticPath('i18n')
|
||||
private outerI18nFolder = ''
|
||||
private localesMap: Map<string, ILocales> = new Map()
|
||||
private currentLanguage: string = 'zh-CN'
|
||||
readonly defaultLanguage: string = 'zh-CN'
|
||||
private i18nFileList: II18nItem[] = builtinI18nList
|
||||
|
||||
setOutterI18nFolder (folder: string) {
|
||||
this.outterI18nFolder = folder
|
||||
setOuterI18nFolder (folder: string) {
|
||||
this.outerI18nFolder = folder
|
||||
}
|
||||
|
||||
addI18nFile (file: string, label: string) {
|
||||
@@ -30,7 +31,7 @@ class I18nManager {
|
||||
}
|
||||
let localesPath = path.join(this.builtinI18nFolder, `${lang}.yml`)
|
||||
if (!fs.existsSync(localesPath)) {
|
||||
localesPath = path.join(this.outterI18nFolder, `${lang}.yml`)
|
||||
localesPath = path.join(this.outerI18nFolder, `${lang}.yml`)
|
||||
if (!fs.existsSync(localesPath)) {
|
||||
localesPath = path.join(this.builtinI18nFolder, `${this.defaultLanguage}.yml`)
|
||||
}
|
||||
|
||||
+35
-22
@@ -2,12 +2,8 @@ import './errorHandler'
|
||||
import {
|
||||
app,
|
||||
globalShortcut,
|
||||
protocol,
|
||||
Notification
|
||||
protocol
|
||||
} from 'electron'
|
||||
import {
|
||||
createProtocol
|
||||
} from 'vue-cli-plugin-electron-builder/lib'
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
import beforeOpen from '~/main/utils/beforeOpen'
|
||||
import ipcList from '~/main/events/ipcList'
|
||||
@@ -19,17 +15,17 @@ import {
|
||||
migrateGalleryFromVersion230
|
||||
} from '~/main/migrate'
|
||||
import {
|
||||
uploadChoosedFiles,
|
||||
uploadSelectedFiles,
|
||||
uploadClipboardFiles
|
||||
} from 'apis/app/uploader/apis'
|
||||
import {
|
||||
createTray, handleDockIcon
|
||||
handleDockIcon, handleMenubarIcon
|
||||
} from 'apis/app/system'
|
||||
import server from '~/main/server/index'
|
||||
import updateChecker from '~/main/utils/updateChecker'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
import { getUploadFiles } from '~/main/utils/handleArgv'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import bus from '@core/bus'
|
||||
import logger from 'apis/core/picgo/logger'
|
||||
import picgo from 'apis/core/picgo'
|
||||
@@ -37,8 +33,11 @@ import fixPath from './fixPath'
|
||||
import { initI18n } from '~/main/utils/handleI18n'
|
||||
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
|
||||
import { isMacOS } from '../utils/getMacOSVersion'
|
||||
import { isWindowShouldShowOnStartup } from '../apis/app/window/windowList'
|
||||
import { showNotification } from '../utils/common'
|
||||
import { initStaticPath, isDev } from '../utils/env'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
const isDevelopment = isDev
|
||||
|
||||
const handleStartUpFiles = (argv: string[], cwd: string) => {
|
||||
const files = getUploadFiles(argv, cwd, logger)
|
||||
@@ -49,7 +48,7 @@ const handleStartUpFiles = (argv: string[], cwd: string) => {
|
||||
} else {
|
||||
logger.info('cli -> uploading files from cli', ...files.map(item => item.path))
|
||||
const win = windowManager.getAvailableWindow()
|
||||
uploadChoosedFiles(win.webContents, files)
|
||||
uploadSelectedFiles(win.webContents, files)
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
@@ -66,27 +65,40 @@ class LifeCycle {
|
||||
initI18n()
|
||||
ipcList.listen()
|
||||
busEventList.listen()
|
||||
updateShortKeyFromVersion212(db, db.get('settings.shortKey'))
|
||||
await migrateGalleryFromVersion230(db, GalleryDB.getInstance(), picgo)
|
||||
updateShortKeyFromVersion212(picgo)
|
||||
await migrateGalleryFromVersion230(GalleryDB.getInstance(), picgo)
|
||||
}
|
||||
|
||||
private onReady () {
|
||||
const readyFunction = async () => {
|
||||
console.log('on ready')
|
||||
createProtocol('picgo')
|
||||
if (isDevelopment && !process.env.IS_TEST) {
|
||||
// Install Vue Devtools
|
||||
try {
|
||||
await installExtension(VUEJS_DEVTOOLS)
|
||||
} catch (e: any) {
|
||||
console.error('Vue Devtools failed to install:', e.toString())
|
||||
console.error('Vue Devtools failed to install:', e?.toString())
|
||||
}
|
||||
}
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
createTray()
|
||||
const settingWindow = windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
settingWindow?.once('show', () => {
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.SETTING_WINDOW_OPEN)
|
||||
})
|
||||
if (isWindowShouldShowOnStartup(IWindowList.SETTING_WINDOW)) {
|
||||
settingWindow?.show()
|
||||
settingWindow?.focus()
|
||||
}
|
||||
if (!isMacOS) {
|
||||
if (isWindowShouldShowOnStartup(IWindowList.MINI_WINDOW)) {
|
||||
const miniWindow = windowManager.create(IWindowList.MINI_WINDOW)
|
||||
miniWindow?.show()
|
||||
miniWindow?.focus()
|
||||
}
|
||||
}
|
||||
handleMenubarIcon()
|
||||
handleDockIcon()
|
||||
db.set('needReload', false)
|
||||
picgo.saveConfig({ needReload: false })
|
||||
updateChecker()
|
||||
// 不需要阻塞
|
||||
process.nextTick(() => {
|
||||
@@ -100,8 +112,9 @@ class LifeCycle {
|
||||
if (global.notificationList && global.notificationList?.length > 0) {
|
||||
while (global.notificationList?.length) {
|
||||
const option = global.notificationList.pop()
|
||||
const notice = new Notification(option!)
|
||||
notice.show()
|
||||
if (option) {
|
||||
showNotification(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
await remoteNoticeHandler.init()
|
||||
@@ -125,7 +138,6 @@ class LifeCycle {
|
||||
}
|
||||
})
|
||||
app.on('activate', () => {
|
||||
createProtocol('picgo')
|
||||
if (!windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
}
|
||||
@@ -135,13 +147,13 @@ class LifeCycle {
|
||||
// click dock to open setting window
|
||||
if (isMacOS) {
|
||||
handleDockIcon()
|
||||
if (db.get('settings.showDockIcon') !== false) {
|
||||
if (picgo.getConfig<boolean>('settings.showDockIcon') !== false) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)?.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: db.get('settings.autoStart') || false
|
||||
openAtLogin: picgo.getConfig<boolean>('settings.autoStart') || false
|
||||
})
|
||||
if (process.platform === 'win32') {
|
||||
app.setAppUserModelId('com.molunerfinn.picgo')
|
||||
@@ -185,6 +197,7 @@ class LifeCycle {
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
initStaticPath()
|
||||
await this.beforeReady()
|
||||
this.onReady()
|
||||
this.onRunning()
|
||||
|
||||
+20
-14
@@ -1,11 +1,12 @@
|
||||
import { DBStore } from '@picgo/store'
|
||||
import ConfigStore from '~/main/apis/core/datastore'
|
||||
import path from 'path'
|
||||
import fse from 'fs-extra'
|
||||
import { PicGo as PicGoCore } from 'picgo'
|
||||
import { T } from '~/main/i18n'
|
||||
import { SHORTKEY_COMMAND_UPLOAD } from 'apis/core/bus/constants'
|
||||
// from v2.1.2
|
||||
const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
|
||||
const updateShortKeyFromVersion212 = (picgo: PicGoCore) => {
|
||||
const shortKeyConfig = picgo.getConfig<IShortKeyConfigs | IOldShortKeyConfigs | undefined>('settings.shortKey')
|
||||
// #557 极端情况可能会出现配置不存在,需要重新写入
|
||||
if (shortKeyConfig === undefined) {
|
||||
const defaultShortKeyConfig = {
|
||||
@@ -14,33 +15,38 @@ const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IS
|
||||
name: 'upload',
|
||||
label: T('QUICK_UPLOAD')
|
||||
}
|
||||
db.set('settings.shortKey[picgo:upload]', defaultShortKeyConfig)
|
||||
picgo.saveConfig({
|
||||
[`settings.shortKey[${SHORTKEY_COMMAND_UPLOAD}]`]: defaultShortKeyConfig
|
||||
})
|
||||
return true
|
||||
}
|
||||
if (shortKeyConfig.upload) {
|
||||
// @ts-ignore
|
||||
shortKeyConfig['picgo:upload'] = {
|
||||
if (typeof (shortKeyConfig as IOldShortKeyConfigs).upload === 'string') {
|
||||
const oldKey = (shortKeyConfig as IOldShortKeyConfigs).upload
|
||||
const nextConfig = Object.fromEntries(
|
||||
Object.entries(shortKeyConfig).filter(([key]) => key !== 'upload')
|
||||
) as IShortKeyConfigs
|
||||
nextConfig[SHORTKEY_COMMAND_UPLOAD] = {
|
||||
enable: true,
|
||||
key: shortKeyConfig.upload,
|
||||
key: oldKey,
|
||||
name: 'upload',
|
||||
label: T('QUICK_UPLOAD')
|
||||
}
|
||||
// @ts-ignore
|
||||
delete shortKeyConfig.upload
|
||||
db.set('settings.shortKey', shortKeyConfig)
|
||||
picgo.saveConfig({
|
||||
'settings.shortKey': nextConfig
|
||||
})
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const migrateGalleryFromVersion230 = async (configDB: typeof ConfigStore, galleryDB: DBStore, picgo: PicGoCore) => {
|
||||
const originGallery: ImgInfo[] = picgo.getConfig('uploaded')
|
||||
const migrateGalleryFromVersion230 = async (galleryDB: DBStore, picgo: PicGoCore) => {
|
||||
const originGallery = picgo.getConfig<ImgInfo[] | undefined>('uploaded')
|
||||
// if hasMigrate, we don't need to migrate
|
||||
const hasMigrate: boolean = configDB.get('__migrateUploaded')
|
||||
const hasMigrate = picgo.getConfig<boolean | undefined>('__migrateUploaded') === true
|
||||
if (hasMigrate) {
|
||||
return
|
||||
}
|
||||
const configPath = configDB.getConfigPath()
|
||||
const configPath = picgo.configPath
|
||||
const configBakPath = path.join(path.dirname(configPath), 'config.bak.json')
|
||||
// migrate gallery from config to gallery db
|
||||
if (originGallery && Array.isArray(originGallery) && originGallery?.length > 0) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import axios from 'axios'
|
||||
import formUploader from './middlewares/formUploader'
|
||||
|
||||
class Server {
|
||||
private httpServer: http.Server
|
||||
@@ -56,6 +57,8 @@ class Server {
|
||||
success: false
|
||||
}
|
||||
})
|
||||
} else if (formUploader.isFileUpload(request)) {
|
||||
formUploader.handleFileUpload(request, response)
|
||||
} else {
|
||||
let body: string = ''
|
||||
let postObj: IObj
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import http from 'http'
|
||||
import multer from 'multer'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { dbPathDir } from 'apis/core/datastore/dbChecker'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { handleResponse } from '../utils'
|
||||
import routers from '../routerManager'
|
||||
import { FORM_IMAGE_FOLDER } from '~/universal/utils/static'
|
||||
|
||||
// Multer 错误类型定义
|
||||
export interface MulterError extends Error {
|
||||
code: string
|
||||
field?: string
|
||||
storageErrors?: any[]
|
||||
}
|
||||
|
||||
// Multer 中间件适配器类型
|
||||
export type MulterMiddleware = (
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
callback: (error?: MulterError) => void
|
||||
) => void
|
||||
|
||||
// 扩展 multer 函数的返回类型
|
||||
declare module 'multer' {
|
||||
interface Multer {
|
||||
array(fieldname: string, maxCount?: number): MulterMiddleware;
|
||||
single(fieldname: string): MulterMiddleware;
|
||||
fields(fields: { name: string; maxCount?: number }[]): MulterMiddleware;
|
||||
none(): MulterMiddleware;
|
||||
}
|
||||
}
|
||||
|
||||
class FormUploader {
|
||||
private upload!: multer.Multer
|
||||
private formImagesPath!: string
|
||||
|
||||
constructor () {
|
||||
this.initializeStorage()
|
||||
this.setupMulter()
|
||||
}
|
||||
|
||||
private initializeStorage () {
|
||||
const STORE_PATH = dbPathDir()
|
||||
this.formImagesPath = path.join(STORE_PATH, FORM_IMAGE_FOLDER)
|
||||
if (!fs.existsSync(this.formImagesPath)) {
|
||||
fs.mkdirSync(this.formImagesPath, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
private setupMulter () {
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req: http.IncomingMessage, file: Express.Multer.File, cb: (error: Error | null, destination: string) => void) => {
|
||||
cb(null, this.formImagesPath)
|
||||
},
|
||||
filename: (req: http.IncomingMessage, file: Express.Multer.File, cb: (error: Error | null, filename: string) => void) => {
|
||||
cb(null, file.originalname || Date.now() + path.extname(file.originalname))
|
||||
}
|
||||
})
|
||||
|
||||
this.upload = multer({
|
||||
storage
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文件上传的中间件
|
||||
*/
|
||||
public handleFileUpload = (request: http.IncomingMessage, response: http.ServerResponse): void => {
|
||||
logger.info('[PicGo Server] handling file upload')
|
||||
|
||||
this.upload.array('files')(request, response, async (err?: MulterError) => {
|
||||
if (err) {
|
||||
logger.error('[PicGo Server] file upload error', err)
|
||||
return handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'File upload failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const files = request.files
|
||||
if (!files || files.length === 0) {
|
||||
return handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'No files were uploaded'
|
||||
}
|
||||
})
|
||||
}
|
||||
const filePaths = files.map((file) => file.path)
|
||||
logger.info('[PicGo Server] files uploaded: ' + filePaths.join(', '))
|
||||
|
||||
const handler = routers.getHandler(request.url!)
|
||||
handler!({
|
||||
list: filePaths,
|
||||
response
|
||||
})
|
||||
} catch (err: any) {
|
||||
logger.error('[PicGo Server] process upload files error', err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'Failed to process uploaded files'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查请求是否为文件上传
|
||||
*/
|
||||
public isFileUpload (request: http.IncomingMessage): boolean {
|
||||
return !!(request.headers['content-type'] && request.headers['content-type'].includes('multipart/form-data'))
|
||||
}
|
||||
}
|
||||
|
||||
export default new FormUploader()
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
} from './utils'
|
||||
import logger from '@core/picgo/logger'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
|
||||
import { uploadSelectedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
|
||||
import path from 'path'
|
||||
import { dbPathDir } from 'apis/core/datastore/dbChecker'
|
||||
const STORE_PATH = dbPathDir()
|
||||
@@ -51,7 +51,7 @@ router.post('/upload', async ({
|
||||
}
|
||||
})
|
||||
const win = windowManager.getAvailableWindow()
|
||||
const res = await uploadChoosedFiles(win.webContents, pathList)
|
||||
const res = await uploadSelectedFiles(win.webContents, pathList)
|
||||
logger.info('[PicGo Server] upload result', res.join(' ; '))
|
||||
if (res.length) {
|
||||
handleResponse({
|
||||
|
||||
@@ -5,6 +5,7 @@ import { dbPathChecker } from 'apis/core/datastore/dbChecker'
|
||||
import yaml from 'js-yaml'
|
||||
import { i18nManager } from '~/main/i18n'
|
||||
// import { ILocales } from '~/universal/types/i18n'
|
||||
import { getStaticPath } from '#/utils/staticPath'
|
||||
|
||||
const configPath = dbPathChecker()
|
||||
const CONFIG_DIR = path.dirname(configPath)
|
||||
@@ -16,6 +17,29 @@ function beforeOpen () {
|
||||
resolveClipboardImageGenerator()
|
||||
resolveOtherI18nFiles()
|
||||
}
|
||||
function copyFileOutsideOfElectronAsar (
|
||||
sourceInAsarArchive: string,
|
||||
destOutsideAsarArchive: string
|
||||
) {
|
||||
if (fs.existsSync(sourceInAsarArchive)) {
|
||||
// file will be copied
|
||||
if (fs.statSync(sourceInAsarArchive).isFile()) {
|
||||
const file = destOutsideAsarArchive
|
||||
const dir = path.dirname(file)
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
fs.writeFileSync(file, fs.readFileSync(sourceInAsarArchive))
|
||||
} else if (fs.statSync(sourceInAsarArchive).isDirectory()) {
|
||||
fs.readdirSync(sourceInAsarArchive).forEach(function (fileOrFolderName) {
|
||||
copyFileOutsideOfElectronAsar(
|
||||
`${sourceInAsarArchive}/${fileOrFolderName}`,
|
||||
`${destOutsideAsarArchive}/${fileOrFolderName}`
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS 右键菜单
|
||||
@@ -26,7 +50,7 @@ function resolveMacWorkFlow () {
|
||||
return true
|
||||
} else {
|
||||
try {
|
||||
fs.copySync(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
|
||||
copyFileOutsideOfElectronAsar(getStaticPath('Upload pictures with PicGo.workflow'), dest)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
@@ -64,16 +88,16 @@ function resolveClipboardImageGenerator () {
|
||||
|
||||
function getClipboardFiles () {
|
||||
const files = [
|
||||
'/linux.sh',
|
||||
'/mac.applescript',
|
||||
'/windows.ps1',
|
||||
'/windows10.ps1',
|
||||
'/wsl.sh'
|
||||
'linux.sh',
|
||||
'mac.applescript',
|
||||
'windows.ps1',
|
||||
'windows10.ps1',
|
||||
'wsl.sh'
|
||||
]
|
||||
|
||||
return files.map(item => {
|
||||
return {
|
||||
origin: path.join(__static, item),
|
||||
origin: getStaticPath(item),
|
||||
dest: path.join(CONFIG_DIR, item)
|
||||
}
|
||||
})
|
||||
@@ -88,7 +112,7 @@ function resolveOtherI18nFiles () {
|
||||
if (!fs.pathExistsSync(i18nFolder)) {
|
||||
fs.mkdirSync(i18nFolder)
|
||||
}
|
||||
i18nManager.setOutterI18nFolder(i18nFolder)
|
||||
i18nManager.setOuterI18nFolder(i18nFolder)
|
||||
const i18nFiles = fs.readdirSync(path.join(CONFIG_DIR, 'i18n'), {
|
||||
withFileTypes: true
|
||||
})
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { getFormImageFolderPath } from '@core/datastore/dbChecker'
|
||||
import logger from '@core/picgo/logger'
|
||||
|
||||
export const cleanupFormUploaderFiles = (fileInfoList?: string[] | ImgInfo[]): void => {
|
||||
const formImageFolderPath = getFormImageFolderPath()
|
||||
if (Array.isArray(fileInfoList)) {
|
||||
fileInfoList.forEach(async fileInfo => {
|
||||
const filePath = typeof fileInfo === 'string' ? fileInfo : fileInfo?.imgPath
|
||||
if (!filePath) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 检查文件路径是否在 formImageFolderPath 目录下
|
||||
const relativePath = path.relative(formImageFolderPath, filePath)
|
||||
const isWithinFolder = !relativePath.startsWith('..') && !path.isAbsolute(relativePath)
|
||||
|
||||
if (isWithinFolder && await fs.pathExists(filePath)) {
|
||||
await fs.remove(filePath)
|
||||
logger.info(`[PicGo] Deleted temp file: ${filePath}`)
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error('[PicGo] Failed to delete temp file', filePath, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+101
-27
@@ -1,9 +1,13 @@
|
||||
import fs from 'fs-extra'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { clipboard, Notification, dialog } from 'electron'
|
||||
import { handleUrlEncode } from '~/universal/utils/common'
|
||||
import { readClipboardFilePaths } from 'clip-filepaths'
|
||||
import crypto from 'node:crypto'
|
||||
import picgo from '@core/picgo'
|
||||
|
||||
export const handleCopyUrl = (str: string): void => {
|
||||
if (db.get('settings.autoCopy') !== false) {
|
||||
if (picgo.getConfig<boolean>('settings.autoCopyUrl') !== false) {
|
||||
clipboard.writeText(str)
|
||||
}
|
||||
}
|
||||
@@ -15,21 +19,31 @@ export const handleCopyUrl = (str: string): void => {
|
||||
export const showNotification = (options: IPrivateShowNotificationOption = {
|
||||
title: '',
|
||||
body: '',
|
||||
text: '',
|
||||
clickToCopy: false,
|
||||
copyContent: '',
|
||||
clickFn: () => {}
|
||||
callback: () => {}
|
||||
}) => {
|
||||
if (options.text) {
|
||||
logger.info('[PicGo Notification]', options.text)
|
||||
clipboard.writeText(options.text)
|
||||
}
|
||||
|
||||
const title = options.title || ''
|
||||
const body = options.body || options.text || ''
|
||||
const silent = picgo.getConfig('settings.notificationSound') === false
|
||||
const notification = new Notification({
|
||||
title: options.title,
|
||||
body: options.body
|
||||
title,
|
||||
body,
|
||||
silent
|
||||
// icon: options.icon || undefined
|
||||
})
|
||||
const handleClick = () => {
|
||||
if (options.clickToCopy) {
|
||||
clipboard.writeText(options.copyContent || options.body)
|
||||
clipboard.writeText(options.copyContent || body)
|
||||
}
|
||||
if (options.clickFn) {
|
||||
options.clickFn()
|
||||
if (options.callback) {
|
||||
options.callback()
|
||||
}
|
||||
}
|
||||
notification.once('click', handleClick)
|
||||
@@ -52,7 +66,7 @@ export const showMessageBox = (options: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const calcDurationRange = (duration: number) => {
|
||||
export const calcUploadProcessDurationRange = (duration: number) => {
|
||||
if (duration < 1000) {
|
||||
return 500
|
||||
} else if (duration < 1500) {
|
||||
@@ -76,6 +90,44 @@ export const calcDurationRange = (duration: number) => {
|
||||
return 100000
|
||||
}
|
||||
|
||||
// 1 2 3 4 5 6 7 8 9 10 20 30 40 50 60 70 80 90 100 200 300 ...
|
||||
export const calcUploadBigFileSizeRange = (fileSizeMB: number) => {
|
||||
if (fileSizeMB < 10) {
|
||||
// 3.2 -> 3, 3.6 -> 4
|
||||
const result = Math.round(fileSizeMB)
|
||||
return result === 0 && fileSizeMB > 0 ? 1 : result
|
||||
}
|
||||
else if (fileSizeMB < 100) {
|
||||
// 13 -> 1.3 -> 1 -> 10
|
||||
// 17 -> 1.7 -> 2 -> 20
|
||||
return Math.round(fileSizeMB / 10) * 10
|
||||
}
|
||||
else {
|
||||
// 135 -> 1.35 -> 1 -> 100
|
||||
// 160 -> 1.60 -> 2 -> 200
|
||||
return Math.round(fileSizeMB / 100) * 100
|
||||
}
|
||||
}
|
||||
|
||||
// 1 2 3 4 5 6 7 8 9 10 20 30 40 50 60 70 80 90 100 200 300 ...
|
||||
export const calcVideoDurationRange = (durationSec: number) => {
|
||||
if (durationSec < 10) {
|
||||
// 3.2 -> 3, 3.6 -> 4
|
||||
const result = Math.round(durationSec)
|
||||
return result === 0 && durationSec > 0 ? 1 : result
|
||||
}
|
||||
else if (durationSec < 100) {
|
||||
// 13 -> 1.3 -> 1 -> 10
|
||||
// 17 -> 1.7 -> 2 -> 20
|
||||
return Math.round(durationSec / 10) * 10
|
||||
}
|
||||
else {
|
||||
// 135 -> 1.35 -> 1 -> 100
|
||||
// 160 -> 1.60 -> 2 -> 200
|
||||
return Math.round(durationSec / 100) * 100
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS public.file-url will get encoded file path,
|
||||
* so we need to decode it
|
||||
@@ -96,24 +148,46 @@ export const ensureFilePath = (filePath: string, prefix = 'file://'): string =>
|
||||
* for builtin clipboard to get image path from clipboard
|
||||
* @returns
|
||||
*/
|
||||
export const getClipboardFilePath = (): string => {
|
||||
// TODO: linux support
|
||||
const img = clipboard.readImage()
|
||||
if (img.isEmpty()) {
|
||||
if (process.platform === 'win32') {
|
||||
const imgPath = clipboard.readBuffer('FileNameW')?.toString('ucs2')?.replace(RegExp(String.fromCharCode(0), 'g'), '')
|
||||
if (imgPath) {
|
||||
return imgPath
|
||||
}
|
||||
export const getClipboardFilePathList = (): string[] => {
|
||||
const { filePaths } = readClipboardFilePaths()
|
||||
return filePaths
|
||||
}
|
||||
|
||||
export const handleUrlEncodeWithSetting = (url: string) => {
|
||||
if (picgo.getConfig<boolean>('settings.encodeOutputURL') === true) {
|
||||
url = handleUrlEncode(url)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
export const replaceHost = (url: string, oldHost: string, newHost: string) => {
|
||||
try {
|
||||
const parsedUrl = new URL(url)
|
||||
if (parsedUrl.host === oldHost) {
|
||||
parsedUrl.host = newHost
|
||||
}
|
||||
} else {
|
||||
if (process.platform === 'darwin') {
|
||||
let imgPath = clipboard.read('public.file-url') // will get file://xxx/xxx
|
||||
imgPath = ensureFilePath(imgPath)
|
||||
if (imgPath) {
|
||||
return imgPath.replace('file://', '')
|
||||
}
|
||||
}
|
||||
return parsedUrl.toString()
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
export const getHost = (url: string = '') => {
|
||||
try {
|
||||
const parsedUrl = new URL(url)
|
||||
return parsedUrl.host
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* remove protocol and suffix
|
||||
*/
|
||||
export const removeProtocolAndSuffix = (url: string = '') => {
|
||||
return url.replace(/^(https?:\/\/)?/, '').replace(/\/$/, '')
|
||||
}
|
||||
|
||||
export const md5 = (str: string): string => {
|
||||
return crypto.createHash('md5').update(str).digest('hex')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const MB = 1024 * 1024
|
||||
export const SECOND = 1000
|
||||
@@ -0,0 +1,132 @@
|
||||
import { ipcMain, IpcMainEvent, type WebContents } from "electron"
|
||||
import { deviceIdManager } from "./deviceId"
|
||||
import { REGISTER_DEVICE_ID, TALKING_DATA_EVENT } from "~/universal/events/constants"
|
||||
import type { IImgInfo } from "picgo"
|
||||
import { calcUploadBigFileSizeRange, calcUploadProcessDurationRange, calcVideoDurationRange } from "./common"
|
||||
import { app } from "electron/main"
|
||||
import picgo from "@core/picgo"
|
||||
import { getVideoDuration } from "@picgo/video-duration"
|
||||
import { MB, SECOND } from "./constants"
|
||||
|
||||
export interface IReportUploadDataOptions {
|
||||
fromClipboard: boolean;
|
||||
duration: number;
|
||||
outputList: IImgInfo[];
|
||||
}
|
||||
|
||||
class DataReportManager {
|
||||
private deviceId: string | null = null
|
||||
private hasRegisterDeviceID: boolean = false
|
||||
constructor () {
|
||||
this.init()
|
||||
this.handleRegisterDeviceID()
|
||||
}
|
||||
private handleRegisterDeviceID() {
|
||||
ipcMain.once(REGISTER_DEVICE_ID, (_evt: IpcMainEvent) => {
|
||||
this.hasRegisterDeviceID = true
|
||||
console.log('Device ID registered')
|
||||
})
|
||||
}
|
||||
private async init () {
|
||||
if (this.deviceId) return
|
||||
this.deviceId = await deviceIdManager.getId()
|
||||
}
|
||||
public async reportUploadData(webContents: WebContents, options: IReportUploadDataOptions) {
|
||||
await this.init()
|
||||
await this.registerDeviceID(webContents)
|
||||
const { fromClipboard, duration, outputList } = options
|
||||
const fileList = outputList.map(item => {
|
||||
return {
|
||||
fileName: item.fileName,
|
||||
filePath: item.filePath,
|
||||
mimeType: item.mimeType,
|
||||
size: item.size || 0
|
||||
}
|
||||
})
|
||||
const uploadEventData: ITalkingDataOptions = {
|
||||
EventId: 'upload',
|
||||
Label: '',
|
||||
MapKv: {
|
||||
by: fromClipboard ? 'clipboard' : 'files', // 上传剪贴板图片还是选择的文文件
|
||||
count: fileList.length, // 上传的数量
|
||||
duration: calcUploadProcessDurationRange(duration || 0), // 上传耗时
|
||||
type: picgo.getConfig<string>('picBed.uploader') || picgo.getConfig<string>('picBed.current') || 'smms',
|
||||
}
|
||||
}
|
||||
this.reportDataToWebContents(webContents, uploadEventData)
|
||||
fileList.forEach(async file => {
|
||||
if (file?.mimeType?.startsWith('video/') && file.filePath) {
|
||||
const metadata = await getVideoDuration(file.filePath)
|
||||
const sizeMB = metadata.size / MB
|
||||
const durationSec = (metadata.duration / SECOND) || 0
|
||||
const videoEventData: ITalkingDataOptions = {
|
||||
EventId: 'upload_video',
|
||||
Label: '',
|
||||
MapKv: {
|
||||
type: picgo.getConfig<string>('picBed.uploader') || picgo.getConfig<string>('picBed.current') || 'smms',
|
||||
mimeType: file.mimeType,
|
||||
sizeRange: calcUploadBigFileSizeRange(sizeMB),
|
||||
durationRange: calcVideoDurationRange(durationSec),
|
||||
}
|
||||
}
|
||||
this.reportDataToWebContents(webContents, videoEventData)
|
||||
} else if (file?.mimeType?.startsWith('image/') || fromClipboard) {
|
||||
const imageEventData: ITalkingDataOptions = {
|
||||
EventId: 'upload_image',
|
||||
Label: '',
|
||||
MapKv: {
|
||||
type: picgo.getConfig<string>('picBed.uploader') || picgo.getConfig<string>('picBed.current') || 'smms',
|
||||
mimeType: file.mimeType || 'image/png',
|
||||
sizeRange: calcUploadBigFileSizeRange(file.size / MB)
|
||||
}
|
||||
}
|
||||
this.reportDataToWebContents(webContents, imageEventData)
|
||||
} else {
|
||||
const otherEventData: ITalkingDataOptions = {
|
||||
EventId: 'upload_file',
|
||||
Label: '',
|
||||
MapKv: {
|
||||
type: picgo.getConfig<string>('picBed.uploader') || picgo.getConfig<string>('picBed.current') || 'smms',
|
||||
mimeType: file.mimeType || 'UNKNOWN',
|
||||
sizeRange: calcUploadBigFileSizeRange(file.size / MB)
|
||||
}
|
||||
}
|
||||
this.reportDataToWebContents(webContents, otherEventData)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public async registerDeviceID(webContents: WebContents) {
|
||||
if (this.hasRegisterDeviceID) return
|
||||
await this.init()
|
||||
webContents.send(REGISTER_DEVICE_ID, this.deviceId)
|
||||
}
|
||||
|
||||
private reportDataToWebContents(webContents: WebContents, data: ITalkingDataOptions) {
|
||||
webContents.send(TALKING_DATA_EVENT, {
|
||||
...data,
|
||||
MapKv: {
|
||||
...data.MapKv,
|
||||
deviceId: this.deviceId,
|
||||
version: app.getVersion(),
|
||||
area: this.getAreaFromTimezone()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private getAreaFromTimezone() {
|
||||
try {
|
||||
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
if (!timeZone) return 'UNKNOWN'
|
||||
|
||||
if (timeZone === 'Asia/Shanghai') return 'CN'
|
||||
|
||||
const region = timeZone.split('/')[0] // Asia, America, Europe
|
||||
return region
|
||||
} catch (e) {
|
||||
return 'UNKNOWN'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const dataReportManager = new DataReportManager()
|
||||
@@ -0,0 +1,62 @@
|
||||
import { networkInterfaceDefault } from 'systeminformation'
|
||||
import { md5 } from './common'
|
||||
import writeFile from 'write-file-atomic'
|
||||
import { DEVICE_ID_PATH } from './env'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
class DeviceIdManager {
|
||||
private deviceId: string | null = null
|
||||
|
||||
constructor() {
|
||||
this.init()
|
||||
}
|
||||
|
||||
private async init() {
|
||||
await this.loadDeviceId()
|
||||
}
|
||||
|
||||
private async getDeviceIdWithFallback(): Promise<string> {
|
||||
try {
|
||||
if (fs.existsSync(DEVICE_ID_PATH)) {
|
||||
const deviceId = await fs.readFile(DEVICE_ID_PATH, 'utf-8')
|
||||
if (deviceId && deviceId?.trim().length > 0) {
|
||||
return deviceId.trim()
|
||||
}
|
||||
}
|
||||
const netInterfaceMac = await networkInterfaceDefault()
|
||||
if (netInterfaceMac) {
|
||||
return md5(netInterfaceMac)
|
||||
} else {
|
||||
// random fallback
|
||||
return md5(`${new Date().getTime()}-${Math.random().toString(36).substring(2, 15)}`)
|
||||
}
|
||||
} catch (error) {
|
||||
// random fallback
|
||||
return md5(`${new Date().getTime()}-${Math.random().toString(36).substring(2, 15)}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async saveDeviceId(id: string): Promise<void> {
|
||||
try {
|
||||
await writeFile(DEVICE_ID_PATH, id, { encoding: 'utf-8' })
|
||||
} catch (error) {
|
||||
console.error('Failed to save device ID:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private async loadDeviceId(): Promise<string> {
|
||||
this.deviceId = await this.getDeviceIdWithFallback()
|
||||
await this.saveDeviceId(this.deviceId)
|
||||
return this.deviceId
|
||||
}
|
||||
|
||||
public async getId(): Promise<string> {
|
||||
if (this.deviceId) {
|
||||
return this.deviceId
|
||||
}
|
||||
this.deviceId = await this.loadDeviceId()
|
||||
return this.deviceId
|
||||
}
|
||||
}
|
||||
|
||||
export const deviceIdManager = new DeviceIdManager()
|
||||
@@ -0,0 +1,35 @@
|
||||
import path from 'path'
|
||||
import { app } from 'electron'
|
||||
import { pathToFileURL } from 'url'
|
||||
|
||||
export const isDev = !app.isPackaged
|
||||
|
||||
const defaultStaticPath = process.env.STATIC_PATH || (isDev
|
||||
? path.join(process.cwd(), 'public')
|
||||
: path.join(process.resourcesPath, 'public'))
|
||||
|
||||
process.env.STATIC_PATH = defaultStaticPath
|
||||
|
||||
const rendererHtmlPath = path.join(__dirname, '../renderer/index.html')
|
||||
|
||||
export const initStaticPath = () => defaultStaticPath
|
||||
|
||||
export const getRendererBaseUrl = () => {
|
||||
if (isDev && process.env.ELECTRON_RENDERER_URL) {
|
||||
return process.env.ELECTRON_RENDERER_URL
|
||||
}
|
||||
return pathToFileURL(rendererHtmlPath).toString()
|
||||
}
|
||||
|
||||
export const buildRendererUrl = (hash?: string) => {
|
||||
const base = getRendererBaseUrl()
|
||||
if (!hash) return base
|
||||
const cleanedHash = hash.startsWith('#') ? hash : `#${hash}`
|
||||
return `${base}${cleanedHash}`
|
||||
}
|
||||
|
||||
export const getStaticPath = () => process.env.STATIC_PATH || defaultStaticPath
|
||||
|
||||
// paths
|
||||
export const STORE_PATH = app.getPath('userData')
|
||||
export const DEVICE_ID_PATH = path.join(STORE_PATH, 'picgo-device-id')
|
||||
@@ -1,6 +1,6 @@
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import { i18nManager } from '~/main/i18n'
|
||||
export const initI18n = () => {
|
||||
const currentLanguage = db.get('settings.language') || 'zh-CN'
|
||||
const currentLanguage = picgo.getConfig<string>('settings.language') || 'zh-CN'
|
||||
i18nManager.setCurrentLanguage(currentLanguage)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { uuid } from 'uuidv4'
|
||||
import { trimValues } from '#/utils/common'
|
||||
import { simpleClone, trimValues } from '#/utils/common'
|
||||
import picgo from '@core/picgo'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IPicGoPluginConfig[] => {
|
||||
for (const i in config) {
|
||||
@@ -121,6 +121,29 @@ export const deleteUploaderConfig = (type: string, id: string): IUploaderConfigI
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* copy uploader config by type & id
|
||||
*/
|
||||
export const copyUploaderConfig = (type: string, id: string): IUploaderConfigItem | void => {
|
||||
const { configList, defaultId } = getUploaderConfigList(type)
|
||||
const existConfig = configList.find((item: IStringKeyMap) => item._id === id)
|
||||
if (!existConfig) {
|
||||
return
|
||||
}
|
||||
const copiedConfig = completeUploaderMetaConfig({
|
||||
...simpleClone(existConfig),
|
||||
_configName: `${existConfig._configName || 'Default'} - Copy`
|
||||
})
|
||||
configList.push(copiedConfig)
|
||||
picgo.saveConfig({
|
||||
[`uploader.${type}.configList`]: configList
|
||||
})
|
||||
return {
|
||||
configList,
|
||||
defaultId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* upgrade old uploader config to new format
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { IPasteStyle } from '#/types/enum'
|
||||
import db from 'apis/core/datastore'
|
||||
import { handleUrlEncode } from '~/universal/utils/common'
|
||||
import { handleUrlEncodeWithSetting } from './common'
|
||||
|
||||
const formatCustomLink = (customLink: string, item: ImgInfo) => {
|
||||
const fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')
|
||||
@@ -22,10 +21,7 @@ const formatCustomLink = (customLink: string, item: ImgInfo) => {
|
||||
}
|
||||
|
||||
export default (style: IPasteStyle, item: ImgInfo, customLink: string | undefined) => {
|
||||
let url = item.url || item.imgUrl
|
||||
if (db.get('settings.encodeOutputURL') !== false) {
|
||||
url = handleUrlEncode(url)
|
||||
}
|
||||
const url = handleUrlEncodeWithSetting(item.url || item.imgUrl)
|
||||
const _customLink = customLink || '$url'
|
||||
const tpl = {
|
||||
markdown: ``,
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import { showMessageBox } from '~/main/utils/common'
|
||||
import { T } from '~/main/i18n'
|
||||
|
||||
class PrivacyManager {
|
||||
async check () {
|
||||
if (db.get('settings.privacyEnsure') !== true) {
|
||||
if (picgo.getConfig<boolean>('settings.privacyEnsure') !== true) {
|
||||
const res = await this.show(true)
|
||||
// cancel
|
||||
if (res.result === 1) {
|
||||
return false
|
||||
} else {
|
||||
db.set('settings.privacyEnsure', true)
|
||||
picgo.saveConfig({ 'settings.privacyEnsure': true })
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { dialog, shell } from 'electron'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import pkg from 'root/package.json'
|
||||
import { lt } from 'semver'
|
||||
import { T } from '~/main/i18n'
|
||||
@@ -10,13 +10,13 @@ const version = pkg.version
|
||||
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
|
||||
|
||||
const checkVersion = async () => {
|
||||
let showTip = db.get('settings.showUpdateTip')
|
||||
let showTip = picgo.getConfig<boolean | undefined>('settings.showUpdateTip')
|
||||
if (showTip === undefined) {
|
||||
db.set('settings.showUpdateTip', true)
|
||||
picgo.saveConfig({ 'settings.showUpdateTip': true })
|
||||
showTip = true
|
||||
}
|
||||
if (showTip) {
|
||||
const isCheckBetaUpdate = db.get('settings.checkBetaUpdate') !== false
|
||||
const isCheckBetaUpdate = picgo.getConfig<boolean>('settings.checkBetaUpdate') !== false
|
||||
const res: string = await getLatestVersion(isCheckBetaUpdate)
|
||||
if (res !== '') {
|
||||
const latest = res
|
||||
@@ -35,7 +35,7 @@ const checkVersion = async () => {
|
||||
if (res.response === 0) { // if selected yes
|
||||
shell.openExternal(downloadUrl)
|
||||
}
|
||||
db.set('settings.showUpdateTip', !res.checkboxChecked)
|
||||
picgo.saveConfig({ 'settings.showUpdateTip': !res.checkboxChecked })
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -50,7 +50,7 @@ const checkVersion = async () => {
|
||||
const compareVersion2Update = (current: string, latest: string) => {
|
||||
try {
|
||||
if (latest.includes('beta')) {
|
||||
const isCheckBetaUpdate = db.get('settings.checkBetaUpdate') !== false
|
||||
const isCheckBetaUpdate = picgo.getConfig<boolean>('settings.checkBetaUpdate') !== false
|
||||
if (!isCheckBetaUpdate) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// temp no used
|
||||
// will be refactor in future
|
||||
import { contextBridge, webUtils } from 'electron'
|
||||
|
||||
const getFilePath = (file: File): string => webUtils.getPathForFile(file)
|
||||
|
||||
const electronApi = {
|
||||
getFilePath
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('electronApi', electronApi)
|
||||
|
||||
export type ElectronApi = typeof electronApi
|
||||
@@ -11,6 +11,9 @@ import { getConfig } from './utils/dataSender'
|
||||
import type { IConfig } from 'picgo'
|
||||
import bus from './utils/bus'
|
||||
import { FORCE_UPDATE } from '~/universal/events/constants'
|
||||
import { useATagClick } from './hooks/useATagClick'
|
||||
|
||||
useATagClick()
|
||||
|
||||
const store = useStore()
|
||||
onBeforeMount(async () => {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -3,81 +3,35 @@
|
||||
id="config-form"
|
||||
:class="props.colorMode === 'white' ? 'white' : ''"
|
||||
>
|
||||
<el-form
|
||||
<BaseConfigForm
|
||||
ref="$form"
|
||||
label-position="left"
|
||||
label-width="50%"
|
||||
:model="ruleForm"
|
||||
size="small"
|
||||
:config="configList"
|
||||
:form-model="formModel"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('UPLOADER_CONFIG_NAME')"
|
||||
required
|
||||
prop="_configName"
|
||||
>
|
||||
<el-input
|
||||
v-model="ruleForm._configName"
|
||||
type="input"
|
||||
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- dynamic config -->
|
||||
<el-form-item
|
||||
v-for="(item, index) in configList"
|
||||
:key="item.name + index"
|
||||
:label="item.alias || item.name"
|
||||
:required="item.required"
|
||||
:prop="item.name"
|
||||
>
|
||||
<el-input
|
||||
v-if="item.type === 'input' || item.type === 'password'"
|
||||
v-model="ruleForm[item.name]"
|
||||
:type="item.type === 'password' ? 'password' : 'input'"
|
||||
:placeholder="item.message || item.name"
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="item.type === 'list' && item.choices"
|
||||
v-model="ruleForm[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
<template #extra-form>
|
||||
<el-form-item
|
||||
v-if="isUploader"
|
||||
:label="$T('UPLOADER_CONFIG_NAME')"
|
||||
required
|
||||
prop="_configName"
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:key="choice.name || choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
<el-input
|
||||
v-model="formModel._configName"
|
||||
type="input"
|
||||
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="item.type === 'checkbox' && item.choices"
|
||||
v-model="ruleForm[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
multiple
|
||||
collapse-tags
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:key="choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
/>
|
||||
</el-select>
|
||||
<el-switch
|
||||
v-else-if="item.type === 'confirm'"
|
||||
v-model="ruleForm[item.name]"
|
||||
active-text="yes"
|
||||
inactive-text="no"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<slot />
|
||||
</el-form>
|
||||
</BaseConfigForm>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch, toRefs } from 'vue'
|
||||
import { cloneDeep, union } from 'lodash'
|
||||
import { getConfig } from '@/utils/dataSender'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import BaseConfigForm from './form/BaseConfigForm.vue'
|
||||
import { useConfigForm } from '@/hooks/useConfigForm'
|
||||
|
||||
interface IProps {
|
||||
config: any[]
|
||||
@@ -88,33 +42,14 @@ interface IProps {
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
const $route = useRoute()
|
||||
const $form = ref<FormInstance>()
|
||||
|
||||
const $form = ref<IFormInstance>()
|
||||
const configList = ref<IPicGoPluginConfig[]>([])
|
||||
const ruleForm = reactive<IStringKeyMap>({})
|
||||
|
||||
watch(toRefs(props.config), (val: IPicGoPluginConfig[]) => {
|
||||
handleConfigChange(val)
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
|
||||
function handleConfigChange (val: any) {
|
||||
handleConfig(val)
|
||||
}
|
||||
const formModel = reactive<IStringKeyMap>({})
|
||||
const isUploader = props.type === 'uploader'
|
||||
|
||||
async function validate (): Promise<IStringKeyMap | false> {
|
||||
return new Promise((resolve) => {
|
||||
$form.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(ruleForm)
|
||||
} else {
|
||||
resolve(false)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
const res = await $form.value?.validate() || false
|
||||
return res
|
||||
}
|
||||
|
||||
function getConfigType () {
|
||||
@@ -133,45 +68,50 @@ function getConfigType () {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfig (val: IPicGoPluginConfig[]) {
|
||||
const config = await getCurConfigFormData()
|
||||
const configId = $route.params.configId
|
||||
Object.assign(ruleForm, config)
|
||||
if (val.length > 0) {
|
||||
configList.value = cloneDeep(val).map((item) => {
|
||||
if (!configId) return item
|
||||
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)
|
||||
}
|
||||
if (config && config[item.name] !== undefined) {
|
||||
defaultValue = config[item.name]
|
||||
}
|
||||
ruleForm[item.name] = defaultValue
|
||||
return item
|
||||
})
|
||||
}
|
||||
const handleConfigForm = useConfigForm()
|
||||
|
||||
async function handleConfig (inputConfig: IPicGoPluginConfig[]) {
|
||||
const currentConfig = await getCurConfigFormData()
|
||||
const resetConfig = isUploader && !$route.params.configId
|
||||
Object.assign(formModel, currentConfig)
|
||||
configList.value = handleConfigForm(inputConfig, formModel, currentConfig, resetConfig)
|
||||
}
|
||||
|
||||
async function getCurConfigFormData () {
|
||||
const configId = $route.params.configId
|
||||
const curTypeConfigList = await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`) || []
|
||||
return curTypeConfigList.find(i => i._id === configId) || {}
|
||||
const configType = getConfigType()
|
||||
if (isUploader) {
|
||||
const curTypeConfigList = await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`) || []
|
||||
return curTypeConfigList.find(i => i._id === configId) || {}
|
||||
} else {
|
||||
const config = await getConfig<IStringKeyMap>(configType)
|
||||
return config || {}
|
||||
}
|
||||
}
|
||||
|
||||
async function resetConfig () {
|
||||
const config = await getCurConfigFormData()
|
||||
Object.assign(formModel, config)
|
||||
}
|
||||
|
||||
watch(toRefs(props.config), (val: IPicGoPluginConfig[]) => {
|
||||
handleConfig(val)
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
getConfigType
|
||||
getConfigType,
|
||||
resetConfig
|
||||
})
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.config-form-common-tips
|
||||
a
|
||||
color #409EFF
|
||||
text-decoration none
|
||||
#config-form
|
||||
.el-form
|
||||
label
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<ConfirmDialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
:width="width"
|
||||
@confirm="handleConfirm"
|
||||
@cancel="handleCancel"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<template #body>
|
||||
<BaseConfigForm
|
||||
ref="$form"
|
||||
:config="configList"
|
||||
:form-model="formModel"
|
||||
theme="light"
|
||||
/>
|
||||
</template>
|
||||
</ConfirmDialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import ConfirmDialog from '@/components/dialog/ConfirmDialog.vue'
|
||||
import { useIPCOn } from '@/hooks/useIPC'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import BaseConfigForm from '@/components/form/BaseConfigForm.vue'
|
||||
import { useConfigForm } from '@/hooks/useConfigForm'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
|
||||
const configList = ref<IPicGoPluginConfig[]>([])
|
||||
const formModel = reactive<IStringKeyMap>({})
|
||||
const $form = ref<IFormInstance>()
|
||||
const title = ref('')
|
||||
const width = ref(500)
|
||||
|
||||
const handleConfigForm = useConfigForm()
|
||||
|
||||
const visible = ref(false)
|
||||
useIPCOn(IRPCActionType.OPEN_CONFIG_DIALOG, (event, options: IPicGoPluginShowConfigDialogOption) => {
|
||||
visible.value = true
|
||||
configList.value = handleConfigForm(options.config, formModel)
|
||||
title.value = options.title
|
||||
width.value = options.width || 500
|
||||
})
|
||||
const handleConfirm = async () => {
|
||||
const res = await $form.value?.validate() || false
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
sendToMain(IRPCActionType.OPEN_CONFIG_DIALOG, res)
|
||||
visible.value = false
|
||||
}
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
sendToMain(IRPCActionType.OPEN_CONFIG_DIALOG, false)
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ConfigFormDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
:width="width + 'px'"
|
||||
:append-to-body="true"
|
||||
@close="handleClose"
|
||||
>
|
||||
<slot name="body" />
|
||||
<template #footer>
|
||||
<slot
|
||||
v-if="footerSlots"
|
||||
name="footer"
|
||||
/>
|
||||
<template v-else>
|
||||
<ElButton
|
||||
type="default"
|
||||
round
|
||||
@click="handleCancel"
|
||||
>
|
||||
{{ cancelButtonText }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
round
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ confirmButtonText }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { useSlots, ref } from 'vue'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
title: string
|
||||
confirmButtonText?: string
|
||||
cancelButtonText?: string
|
||||
width?: number
|
||||
}
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
confirmButtonText: $T('CONFIRM'),
|
||||
cancelButtonText: $T('CANCEL'),
|
||||
width: 500
|
||||
})
|
||||
const $emit = defineEmits(['confirm', 'cancel', 'close'])
|
||||
|
||||
const isCancel = ref(false)
|
||||
const isConfirm = ref(false)
|
||||
|
||||
const visible = useVModel(props, 'modelValue')
|
||||
|
||||
const handleClose = () => {
|
||||
if (isConfirm.value) {
|
||||
return
|
||||
}
|
||||
if (isCancel.value) {
|
||||
$emit('cancel')
|
||||
isCancel.value = false
|
||||
} else {
|
||||
$emit('close')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
isCancel.value = true
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
isConfirm.value = true
|
||||
$emit('confirm')
|
||||
}
|
||||
|
||||
const footerSlots = !!useSlots().footer
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ConfirmDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
+14
-2
@@ -2,11 +2,15 @@
|
||||
<el-dialog
|
||||
v-model="showInputBoxVisible"
|
||||
:title="inputBoxOptions.title || $T('INPUT')"
|
||||
:modal-append-to-body="false"
|
||||
:append-to-body="true"
|
||||
:width="inputBoxOptions.width + 'px'"
|
||||
>
|
||||
<el-input
|
||||
v-model="inputBoxValue"
|
||||
:placeholder="inputBoxOptions.placeholder"
|
||||
:type="inputBoxOptions.inputType || 'text'"
|
||||
:rows="inputBoxOptions.inputType === 'textarea' ? 6 : undefined"
|
||||
:class="{ 'input-box__textarea': inputBoxOptions.inputType === 'textarea' }"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button
|
||||
@@ -38,7 +42,9 @@ const inputBoxValue = ref('')
|
||||
const showInputBoxVisible = ref(false)
|
||||
const inputBoxOptions = reactive({
|
||||
title: '',
|
||||
placeholder: ''
|
||||
placeholder: '',
|
||||
inputType: 'text' as 'text' | 'textarea',
|
||||
width: 500
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
@@ -54,6 +60,8 @@ function initInputBoxValue (options: IShowInputBoxOption) {
|
||||
inputBoxValue.value = options.value || ''
|
||||
inputBoxOptions.title = options.title || ''
|
||||
inputBoxOptions.placeholder = options.placeholder || ''
|
||||
inputBoxOptions.inputType = options.inputType || 'text'
|
||||
inputBoxOptions.width = options.width || 400
|
||||
showInputBoxVisible.value = true
|
||||
}
|
||||
|
||||
@@ -82,4 +90,8 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.input-box__textarea
|
||||
.el-textarea__inner
|
||||
resize vertical
|
||||
max-height 240px
|
||||
</style>
|
||||
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div
|
||||
id="base-config-form"
|
||||
:class="theme ?? 'dark'"
|
||||
>
|
||||
<el-form
|
||||
ref="$form"
|
||||
label-position="left"
|
||||
label-width="50%"
|
||||
:model="form"
|
||||
size="small"
|
||||
>
|
||||
<slot name="extra-form" />
|
||||
<!-- dynamic config -->
|
||||
<el-form-item
|
||||
v-for="(item, index) in config"
|
||||
:key="item.name + index"
|
||||
:required="item.required"
|
||||
:prop="item.name"
|
||||
:class="index !== config.length - 1 ? 'has-border' : ''"
|
||||
>
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
{{ item.alias || item.name }}
|
||||
<template v-if="item.tips">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
placement="right"
|
||||
>
|
||||
<template #content>
|
||||
<span
|
||||
class="config-form-common-tips"
|
||||
v-html="transformMarkdownToHTML(item.tips)"
|
||||
/>
|
||||
</template>
|
||||
<el-icon class="ml-[4px] cursor-pointer hover:text-blue">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-input
|
||||
v-if="item.type === 'input' || item.type === 'password'"
|
||||
v-model="form[item.name]"
|
||||
:type="item.type === 'password' ? 'password' : 'input'"
|
||||
:placeholder="item.message || item.name"
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="item.type === 'list' && item.choices"
|
||||
v-model="form[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:key="choice.name || choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="item.type === 'checkbox' && item.choices"
|
||||
v-model="form[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
multiple
|
||||
collapse-tags
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:key="choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
/>
|
||||
</el-select>
|
||||
<el-switch
|
||||
v-else-if="item.type === 'confirm'"
|
||||
v-model="form[item.name]"
|
||||
:active-text="item.confirmText || 'yes'"
|
||||
:inactive-text="item.cancelText || 'no'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<slot />
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { marked } from 'marked'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const $form = ref<FormInstance>()
|
||||
|
||||
interface IProps {
|
||||
config: IPicGoPluginConfig[]
|
||||
formModel: IStringKeyMap
|
||||
theme?: 'light' | 'dark'
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const form = useVModel(props, 'formModel')
|
||||
|
||||
function transformMarkdownToHTML (markdown: string) {
|
||||
try {
|
||||
return marked.parse(markdown)
|
||||
} catch (e) {
|
||||
return markdown
|
||||
}
|
||||
}
|
||||
|
||||
async function validate (): Promise<IStringKeyMap | false> {
|
||||
return new Promise((resolve) => {
|
||||
$form.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(form.value)
|
||||
} else {
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'BaseConfigForm'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#base-config-form
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
&-item
|
||||
display: flex
|
||||
justify-content space-between
|
||||
border-bottom 1px solid darken(#eee, 50%)
|
||||
padding-bottom 16px
|
||||
&:last-child
|
||||
border-bottom none
|
||||
&__content
|
||||
justify-content flex-end
|
||||
.el-button-group
|
||||
width 100%
|
||||
.el-button
|
||||
width 50%
|
||||
.el-radio-group
|
||||
margin-left 25px
|
||||
.el-switch__label
|
||||
&.is-active
|
||||
color #409EFF
|
||||
&.light
|
||||
.el-form-item.has-border
|
||||
border-bottom 1px solid #ddd
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user