├── assets ├── test.md ├── test.png └── vs-picgo-auto-launch.txt ├── .eslintignore ├── logo.png ├── src ├── webview │ ├── pages │ │ ├── index.less │ │ ├── PicGoControlPanel │ │ │ ├── PicGoGallery │ │ │ │ └── index.tsx │ │ │ ├── models │ │ │ │ ├── index.ts │ │ │ │ └── settings.ts │ │ │ ├── store.ts │ │ │ ├── hooks.ts │ │ │ ├── index.tsx │ │ │ ├── PicGoSettings │ │ │ │ ├── index.tsx │ │ │ │ └── PicGoPluginConfigForm.tsx │ │ │ ├── PicGoDrawerList │ │ │ │ └── index.tsx │ │ │ └── PicGoUpload │ │ │ │ └── index.tsx │ │ ├── Demo │ │ │ ├── models.ts │ │ │ ├── store.ts │ │ │ ├── index.less │ │ │ └── index.tsx │ │ └── index.tsx │ ├── tsconfig.json │ ├── components │ │ ├── Copyright.tsx │ │ ├── ThemeWrapper.tsx │ │ └── PicGoPanelWrapper.tsx │ ├── utils │ │ └── channel.ts │ └── react-app-env.d.ts ├── images │ ├── roundLogo.png │ └── squareLogo.png ├── utils │ ├── page.ts │ ├── meta.ts │ ├── message-method.ts │ └── index.ts ├── vscode │ ├── Editor.ts │ ├── template.html │ ├── settings.ts │ ├── DataStore.ts │ ├── utils │ │ ├── index.ts │ │ └── channel.ts │ ├── CommandManager.ts │ ├── PanelManager.ts │ ├── PicgoAddon.ts │ └── PicgoAPI.ts └── extension.ts ├── .gitattributes ├── test ├── utils │ ├── index.ts │ ├── upload-starter.ts │ └── constants-and-interfaces.ts ├── run-test.ts ├── runner │ └── index.ts └── suite │ └── extension.test.ts ├── .vscodeignore ├── .gitignore ├── CODE_OF_CONDUCT.md ├── .yarnrc ├── .vscode ├── extensions.json ├── tasks.json ├── launch.json └── settings.json ├── .scripts └── update_dependencies.sh ├── tsconfig.json ├── LICENSE ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.yml │ └── bug_report.md └── workflows │ ├── ci.yml │ └── release-tag.yml ├── CONTRIBUTING.md ├── .eslintrc.js ├── docs └── README_ZH.md ├── package.nls.json ├── esbuild.mjs ├── CHANGELOG.md ├── README.md └── package.json /assets/test.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PicGo/vs-picgo/HEAD/logo.png -------------------------------------------------------------------------------- /assets/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PicGo/vs-picgo/HEAD/assets/test.png -------------------------------------------------------------------------------- /src/webview/pages/index.less: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | -------------------------------------------------------------------------------- /src/images/roundLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PicGo/vs-picgo/HEAD/src/images/roundLogo.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | 4 | -------------------------------------------------------------------------------- /src/images/squareLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PicGo/vs-picgo/HEAD/src/images/squareLogo.png -------------------------------------------------------------------------------- /test/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants-and-interfaces' 2 | export * from './upload-starter' 3 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | ** 2 | !dist 3 | !logo.png 4 | !README.md 5 | !CHANGELOG.md 6 | !LICENSE 7 | !package.json 8 | !package.nls.json 9 | -------------------------------------------------------------------------------- /src/utils/page.ts: -------------------------------------------------------------------------------- 1 | export const pageMap = { 2 | Demo: 'Demo', 3 | PicGoControlPanel: 'PicGo Control Panel' 4 | } 5 | export type PageId = keyof typeof pageMap 6 | -------------------------------------------------------------------------------- /src/webview/pages/PicGoControlPanel/PicGoGallery/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const PicGoGallery = () => { 4 | return
Gallery
5 | } 6 | -------------------------------------------------------------------------------- /assets/vs-picgo-auto-launch.txt: -------------------------------------------------------------------------------- 1 | This file is just for developers, and it will trigger vs-picgo's 2 | `workspaceContains` activation events and launch the webview panel 3 | for quick testing. 4 | -------------------------------------------------------------------------------- /src/webview/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "noEmit": true, 6 | "jsx": "react" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | .imgs 6 | yarn-error.log 7 | package-lock.json 8 | coverage 9 | .coveralls.yml 10 | .DS_Store 11 | dist 12 | tmp 13 | assets/test.md 14 | *.zip 15 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project has adopted the [PicGo BumpVersion convention](https://github.com/PicGo/bump-version). For more information, go to the repository and open an issue there if you have any additional questions or comments. -------------------------------------------------------------------------------- /src/utils/meta.ts: -------------------------------------------------------------------------------- 1 | import pkg = require('../../package.json') 2 | import nls = require('../../package.nls.json') 3 | 4 | export const getNLSText = (field: keyof typeof nls) => { 5 | return nls[field] 6 | } 7 | 8 | export const contributes = pkg.contributes 9 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | --add.ignore-engines true 2 | --add.exact true 3 | --add.registry "https://registry.yarnpkg.com" 4 | --upgrade.ignore-engines true 5 | --upgrade.registry "https://registry.yarnpkg.com" 6 | --install.ignore-engines true 7 | --install.registry "https://registry.yarnpkg.com" 8 | --remove.ignore-engines true 9 | ignore-engines true 10 | -------------------------------------------------------------------------------- /src/vscode/Editor.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode' 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class 4 | export class Editor { 5 | static get editor() { 6 | return vscode.window.activeTextEditor 7 | } 8 | 9 | static async writeToEditor(text: string) { 10 | const editor = this.editor 11 | return await editor?.edit((textEditor) => { 12 | textEditor.replace(editor.selection, text) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/message-method.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * message method from webview to vscode 3 | */ 4 | export enum W2VMessage { 5 | GET_WEBVIEW_URI = 'GET_WEBVIEW_URI', 6 | SHOW_MESSAGE = 'SHOW_MESSAGE', 7 | UPLOAD_FILES = 'UPLOAD_FILES', 8 | EXECUTE_COMMAND = 'EXECUTE_COMMAND', 9 | GET_ALL_UPLOADERS = 'GET_ALL_UPLOADERS', 10 | GET_ALL_UPLOADER_CONFIGS = 'GET_ALL_UPLOADER_CONFIGS', 11 | SET_CONFIG = 'SET_CONFIG' 12 | } 13 | 14 | /** 15 | * message method from vscode to webview 16 | */ 17 | export enum V2WMessage {} 18 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "streetsidesoftware.code-spell-checker", 8 | "davidanson.vscode-markdownlint", 9 | "yzhang.markdown-all-in-one", 10 | "wix.vscode-import-cost", 11 | "pkief.material-icon-theme", 12 | "eamodio.gitlens", 13 | "connor4312.esbuild-problem-matchers" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/webview/components/Copyright.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Typography } from '@mui/material' 2 | import React from 'react' 3 | 4 | export function Copyright(props: any) { 5 | return ( 6 | 11 | {'Copyright © '} 12 | 13 | PicGo Group 14 | {' '} 15 | {new Date().getFullYear()} 16 | {'.'} 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/vscode/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{pageId}} 8 | 9 | 10 | 11 |
12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utils that can work in both webview and vscode 3 | */ 4 | 5 | export interface IMessageToShow { 6 | type: 'warning' | 'error' | 'info' 7 | message: string 8 | } 9 | 10 | export const isUrlEncode = (url: string): boolean => { 11 | url = url || '' 12 | try { 13 | return url !== decodeURI(url) 14 | } catch (e) { 15 | // if some error caught, try to let it go 16 | return true 17 | } 18 | } 19 | 20 | export const handleUrlEncode = (url: string): string => { 21 | if (!isUrlEncode(url)) { 22 | url = encodeURI(url) 23 | } 24 | return url 25 | } 26 | -------------------------------------------------------------------------------- /.scripts/update_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # MIT © Sindre Sorhus - sindresorhus.com 3 | # https://gist.github.com/sindresorhus/7996717 4 | # https://gist.github.com/GianlucaGuarini/8001627 5 | 6 | # git hook to run a command after `git pull` if a specified file was changed 7 | # Run `chmod +x post-merge` to make it executable then put it into `.git/hooks/`. 8 | 9 | changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)" 10 | 11 | check_run() { 12 | echo "$changed_files" | grep -E --quiet "$1" && eval "$2" 13 | } 14 | 15 | # run yarn when yarn.lock changed 16 | check_run "yarn.lock" "yarn" 17 | -------------------------------------------------------------------------------- /src/webview/pages/Demo/models.ts: -------------------------------------------------------------------------------- 1 | import { Models, createModel } from '@rematch/core' 2 | 3 | export interface ICommonState { 4 | count: number 5 | } 6 | 7 | export const defaultCommonState: ICommonState = { 8 | count: 10 9 | } 10 | 11 | export const common = createModel()({ 12 | state: defaultCommonState, 13 | reducers: { 14 | countUp(state) { 15 | state.count += 1 16 | }, 17 | countDown(state) { 18 | state.count -= 1 19 | } 20 | } 21 | }) 22 | 23 | export interface IRootModel extends Models { 24 | common: typeof common 25 | } 26 | 27 | export const models: IRootModel = { common } 28 | -------------------------------------------------------------------------------- /src/webview/pages/PicGoControlPanel/models/index.ts: -------------------------------------------------------------------------------- 1 | import { Models, createModel } from '@rematch/core' 2 | import { settings } from './settings' 3 | 4 | export interface ICommonState { 5 | count: number 6 | } 7 | 8 | export const defaultCommonState: ICommonState = { 9 | count: 10 10 | } 11 | 12 | export const common = createModel()({ 13 | state: defaultCommonState, 14 | reducers: { 15 | countUp(state) { 16 | state.count += 1 17 | }, 18 | countDown(state) { 19 | state.count -= 1 20 | } 21 | } 22 | }) 23 | 24 | export interface IRootModel extends Models { 25 | common: typeof common 26 | settings: typeof settings 27 | } 28 | 29 | export const models: IRootModel = { common, settings } 30 | -------------------------------------------------------------------------------- /src/webview/pages/Demo/store.ts: -------------------------------------------------------------------------------- 1 | import { init, RematchDispatch, RematchRootState } from '@rematch/core' 2 | import loading, { ExtraModelsFromLoading } from '@rematch/loading' 3 | import updated, { ExtraModelsFromUpdated } from '@rematch/updated' 4 | import immerPlugin from '@rematch/immer' 5 | import selectPlugin from '@rematch/select' 6 | import { models, IRootModel } from './models' 7 | 8 | type FullModel = ExtraModelsFromLoading & 9 | ExtraModelsFromUpdated 10 | export const store = init({ 11 | models, 12 | plugins: [updated(), loading(), immerPlugin(), selectPlugin()] 13 | }) 14 | 15 | export type Store = typeof store 16 | export type Dispatch = RematchDispatch 17 | export type RootState = RematchRootState 18 | -------------------------------------------------------------------------------- /src/webview/pages/Demo/index.less: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | &-logo { 4 | height: 40vmin; 5 | pointer-events: none; 6 | } 7 | &-header { 8 | background-color: #282c34; 9 | min-height: 100vh; 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | justify-content: center; 14 | font-size: calc(10px + 2vmin); 15 | color: white; 16 | } 17 | &-link { 18 | color: #61dafb; 19 | } 20 | } 21 | 22 | @media (prefers-reduced-motion: no-preference) { 23 | .App-logo { 24 | animation: App-logo-spin infinite 20s linear; 25 | } 26 | } 27 | 28 | @keyframes App-logo-spin { 29 | from { 30 | transform: rotate(0deg); 31 | } 32 | to { 33 | transform: rotate(360deg); 34 | } 35 | } 36 | 37 | button { 38 | font-size: calc(10px + 2vmin); 39 | } 40 | -------------------------------------------------------------------------------- /test/run-test.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import { runTests } from 'vscode-test' 3 | 4 | async function main() { 5 | try { 6 | // The folder containing the Extension Manifest package.json 7 | const extensionDevelopmentPath = path.resolve(__dirname, '../') 8 | 9 | // The path to test runner 10 | const extensionTestsPath = path.resolve( 11 | extensionDevelopmentPath, 12 | './dist/runner/index' 13 | ) 14 | 15 | // Download VS code, unzip it and run the integration test 16 | await runTests({ 17 | extensionDevelopmentPath, 18 | extensionTestsPath, 19 | launchArgs: ['--disable-extensions'] 20 | }) 21 | } catch (err) { 22 | console.log(err) 23 | console.error('Failed to run tests') 24 | process.exit(1) 25 | } 26 | } 27 | 28 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 29 | main() 30 | -------------------------------------------------------------------------------- /test/runner/index.ts: -------------------------------------------------------------------------------- 1 | import glob from 'glob' 2 | import * as path from 'path' 3 | import Mocha from 'mocha' 4 | 5 | export async function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | color: true, 9 | ui: 'bdd' 10 | }) 11 | 12 | const testsRoot = path.resolve(__dirname, '../suite') 13 | 14 | // TODO: Add coverage collector 15 | return await new Promise((resolve, reject) => { 16 | glob('**/**test.js', { cwd: testsRoot }, (err, files) => { 17 | if (err) { 18 | return reject(err) 19 | } 20 | 21 | files.forEach( 22 | (file): Mocha => mocha.addFile(path.resolve(testsRoot, file)) 23 | ) 24 | 25 | try { 26 | mocha.run((failures) => { 27 | if (failures > 0) { 28 | reject(new Error(`${failures} tests failed.`)) 29 | } 30 | resolve() 31 | }) 32 | } catch (err) { 33 | console.error(err) 34 | reject(err) 35 | } 36 | }) 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "useDefineForClassFields": true, 7 | "lib": ["ESNext"], 8 | "allowJs": false, 9 | "allowSyntheticDefaultImports": true, 10 | "resolveJsonModule": true, 11 | "sourceMap": true, 12 | "esModuleInterop": true, 13 | "rootDir": ".", 14 | /* Strict Type-Checking Option */ 15 | "strict": true, 16 | /* enable all strict type-checking options */ 17 | /* Additional Checks */ 18 | "noUnusedLocals": true /* Report errors on unused locals. */, 19 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 20 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 21 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 22 | "skipLibCheck": true 23 | }, 24 | "include": ["./src/**/*", "./test/**/*"], 25 | "exclude": ["node_modules", ".vscode-test"] 26 | } 27 | -------------------------------------------------------------------------------- /src/webview/pages/PicGoControlPanel/store.ts: -------------------------------------------------------------------------------- 1 | import { init, RematchDispatch, RematchRootState } from '@rematch/core' 2 | import loading, { ExtraModelsFromLoading } from '@rematch/loading' 3 | import updated, { ExtraModelsFromUpdated } from '@rematch/updated' 4 | import immerPlugin from '@rematch/immer' 5 | import selectPlugin from '@rematch/select' 6 | import { models, IRootModel } from './models' 7 | 8 | export type FullModel = ExtraModelsFromLoading & 9 | ExtraModelsFromUpdated 10 | export const store = init({ 11 | models, 12 | plugins: [updated(), loading(), immerPlugin(), selectPlugin()] 13 | }) 14 | 15 | export type Store = typeof store 16 | export type Dispatch = RematchDispatch 17 | export type RootState = RematchRootState 18 | 19 | // https://stackoverflow.com/a/66252656 20 | type RemoveIndex = { 21 | [K in keyof T as string extends K 22 | ? never 23 | : number extends K 24 | ? never 25 | : K]: T[K] 26 | } 27 | export type ModelKeys = keyof RemoveIndex 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2021 Molunerfinn & Spades & upupming 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "✨ Feature Request" 2 | description: 功能请求 / Feature request 3 | title: "[Feature]: " 4 | labels: ["feature request"] 5 | assignees: 6 | - molunerfinn 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: |+ 11 | ## VS-PicGo Issue 模板 12 | 13 | 请依照该模板来提交,否则将会被关闭。 14 | **提问之前请注意你看过文档以及那些被关闭的 issues。否则同样的提问也会被关闭!** 15 | 16 | Please submit according to this template, otherwise it will be closed. 17 | **Before asking a question, please note that you have read the Doc, and those closed issues. Otherwise the same question will also be closed! ** 18 | 19 | - type: dropdown 20 | id: platform 21 | attributes: 22 | label: 系统信息 | System Information 23 | options: 24 | - Windows 25 | - Mac 26 | - Mac(arm64) 27 | - Linux 28 | - All 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: reproduce 33 | attributes: 34 | label: 功能请求 | Feature request 35 | description: 详细描述你所预想的功能或者是现有功能的改进 | Describe in detail the features you envision or improvements to existing features 36 | validations: 37 | required: true 38 | -------------------------------------------------------------------------------- /src/vscode/settings.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-template-curly-in-string */ 2 | export interface IPicgoSettings { 3 | settings: { 4 | vsPicgo: { 5 | customUploadName: string 6 | customOutputFormat: string 7 | } 8 | } 9 | } 10 | 11 | export const outputFormats = { 12 | Markdown: '![${uploadedName}](${url})', 13 | HTML: '${uploadedName}', 14 | UBB: '[IMG]${url}[/IMG]', 15 | URL: '${url}' 16 | } 17 | 18 | export const uploadName = { 19 | auto: 20 | '${editorSelectionText ? `${editorSelectionText}${imgIdx}` : fileName}${extName}', 21 | dateExt: '${date}${extName}' 22 | } 23 | 24 | export const defaultSettings: IPicgoSettings = { 25 | settings: { 26 | vsPicgo: { 27 | customOutputFormat: outputFormats.Markdown, 28 | customUploadName: 29 | '${editorSelectionText ? `${editorSelectionText}${imgIdx}` : fileName}${extName}' 30 | } 31 | } 32 | } 33 | 34 | const editorSelectionText = 'editorSelectionText' 35 | const imgIdx = 0 36 | const fileName = 'fileName' 37 | const extName = '.png' 38 | 39 | // eslint-disable-next-line prettier/prettier 40 | export const customUploadNameTest1 = `${editorSelectionText ? `${editorSelectionText}${imgIdx}` : fileName}${extName}` 41 | -------------------------------------------------------------------------------- /src/webview/pages/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This script will be loaded by the template html used in webview, see PanelManager.getPageHtml 3 | */ 4 | import React from 'react' 5 | import ReactDOM from 'react-dom' 6 | import { showMessage } from '../utils/channel' 7 | import { Demo } from './Demo' 8 | import { PicGoControlPanel } from './PicGoControlPanel' 9 | import { PageId } from '../../utils/page' 10 | import { ThemeWrapper } from '../components/ThemeWrapper' 11 | import { HashRouter, Switch } from 'react-router-dom' 12 | 13 | import './index.less' 14 | 15 | const pages = { 16 | Demo, 17 | PicGoControlPanel 18 | } 19 | 20 | export const renderApp = (pageId: PageId) => { 21 | const root = document.getElementById('root') 22 | if (!root) { 23 | showMessage({ 24 | type: 'error', 25 | message: 26 | '#root element not found in Webview html template, cannot mount React App!' 27 | }) 28 | return 29 | } 30 | 31 | const App = pages[pageId] 32 | ReactDOM.unmountComponentAtNode(root) 33 | ReactDOM.render( 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | , 43 | root 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | // A task runner that calls a custom npm script that compiles the extension. 9 | { 10 | "version": "2.0.0", 11 | "presentation": { 12 | "echo": false, 13 | "reveal": "always", 14 | "focus": false, 15 | "panel": "dedicated", 16 | "showReuseMessage": false 17 | }, 18 | "tasks": [ 19 | { 20 | "type": "npm", 21 | "script": "build", 22 | "group": "build", 23 | "problemMatcher": ["$esbuild"] 24 | }, 25 | { 26 | "type": "npm", 27 | "script": "lint", 28 | "group": "build", 29 | "problemMatcher": ["$eslint-stylish"] 30 | }, 31 | { 32 | "type": "npm", 33 | "script": "watch", 34 | "group": { 35 | "kind": "build", 36 | "isDefault": true 37 | }, 38 | "isBackground": true, 39 | "label": "npm: watch", 40 | "problemMatcher": ["$esbuild-watch"] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /src/vscode/DataStore.ts: -------------------------------------------------------------------------------- 1 | import { DBStore } from '@picgo/store' 2 | import { getAppDataPath } from 'appdata-path' 3 | import path from 'path' 4 | import fs from 'fs-extra' 5 | 6 | export class DataStore { 7 | static dataStore: DataStore = new DataStore() 8 | 9 | readonly db: DBStore 10 | private constructor() { 11 | this.db = new DBStore(this.galleryStorePath, 'gallery') 12 | } 13 | 14 | /** 15 | * Default paths: 16 | * - the `userDataPath` is consistent with electron: https://www.electronjs.org/docs/api/app#appgetpathname 17 | * - the uploaded images data: `userDataPath/picgo/picgo.db`, eg. `~/Library/Application Support/picgo/picgo.db` in macOS 18 | * - the configuration path: `userDataPath/picgo/data.json`, eg. `~/Library/Application Support/picgo/data.json` in macOS 19 | */ 20 | get appDataPath() { 21 | const appDataPath = getAppDataPath('picgo') 22 | fs.ensureDirSync(appDataPath) 23 | return appDataPath 24 | } 25 | 26 | get galleryStorePath() { 27 | return path.join(this.appDataPath, 'picgo.db') 28 | } 29 | 30 | /** 31 | * picgo.log will be automatically calculated according to configPath by picgo, so we do not need to handle log file path here 32 | */ 33 | get configPath() { 34 | return path.join(this.appDataPath, 'data.json') 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | # Avoid triggering two build on PR 3 | # https://github.community/t/how-to-trigger-an-action-on-push-or-pull-request-but-not-both/16662/2 4 | on: 5 | push: 6 | branches: 7 | - master 8 | - dev 9 | pull_request: 10 | branches: 11 | - master 12 | - dev 13 | 14 | jobs: 15 | build-and-prerelease: 16 | runs-on: macos-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-node@v2 20 | with: 21 | node-version: '16' 22 | 23 | - name: yarn install, compile, and test coverage 24 | run: | 25 | # https://github.com/mui-org/material-ui/issues/12432#issuecomment-411046157 26 | yarn install --network-timeout 500000 27 | yarn build 28 | yarn test 29 | 30 | # TODO: add coverage info 31 | # - name: Coveralls 32 | # uses: coverallsapp/github-action@master 33 | # with: 34 | # github-token: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: Build artifact 37 | run: | 38 | mkdir artifact 39 | rm README.md && yarn vsce package --yarn -o artifact 40 | 41 | - name: Upload artifact 42 | uses: actions/upload-artifact@v1 43 | with: 44 | name: vs-picgo CI built release 45 | path: artifact 46 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Debug Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--disable-extensions", 15 | "--user-data-dir=tmp", 16 | "--extensionDevelopmentPath=${workspaceFolder}", 17 | "${workspaceFolder}/assets" 18 | ], 19 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 20 | "preLaunchTask": "${defaultBuildTask}" 21 | }, 22 | { 23 | "name": "Extension Tests", 24 | "type": "extensionHost", 25 | "request": "launch", 26 | "runtimeExecutable": "${execPath}", 27 | "args": [ 28 | "--disable-extensions", 29 | "--user-data-dir=tmp", 30 | "--extensionDevelopmentPath=${workspaceFolder}", 31 | "--extensionTestsPath=${workspaceFolder}/dist/runner/index" 32 | ], 33 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 34 | "preLaunchTask": "npm: build:test" 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /src/webview/pages/PicGoControlPanel/hooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useDispatch as _useDispatch, 3 | useSelector as _useSelector, 4 | useStore as _useStore 5 | } from 'react-redux' 6 | import { RematchRootState, RematchStore } from '@rematch/core' 7 | import type { FullModel, Dispatch, ModelKeys, RootState, Store } from './store' 8 | import { IRootModel } from './models' 9 | import { RematchSelect } from '@rematch/select' 10 | 11 | /** 12 | * Encapsulate the hooks to make type stronger and code shorter to use in components 13 | */ 14 | export const useStore = () => { 15 | return _useStore() as RematchStore 16 | } 17 | 18 | export const useDispatch = (modelKey: T) => { 19 | return _useDispatch()[modelKey] 20 | } 21 | 22 | export const useState = (modelKey: T) => { 23 | return _useSelector((state: RootState) => state[modelKey]) 24 | } 25 | 26 | export const useSelector = < 27 | T extends ModelKeys, 28 | M extends keyof RematchSelect< 29 | IRootModel, 30 | FullModel, 31 | RematchRootState 32 | >[T] 33 | >( 34 | modelKey: T, 35 | selector: M 36 | /* @ts-expect-error */ 37 | ): ReturnType => { 38 | _useSelector(useStore().select.settings.uploaderTitleItems) 39 | return _useSelector(useStore().select[modelKey][selector] as any) 40 | } 41 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { CommandManager } from './vscode/CommandManager' 3 | import { PanelManager } from './vscode/PanelManager' 4 | 5 | export async function activate(context: vscode.ExtensionContext) { 6 | PanelManager.bindContext(context) 7 | const panelManager = PanelManager.panelManager 8 | 9 | const disposable = [ 10 | vscode.commands.registerCommand( 11 | 'picgo.uploadImageFromClipboard', 12 | async () => await CommandManager.commandManager.uploadImageFromClipboard() 13 | ), 14 | vscode.commands.registerCommand( 15 | 'picgo.uploadImageFromExplorer', 16 | async () => await CommandManager.commandManager.uploadImageFromExplorer() 17 | ), 18 | vscode.commands.registerCommand( 19 | 'picgo.uploadImageFromInputBox', 20 | async () => await CommandManager.commandManager.uploadImageFromInputBox() 21 | ), 22 | 23 | vscode.commands.registerCommand('picgo.webviewDemo', () => 24 | panelManager.createOrShowWebviewPanel('Demo') 25 | ), 26 | vscode.commands.registerCommand('picgo.webviewPicGoControlPanel', () => 27 | panelManager.createOrShowWebviewPanel('PicGoControlPanel') 28 | ) 29 | ] 30 | context.subscriptions.push(...disposable) 31 | if (process.env.NODE_ENV === 'development') { 32 | panelManager.createOrShowWebviewPanel('PicGoControlPanel') 33 | } 34 | 35 | return context 36 | } 37 | 38 | export function deactivate() {} 39 | -------------------------------------------------------------------------------- /src/vscode/utils/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utils that can only work in vscode 3 | */ 4 | 5 | import { window } from 'vscode' 6 | import { IMessageToShow } from '../../utils' 7 | import { getNLSText } from '../../utils/meta' 8 | 9 | export function addPeriod(message: string) { 10 | if (!message.endsWith('.') && !message.endsWith('!')) { 11 | message = message + '.' 12 | } 13 | return message 14 | } 15 | 16 | export function decorateMessage(message: string): string { 17 | message = addPeriod(message) 18 | return `${getNLSText('ext.displayName')}: ${message}` 19 | } 20 | 21 | export const showWarning = async (message: string) => { 22 | message = decorateMessage(message) 23 | return await window.showWarningMessage(message) 24 | } 25 | 26 | export const showError = async (message: string) => { 27 | message = decorateMessage(message) 28 | return await window.showErrorMessage(message) 29 | } 30 | 31 | export const showInfo = async (message: string) => { 32 | message = decorateMessage(message) 33 | return await window.showInformationMessage(message) 34 | } 35 | 36 | export const showMessage = (messageToShow: IMessageToShow) => { 37 | switch (messageToShow.type) { 38 | case 'warning': 39 | showWarning(messageToShow.message) 40 | break 41 | case 'error': 42 | showError(messageToShow.message) 43 | break 44 | case 'info': 45 | showInfo(messageToShow.message) 46 | break 47 | default: 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ### Describe the bug 13 | 14 | 15 | 16 | ### To Reproduce 17 | 18 | 19 | 20 | 1. Go to '...' 21 | 2. Click on '....' 22 | 3. Scroll down to '....' 23 | 4. See error 24 | 25 | ### Expected behavior 26 | 27 | 28 | 29 | ### Screenshots 30 | 31 | 32 | 33 | ### System information 34 | 35 | - OS: [e.g. Windows] 36 | - Version [e.g. Windows 10] 37 | 38 | ### VSCode information 39 | 40 | - `Help` -> `About` -> `Copy` 41 | 42 | ```txt 43 | // Place you VScode `About` data here 44 | ``` 45 | 46 | - `vs-picgo` version 47 | 48 | - Settings begin with `picgo`: 49 | 50 | ```json 51 | // ... 52 | "picgo.customUploadName": "${fileName}-${date}${extName}" 53 | // ... 54 | ``` 55 | 56 | ### Additional context 57 | 58 | **Is this bug picBed specific, i.e. only the picBed you use is affected?** Yes/No. 59 | 60 | **Is there any error in the console (`Help` -> `Toggle Developer tools`)?**: Yes/No. 61 | 62 | ```txt 63 | If yes, please paste the error here. 64 | ``` -------------------------------------------------------------------------------- /test/utils/upload-starter.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-misused-promises */ 2 | import { Range, Position, window } from 'vscode' 3 | import { CommandManager } from '../../src/vscode/CommandManager' 4 | import { IVSPicgoUploadStarterOptions } from './constants-and-interfaces' 5 | 6 | export async function VSPicgoUploadStarter( 7 | options: IVSPicgoUploadStarterOptions 8 | ) { 9 | const editor = window.activeTextEditor 10 | 11 | if (!editor) { 12 | throw new Error('No activeTextEditor.') 13 | } 14 | 15 | const applied = await editor.edit((editorBuilder) => { 16 | // clean up content in test.md, insert custom content 17 | const endPosition = editor.document.positionAt( 18 | editor.document.getText().length 19 | ) 20 | console.log( 21 | 'document text', 22 | editor.document.getText(), 23 | editor.document.getText().length 24 | ) 25 | console.log('endPosition', endPosition) 26 | const fullRange = new Range(new Position(0, 0), endPosition) 27 | editorBuilder.replace(fullRange, options.editor.content) 28 | }) 29 | if (!applied) { 30 | console.error('edit cannot be applied') 31 | } 32 | 33 | editor.selection = options.editor.selection 34 | 35 | console.log('before upload text content', editor.document.getText()) 36 | const ans = await CommandManager.commandManager.uploadCommand( 37 | options.args4uploader 38 | ) 39 | console.log('after upload text content', editor.document.getText()) 40 | return ans 41 | } 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to vs-picgo 2 | 3 | We encourage and appreciate community feedback and contributions. Thank you for your interest in making `vs-picgo` better! There are several ways you can get involved. 4 | 5 | ## Reporting issues and suggesting new features 6 | 7 | If `vs-picgo` is not working properly, please file a report in the [GitHub issue](https://github.com/PicGo/vs-picgo/issues). You should provide adequate and clean diagnostic data, such as the version of `vs-picgo` you're using, log file, etc. 8 | 9 | We are happy to hear your ideas for the future of `vs-picgo`. Check the GitHub issues and see if others have 10 | submitted similar feedback. You can upvote existing feedback or submit a new suggestion. 11 | 12 | We always look at upvoted items in GitHub issue when we decide what to work on next. We read all the 13 | comments as soon as possible, and we look forward to hearing your input. Remember that 14 | all community interactions must abide by the [Code of Conduct](CODE_OF_CONDUCT.md). 15 | 16 | ## Finding issues you can help with 17 | 18 | Looking for something to work on? 19 | Issues marked [``good first issue``](https://github.com/PicGo/vs-picgo/labels/good%20first%20issue) 20 | are a good place to start. 21 | 22 | You can also check the [``help wanted``](https://github.com/PicGo/vs-picgo/labels/help%20wanted) tag to find 23 | other issues to help with. If you're interested in working on a fix, leave a comment to let everyone know and to help 24 | avoid duplicated effort from others. -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | 'standard-with-typescript', 5 | 'prettier-standard', 6 | 'plugin:react/recommended' 7 | ], 8 | parserOptions: { 9 | project: './tsconfig.json' 10 | }, 11 | rules: { 12 | '@typescript-eslint/explicit-function-return-type': 0, 13 | '@typescript-eslint/strict-boolean-expressions': 0, 14 | 'no-console': 'warn', 15 | // https://stackoverflow.com/a/64024916/8242705 16 | 'no-use-before-define': 'off', 17 | 'no-undef': 'off', 18 | 'react/jsx-sort-props': 'error', 19 | 'react/jsx-boolean-value': 'error', 20 | 'no-void': 'off', 21 | '@typescript-eslint/no-floating-promises': 'off', 22 | '@typescript-eslint/promise-function-async': 'off', 23 | // don't need this, because already has @typescript-eslint/no-unused-vars 24 | 'no-unused-vars': 'off' 25 | }, 26 | overrides: [ 27 | { 28 | files: ['./*.js', './*.ts'], 29 | rules: { 30 | 'import/no-anonymous-default-export': 0, 31 | 'filenames/match-exported': 0 32 | } 33 | }, 34 | { 35 | files: ['**/*.ts', '**/*.tsx'], 36 | rules: { 37 | '@typescript-eslint/naming-convention': [ 38 | 'error', 39 | { 40 | selector: 'interface', 41 | format: ['PascalCase'], 42 | custom: { 43 | regex: '^I[A-Z]', 44 | match: true 45 | } 46 | } 47 | ] 48 | } 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /src/webview/pages/PicGoControlPanel/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { PicGoControlPanelWrapper } from '../../components/PicGoPanelWrapper' 3 | import { Provider } from 'react-redux' 4 | import { store } from './store' 5 | import { PicGoDrawerList } from './PicGoDrawerList' 6 | import { PicGoUpload } from './PicGoUpload' 7 | import { PicGoGallery } from './PicGoGallery' 8 | import { PicGoSettings } from './PicGoSettings' 9 | import { Route } from 'react-router-dom' 10 | import { getAllUploaderConfigs } from '../../utils/channel' 11 | import { useDispatch } from './hooks' 12 | import { useAsync } from 'react-use' 13 | export const PicGoControlPanelInner = () => { 14 | const dispatch = useDispatch('settings') 15 | 16 | useAsync(async () => { 17 | dispatch.setAllUploaderConfigs(await getAllUploaderConfigs()) 18 | }, []) 19 | 20 | return ( 21 | }> 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ) 36 | } 37 | 38 | export const PicGoControlPanel = () => ( 39 | 40 | 41 | 42 | ) 43 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 4 | "typescript.tsc.autoDetect": "off", 5 | "files.insertFinalNewline": true, 6 | "eslint.validate": [ 7 | "javascript", 8 | "javascriptreact", 9 | "typescript", 10 | "typescriptreact" 11 | ], 12 | // Use eslint to format js/ts files 13 | // prettier is included by eslint 14 | "[javascript]": { 15 | "editor.formatOnSave": false 16 | }, 17 | "[javascriptreact]": { 18 | "editor.formatOnSave": false 19 | }, 20 | "[typescript]": { 21 | "editor.formatOnSave": false 22 | }, 23 | "[typescriptreact]": { 24 | "editor.formatOnSave": false 25 | }, 26 | "editor.codeActionsOnSave": { 27 | "source.fixAll.eslint": true 28 | }, 29 | "eslint.packageManager": "yarn", 30 | "typescript.tsdk": "node_modules\\typescript\\lib", 31 | "editor.tabSize": 2, 32 | "cSpell.words": [ 33 | "aliyun", 34 | "cjsx", 35 | "commitlint", 36 | "Dropzone", 37 | "esbuild", 38 | "globby", 39 | "iife", 40 | "imgur", 41 | "immer", 42 | "luozhu", 43 | "metafile", 44 | "Mixins", 45 | "mjsx", 46 | "Molunerfinn", 47 | "outdir", 48 | "picgo", 49 | "qiniu", 50 | "smms", 51 | "tcyun", 52 | "uploader", 53 | "Uploaders", 54 | "upupming", 55 | "upyun", 56 | "vspicgo", 57 | "weibo" 58 | ], 59 | "[markdown]": { 60 | "editor.tabSize": 4 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/release-tag.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 5 | 6 | name: Create Release 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: '14' 17 | 18 | # https://github.com/juliangruber/browser-run/issues/147#issuecomment-580958935 19 | - run: sudo apt-get install xvfb 20 | 21 | - name: yarn install, compile, and test coverage 22 | run: | 23 | # https://github.com/mui-org/material-ui/issues/12432#issuecomment-411046157 24 | yarn install --network-timeout 500000 25 | yarn build 26 | xvfb-run --auto-servernum yarn test 27 | 28 | # TODO: add coverage info 29 | # - name: Coveralls 30 | # uses: coverallsapp/github-action@master 31 | # with: 32 | # github-token: ${{ secrets.GITHUB_TOKEN }} 33 | 34 | - name: Get the version 35 | id: get_version 36 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 37 | 38 | - name: Create Release for Tag 39 | id: release_tag 40 | uses: yyx990803/release-tag@master 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | with: 44 | tag_name: ${{ github.ref }} 45 | body: | 46 | Please refer to [CHANGELOG.md](https://github.com/PicGo/vs-picgo/blob/${{ steps.get_version.outputs.VERSION }}/CHANGELOG.md) for details. 47 | 48 | - name: Publish to VSCode Marketplace 49 | uses: lannonbr/vsce-action@master 50 | with: 51 | args: 'publish -p $VSCE_TOKEN' 52 | env: 53 | VSCE_TOKEN: ${{ secrets.VSCE_TOKEN }} 54 | -------------------------------------------------------------------------------- /src/webview/utils/channel.ts: -------------------------------------------------------------------------------- 1 | import Channel from '@luozhu/vscode-channel' 2 | import { IMessageToShow } from '../../utils' 3 | import { W2VMessage } from '../../utils/message-method' 4 | import { IImgInfo } from 'picgo' 5 | import type { IUploaderConfig, PicgoAPI } from '../../vscode/PicgoAPI' 6 | 7 | export const channel = new Channel() 8 | 9 | /** 10 | * Convert a path in dist folder to vscode webview uri, webview in vscode can only load webview uri paths 11 | * Simply pass string imported from file loader will not work because local assets in vscode's webview must use vscode's Uri scheme 12 | * So we use an extra getWebviewUri call to get the Uri path 13 | */ 14 | export const getWebviewUri = async (url: string) => 15 | await channel.call(W2VMessage.GET_WEBVIEW_URI, url) 16 | 17 | /** 18 | * Show an message in VSCode bottom-down area 19 | */ 20 | export const showMessage = async (messageToShow: IMessageToShow) => 21 | await channel.call(W2VMessage.SHOW_MESSAGE, messageToShow) 22 | 23 | export const uploadFiles = async (files: string[]) => 24 | await channel.call(W2VMessage.UPLOAD_FILES, files) 25 | 26 | /** Execute extension commands */ 27 | export const executeCommand = async (command: string) => 28 | await channel.call(W2VMessage.EXECUTE_COMMAND, command) 29 | 30 | /** Get all picgo uploaders */ 31 | export const getAllUploaders = async () => 32 | await channel.call(W2VMessage.GET_ALL_UPLOADERS) 33 | 34 | /** Get all picgo uploaders' config */ 35 | export const getAllUploaderConfigs = async () => 36 | await channel.call( 37 | W2VMessage.GET_ALL_UPLOADER_CONFIGS 38 | ) 39 | 40 | /** Set picgo config */ 41 | export const setConfig = async (...args: Parameters) => 42 | await channel.call(W2VMessage.SET_CONFIG, args) 43 | -------------------------------------------------------------------------------- /src/webview/pages/Demo/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import logo from '../../../images/roundLogo.png' 3 | import { showMessage } from '../../utils/channel' 4 | import { Provider, useSelector, useDispatch } from 'react-redux' 5 | import { RootState, store, Dispatch } from './store' 6 | 7 | import './index.less' 8 | 9 | function Counter() { 10 | const common = useSelector((state: RootState) => state.common) 11 | const dispatch = useDispatch() 12 | 13 | return ( 14 |
15 |
{common.count}
16 | 17 | 18 |
19 | ) 20 | } 21 | 22 | export const Demo = () => { 23 | useEffect(() => { 24 | showMessage({ 25 | type: 'info', 26 | message: 'Hello from webview Demo page' 27 | }) 28 | }, []) 29 | 30 | return ( 31 | 32 |
33 |
34 | logo 35 |

Hello esbuild + React!

36 | 37 |

38 | Edit App.tsx and save to test browser-sync updates. 39 |

40 |

41 | 46 | Learn React 47 | 48 | {' | '} 49 | 54 | esbuild-react-less 55 | 56 |

57 |
58 |
59 |
60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /src/webview/pages/PicGoControlPanel/PicGoSettings/index.tsx: -------------------------------------------------------------------------------- 1 | import { Grid } from '@mui/material' 2 | import React from 'react' 3 | import { PicGoPluginConfigForm } from './PicGoPluginConfigForm' 4 | import { useState, useDispatch } from '../hooks' 5 | import { useParams } from 'react-router-dom' 6 | 7 | export interface IPicGoSettingsParams { 8 | uploaderID?: string 9 | pluginID?: string 10 | } 11 | 12 | export const PicGoSettings: React.FC = () => { 13 | const { allUploaderConfigs, currentUploaderConfigs } = useState('settings') 14 | const dispatch = useDispatch('settings') 15 | 16 | const params = useParams() 17 | 18 | return ( 19 | 20 | {/* If it is a uploader config page */} 21 | {params.uploaderID 22 | ? (() => { 23 | const config = allUploaderConfigs.find( 24 | (config) => config.uploaderID === params.uploaderID 25 | ) 26 | if (!config) return null 27 | const { uploaderID, configList, uploaderName } = config 28 | 29 | return ( 30 | { 35 | dispatch.updateCurrentUploaderConfigs({ 36 | uploaderID, 37 | configName, 38 | value 39 | }) 40 | }} 41 | pluginConfigList={configList} 42 | pluginId={uploaderID} 43 | pluginName={`uploader: ${uploaderName}`} 44 | /> 45 | ) 46 | })() 47 | : null} 48 | {/* If it is a plugin config page */} 49 | 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /src/vscode/utils/channel.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode' 2 | import { W2VMessage } from '../../utils/message-method' 3 | import { IMessageToShow } from '../../utils' 4 | import { showMessage } from '.' 5 | import Channel from '@luozhu/vscode-channel' 6 | import path from 'path' 7 | import { PanelManager } from '../PanelManager' 8 | import { CommandManager } from '../CommandManager' 9 | import { PicgoAPI } from '../PicgoAPI' 10 | 11 | /** 12 | * Each Webview has a different channel connected to vscode, so we should call `getChannel` to create a channel for one webview 13 | */ 14 | export const getChannel = ( 15 | context: vscode.ExtensionContext, 16 | panel: vscode.WebviewPanel 17 | ) => { 18 | const channel = new Channel(context, panel) 19 | 20 | channel.bind(W2VMessage.GET_WEBVIEW_URI, async (filename) => { 21 | return panel.webview 22 | .asWebviewUri( 23 | vscode.Uri.file( 24 | path.join( 25 | context.extensionPath, 26 | PanelManager.WEBVIEW_FOLDER, 27 | filename 28 | ) 29 | ) 30 | ) 31 | .toString() 32 | }) 33 | 34 | channel.bind(W2VMessage.SHOW_MESSAGE, (msg) => 35 | showMessage(msg) 36 | ) 37 | 38 | channel.bind(W2VMessage.UPLOAD_FILES, async (files) => { 39 | return await CommandManager.commandManager.uploadCommand(files) 40 | }) 41 | 42 | channel.bind(W2VMessage.EXECUTE_COMMAND, async (command) => { 43 | return await vscode.commands.executeCommand(command) 44 | }) 45 | 46 | channel.bind(W2VMessage.GET_ALL_UPLOADERS, () => { 47 | return PicgoAPI.picgoAPI.getAllUploaders() 48 | }) 49 | channel.bind(W2VMessage.GET_ALL_UPLOADER_CONFIGS, () => { 50 | return PicgoAPI.picgoAPI.getAllUploaderConfigs() 51 | }) 52 | 53 | channel.bind>( 54 | W2VMessage.SET_CONFIG, 55 | (request) => { 56 | return PicgoAPI.picgoAPI.setConfig(...request) 57 | } 58 | ) 59 | 60 | return channel 61 | } 62 | -------------------------------------------------------------------------------- /src/vscode/CommandManager.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import * as vscode from 'vscode' 4 | import { Editor } from './Editor' 5 | import { PicgoAPI } from './PicgoAPI' 6 | import { PicgoAddon } from './PicgoAddon' 7 | import { showError } from './utils' 8 | 9 | export class CommandManager { 10 | static commandManager: CommandManager = new CommandManager() 11 | 12 | async uploadCommand(input?: string[]) { 13 | const pluginName = 'vspicgo' 14 | PicgoAPI.picgoAPI.setCurrentPluginName(pluginName) 15 | const [id, plugin] = PicgoAddon.picgoAddon.beforeUploadPlugin() 16 | PicgoAPI.picgoAPI.helper.beforeUploadPlugins.register(id, plugin) 17 | 18 | const output = await PicgoAPI.picgoAPI.upload(input) 19 | PicgoAPI.picgoAPI.helper.beforeUploadPlugins.unregister(pluginName) 20 | 21 | // error has been handled in picgoAPI.upload 22 | if (!output) return 23 | 24 | const outputString = PicgoAddon.picgoAddon.outputToString(output) 25 | 26 | vscode.env.clipboard.writeText(outputString) 27 | await Editor.writeToEditor(outputString) 28 | return outputString 29 | } 30 | 31 | async uploadImageFromClipboard() { 32 | this.uploadCommand() 33 | } 34 | 35 | async uploadImageFromExplorer() { 36 | const result = await vscode.window.showOpenDialog({ 37 | filters: { 38 | Images: [ 39 | 'png', 40 | 'jpg', 41 | 'jpeg', 42 | 'webp', 43 | 'gif', 44 | 'bmp', 45 | 'tiff', 46 | 'ico', 47 | 'svg' 48 | ] 49 | }, 50 | canSelectMany: true 51 | }) 52 | 53 | if (result != null) { 54 | const input = result.map((item) => item.fsPath) 55 | this.uploadCommand(input) 56 | } 57 | } 58 | 59 | async uploadImageFromInputBox() { 60 | let result = await vscode.window.showInputBox({ 61 | placeHolder: 'Please input an image location path' 62 | }) 63 | // check if `result` is a path of image file 64 | const imageReg = /\.(png|jpg|jpeg|webp|gif|bmp|tiff|ico|svg)$/ 65 | if (result && imageReg.test(result)) { 66 | result = path.isAbsolute(result) 67 | ? result 68 | : path.join(Editor.editor?.document.uri.fsPath ?? '', '../', result) 69 | if (fs.existsSync(result)) { 70 | return await this.uploadCommand([result]) 71 | } else { 72 | showError('No such image.') 73 | } 74 | } else { 75 | showError('No such image.') 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-escape */ 2 | /* eslint-disable no-template-curly-in-string */ 3 | /* eslint-disable @typescript-eslint/no-misused-promises */ 4 | /* eslint-disable no-undef */ 5 | import * as assert from 'assert' 6 | import { workspace, window, Selection } from 'vscode' 7 | import { PicgoAPI } from '../../src/vscode/PicgoAPI' 8 | import { uploadName } from '../../src/vscode/settings' 9 | 10 | import { 11 | TEST_MD_FILE_PATH, 12 | TEST_PICTURE_PATH, 13 | VSPicgoUploadStarter 14 | } from '../utils' 15 | 16 | const REG4CUSTOM_OUTPUT_FORMAT = /^\!\[.+\]\(.+\)$/ 17 | const REG4CUSTOM_FILE_FORMAT = /^\!\[\d{4}\-\d{2}\-\d{2}]\(.+\)$/ 18 | 19 | before(async () => { 20 | const document = await workspace.openTextDocument(TEST_MD_FILE_PATH) 21 | await window.showTextDocument(document) 22 | }) 23 | 24 | describe('VSPicgo', async function () { 25 | it('customOutputFormat should work', async function () { 26 | this.timeout(1e10) 27 | PicgoAPI.picgoAPI.setConfig( 28 | 'settings.vsPicgo.customUploadName', 29 | uploadName.auto 30 | ) 31 | const res = await VSPicgoUploadStarter({ 32 | args4uploader: [TEST_PICTURE_PATH], 33 | editor: { 34 | content: '', 35 | selection: new Selection(0, 0, 0, 0) 36 | } 37 | }) 38 | console.log('customOutputFormat result: ', res) 39 | assert.equal(REG4CUSTOM_OUTPUT_FORMAT.test(res), true) 40 | }) 41 | 42 | it('customUploadName should work', async function () { 43 | this.timeout(1e10) 44 | PicgoAPI.picgoAPI.setConfig( 45 | 'settings.vsPicgo.customUploadName', 46 | uploadName.dateExt 47 | ) 48 | const res = await VSPicgoUploadStarter({ 49 | args4uploader: [TEST_PICTURE_PATH], 50 | editor: { 51 | content: '', 52 | selection: new Selection(0, 0, 0, 0) 53 | } 54 | }) 55 | console.log('customUploadName result: ', res) 56 | assert.equal(REG4CUSTOM_FILE_FORMAT.test(res), true) 57 | }) 58 | 59 | it('selection as fileName should work', async function () { 60 | this.timeout(1e10) 61 | PicgoAPI.picgoAPI.setConfig( 62 | 'settings.vsPicgo.customUploadName', 63 | uploadName.auto 64 | ) 65 | const res = await VSPicgoUploadStarter({ 66 | args4uploader: [TEST_PICTURE_PATH], 67 | editor: { 68 | content: 'TEST', 69 | selection: new Selection(0, 0, 0, 4) 70 | } 71 | }) 72 | console.log('selection as fileName result: ', res) 73 | assert.equal(res.indexOf('TEST'), 2) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /src/webview/pages/PicGoControlPanel/models/settings.ts: -------------------------------------------------------------------------------- 1 | import { createModel } from '@rematch/core' 2 | import type { IUploaderConfig } from '../../../../vscode/PicgoAPI' 3 | import { IRootModel } from '../models' 4 | import { setConfig } from '../../../utils/channel' 5 | export interface IPicGoSettingState { 6 | /** 7 | * Only update `allUploaderConfigs` on mounted and new uploader installed, it is the initial state (its default config is read from the data.json file). 8 | */ 9 | allUploaderConfigs: IUploaderConfig[] 10 | /** 11 | * Current config is synced with user input and will write to data.json config file immediately 12 | * 13 | * currentUploaderConfigs[uploaderID][config.name] = value 14 | */ 15 | currentUploaderConfigs: Record> 16 | } 17 | 18 | export interface IUpdateCurrentUploaderConfigsPayload { 19 | uploaderID: string 20 | configName: string 21 | value: any 22 | } 23 | 24 | export const defaultCommonState: IPicGoSettingState = { 25 | allUploaderConfigs: [], 26 | currentUploaderConfigs: {} 27 | } 28 | 29 | export const settings = createModel()({ 30 | state: defaultCommonState, 31 | reducers: { 32 | /** 33 | * This reducer should be called on paged loaded so that we have the initial `currentUploaderConfigs` state for page to render correctly, such as the uploader list in the drawer and the whole settings page. 34 | */ 35 | setAllUploaderConfigs(state, allUploaderConfigs: IUploaderConfig[]) { 36 | state.allUploaderConfigs = allUploaderConfigs 37 | /** 38 | * Once we go new `allUploaderConfigs`, we will start with a new editing state, i.e., we recalculate `currentUploaderConfigs` 39 | */ 40 | state.currentUploaderConfigs = {} 41 | allUploaderConfigs.forEach(({ configList, uploaderID }) => { 42 | state.currentUploaderConfigs[uploaderID] ??= {} 43 | configList?.forEach((config) => { 44 | state.currentUploaderConfigs[uploaderID][config.name] = config.default 45 | }) 46 | }) 47 | }, 48 | updateCurrentUploaderConfigs( 49 | state, 50 | { uploaderID, configName, value }: IUpdateCurrentUploaderConfigsPayload 51 | ) { 52 | setConfig(`picBed.${uploaderID}.${configName}`, value) 53 | state.currentUploaderConfigs[uploaderID][configName] = value 54 | } 55 | }, 56 | selectors: (slice, createSelector, hasProps) => ({ 57 | uploaderTitleItems() { 58 | return slice((settings) => { 59 | return settings.allUploaderConfigs.map((uploaderConfig) => { 60 | return { 61 | id: uploaderConfig.uploaderID, 62 | title: uploaderConfig.uploaderName 63 | } 64 | }) 65 | }) 66 | } 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /src/webview/pages/PicGoControlPanel/PicGoDrawerList/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | List, 3 | ListItemButton, 4 | ListItemText, 5 | ListItemIcon, 6 | Collapse 7 | } from '@mui/material' 8 | import * as MuiIconsMaterial from '@mui/icons-material' 9 | import React from 'react' 10 | import { useHistory, useLocation } from 'react-router-dom' 11 | import { useSelector } from '../hooks' 12 | 13 | /** 14 | * The menu list in the drawer. 15 | * This list will help redirect to the different route 16 | * to show different routes. The current activated route 17 | * will highlighted as selected item in the list. 18 | */ 19 | export const PicGoDrawerList = () => { 20 | const history = useHistory() 21 | const { pathname } = useLocation() 22 | 23 | const uploaderTitleItems = useSelector('settings', 'uploaderTitleItems') 24 | 25 | const [uploaderListOpened, setUploaderListOpened] = React.useState(false) 26 | 27 | return ( 28 | 29 | history.push('/')} 31 | selected={pathname === '/'} 32 | sx={{ 33 | borderRadius: 4, 34 | my: 0.5 35 | }}> 36 | 37 | 38 | 39 | 40 | 41 | 42 | history.push('/gallery')} 44 | selected={pathname === '/gallery'} 45 | sx={{ 46 | borderRadius: 4, 47 | my: 0.5 48 | }}> 49 | 50 | 51 | 52 | 53 | 54 | 55 | {/* Nest settings of uploaders as nested list item: https://mui.com/components/lists/#nested-list */} 56 | setUploaderListOpened(!uploaderListOpened)} 58 | sx={{ 59 | borderRadius: 4, 60 | my: 0.5 61 | }}> 62 | 63 | 64 | 65 | 66 | {uploaderListOpened ? ( 67 | 68 | ) : ( 69 | 70 | )} 71 | 72 | 73 | 74 | {uploaderTitleItems.map((titleItem) => ( 75 | history.push(`/settings/uploader/${titleItem.id}`)} 78 | selected={pathname === `/settings/uploader/${titleItem.id}`} 79 | sx={{ 80 | borderRadius: 4, 81 | textAlign: 'center', 82 | my: 0.5 83 | }}> 84 | 85 | 86 | ))} 87 | 88 | 89 | 90 | ) 91 | } 92 | -------------------------------------------------------------------------------- /src/webview/components/ThemeWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { DefaultTheme } from '@mui/system' 3 | import { ThemeProvider, createTheme } from '@mui/material/styles' 4 | import CssBaseline from '@mui/material/CssBaseline' 5 | 6 | const calculateTheme = () => { 7 | const body = document.body 8 | const computedStyle = window.getComputedStyle(body) 9 | const getPropertyValue = (property: string) => 10 | computedStyle.getPropertyValue(property).trim() 11 | const getPropertyValueFormList = (propertyList: string[]) => { 12 | for (const property of propertyList) { 13 | const value = getPropertyValue(property) 14 | if (value) return value 15 | } 16 | return '' 17 | } 18 | const fontFamily = getPropertyValueFormList([ 19 | '--vscode-font-family', 20 | '--vscode-editor-font-family' 21 | ]) 22 | const fontSize = parseFloat( 23 | getPropertyValueFormList([ 24 | '--vscode-font-size', 25 | '--vscode-editor-font-size' 26 | ]) 27 | ) 28 | const fontWeight = getPropertyValueFormList([ 29 | '--vscode-font-weight', 30 | '--vscode-editor-font-weight' 31 | ]) as React.CSSProperties['fontWeight'] 32 | 33 | const background = getPropertyValue('--vscode-editor-background') 34 | 35 | return createTheme({ 36 | // See docs at https://mui.com/customization/typography/ 37 | typography: { 38 | fontFamily, 39 | fontSize, 40 | fontWeightRegular: fontWeight 41 | }, 42 | palette: { 43 | background: { 44 | default: background, 45 | paper: background 46 | }, 47 | mode: [...document.body.classList].includes('vscode-dark') 48 | ? 'dark' 49 | : 'light', 50 | primary: { 51 | // `light` & `dark` will auto calculated according to `main` 52 | main: '#409EFF' 53 | }, 54 | secondary: { 55 | main: '#67C23A' 56 | }, 57 | error: { 58 | main: getPropertyValue('--vscode-terminal-ansiRed') 59 | }, 60 | warning: { 61 | main: getPropertyValue('--vscode-terminal-ansiYellow') 62 | }, 63 | info: { 64 | main: getPropertyValue('--vscode-terminal-ansiBlue') 65 | }, 66 | success: { 67 | main: getPropertyValue('--vscode-terminal-ansiGreen') 68 | }, 69 | common: { 70 | white: getPropertyValue('--vscode-terminal-ansiWhite'), 71 | black: getPropertyValue('--vscode-terminal-ansiBlack') 72 | } 73 | } 74 | }) 75 | } 76 | 77 | export const ThemeWrapper: React.FunctionComponent = ({ children }) => { 78 | const [theme, setTheme] = useState() 79 | 80 | useEffect(() => { 81 | const onVSCodeColorThemeChanged = () => { 82 | const theme = calculateTheme() 83 | setTheme(theme) 84 | } 85 | const observer = new MutationObserver(onVSCodeColorThemeChanged) 86 | observer.observe(document.body, { 87 | attributes: true, 88 | attributeFilter: ['class'] 89 | }) 90 | onVSCodeColorThemeChanged() 91 | }, []) 92 | 93 | return theme ? ( 94 | 95 | 96 | {children} 97 | 98 | ) : null 99 | } 100 | -------------------------------------------------------------------------------- /docs/README_ZH.md: -------------------------------------------------------------------------------- 1 | # vs-picgo(Vscode Plugin for PicGo) 2 | 3 | > [PicGo](https://github.com/PicGo) 的 vscode 插件 4 | 5 | ## 功能 6 | 7 | 在 VSCode 里使用 picgo,实现快速上传图片到远端图床并直接将 URL 写进 Markdown 文件里,极大提升 Markdown 贴图效率与体验。支持 [PicGo](https://github.com/Molunerfinn/PicGo) 原生自带的 8 种图床。 8 | 9 | - 截图上传 10 | 11 | ![](https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/vs-picgo-clipboard.gif) 12 | 13 | - 文件管理器选择上传 14 | 15 | ![](https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/vs-picgo-explorer.gif) 16 | 17 | - 输入文件路径上传 18 | 19 | ![](https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/vs-picgo-inputbox.gif) 20 | 21 | ## 配置 22 | 23 | - 0 配置:采用默认配置,默认配置中图床采用 SM.MS 24 | - 自定义配置: 25 | 26 | 在 vscode 配置信息文件 `usersetting.json` 中加入 27 | 28 | ```js 29 | { 30 | "picgo": { 31 | "path": "path to your configure file" // 默认为空,则表示使用VSCode的setting.json 32 | }, 33 | "picBed": { 34 | "current": "smms" // 默认使用 SM.MS 图床 35 | } 36 | } 37 | ``` 38 | 39 | **如果你指定的`picgo`的`path`为空,那么将使用 VSCode 默认的`setting.json`作为配置文件。** 40 | 41 | 配置文件内容(usersetting.json 文件中 picgo.path 路径指定的文件)里需要配置的项主要是`picBed`: 42 | 详细信息可参看 [PicGo-配置](https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#%E9%BB%98%E8%AE%A4%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6) 43 | 44 | ```js 45 | { 46 | "picBed": { 47 | "current": "smms", // 代表当前的默认上传图床为SM.MS 48 | "weibo":{ // 微博图床配置 49 | "chooseCookie": true | false, 50 | "username": "", 51 | "password": "", 52 | "quality": "thumbnail" | "mw690" | "large", 53 | "cookie": "" 54 | }, 55 | "qiniu": { // 七牛图床配置 56 | "accessKey": "", 57 | "secretKey": "", 58 | "bucket": "", 59 | "url": "", 60 | "area": "", //"z0" -> 华东, "z1" -> 华北, "z2" -> 华南, "na0" -> 北美, "as0" -> 东南亚 61 | "options": "" //网址后缀 62 | "path":"" // 路径前缀 63 | }, 64 | "upyun": { // 又拍云图床配置 65 | "bucket": "", 66 | "operator": "", 67 | "password": "", 68 | "options": "", 69 | "path": "", 70 | "url": "" 71 | }, 72 | "tcyun": { // 腾讯云图床配置 73 | "secretId": "", 74 | "secretKey": "", 75 | "bucket": "", 76 | "appId": "", 77 | "area": "", 78 | "path": "", 79 | "customUrl": "", 80 | "version": "v5" | "v4" 81 | }, 82 | "github": { // github图床配置 83 | "repo": "", 84 | "token": "", 85 | "path": "", 86 | "customUrl": "", 87 | "branch": "", 88 | "username": "" 89 | }, 90 | "aliyun": { // 阿里云图床配置 91 | "accessKeyId": "", 92 | "accessKeySecret": "", 93 | "bucket": "", 94 | "area": "", 95 | "path": "", 96 | "customUrl": "" 97 | } 98 | } 99 | } 100 | ``` 101 | 102 | ## 键盘快捷键 103 | 104 | 1. 剪贴板图片上传:`ctrlOrCmd+alt+u` 105 | 2. 打开文件管理器上传:`ctrlOrCmd+alt+e` 106 | 3. 打开输入框输入路径上传:`ctrlOrCmd+alt+o` 107 | 108 | 以上快捷键均可重新自定义。 109 | 110 | ## 使用 111 | 112 | - 在插件商店中查找**PicGo**,并安装 113 | - 安装完成后,可在命令面板中输入 114 | - Upload image from inputBox [从输入框上传] 115 | - Upload image from explorer [从文件管理器上传] 116 | - Upload image from clipboard [从剪切板上传] 117 | - 推荐:使用键盘快捷方式(可自行修改) 118 | - 选定文本再上传的话会使用选定的文本作为文件名 119 | 120 | ## Contributers 121 | 122 | - [Spades-S](https://github.com/Spades-S) 123 | - [Molunerfinn](https://github.com/Molunerfinn) 124 | 125 | ## Thanks 126 | 127 | - [PicGo-Core](https://github.com/PicGo/PicGo-Core) 128 | 129 | **Enjoy!** 130 | -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext.displayName": "PicGo", 3 | "ext.description": "A fast and powerful image uploading plugin for VSCode based on PicGo. Supports most image types (png, jpg, gif, etc.) and many image hosting services (smms, qiniu, imgur, etc.)!", 4 | "command.upload.clipboard.title": "Upload from clipboard", 5 | "command.upload.explorer.title": "Upload from explorer", 6 | "command.upload.inputBox.title": "Upload from input box", 7 | "command.webview.demo.title": "Open demo page", 8 | "command.webview.picGoControlPanel.title": "Open PicGo Control Panel", 9 | "config.title": "PicGo", 10 | "config.configPath.description": "The path to your PicGo-Core configuration. PicGo will use `#picgo.picBed#` if this is not specified. Please see [PicGo-Core Docs](https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#%E9%BB%98%E8%AE%A4%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6) and [PicGo Docs](https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E5%9B%BE%E5%BA%8A%E5%8C%BA) for more detail about configuration file.", 11 | "config.dataPath.description": "The path to your data file, including all uploaded images' info. PicGo will use `your_home_dir/vs-picgo-data.json` if this is not specified.", 12 | "config.customUploadName.description": "Customize the name of the image to be uploaded, image will be renamed before uploading.\n- `${fileName}`: the name of the original image, without extension name.\n **Notice: If you selected some text before uploading, the selection will become the `fileName` of the image to be uploaded.**\n- `${extName}`: the extension name of the original image.\n- `${mdFileName}`: the name of the current editing markdown file.\n- `${date}`: YY-MM-DD formatted date.\n- `${dateTime}`: YY-MM-DD-hh-mm-ss formatted date.\n\nExamples:\n- `${fileName}-${date}${extName}` -> `picName-2016-07-25.jpg`\n- `${mdFileName}`-`${dateTime}${extName}` -> `markdownName-2017-04-12-22-28-10.jpg`", 13 | "config.customOutputFormat.description": "Customize the output format of the uploaded image.\n- `${url}`: the url of the uploaded image.\n- `${uploadedName}`: the name of the uploaded image without extension name, see `#picgo.customUploadName#`, note that even if you used `${extName}` in `#picgo.customUploadName#`, there still will be no extension name in the output.\n\nExamples:\n- `![${uploadedName}](${url})` -> `![picName-2016-07-25](https://example.com/xxx.jpg)`\n- `\"${uploadedName}\"` -> `\"picName-2016-07-25\"`", 14 | "config.picBed.uploader": "Please see [`picBed.uploader`](https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#picbed-uploader).", 15 | "config.picBed.current": "Please see [`picBed.current`](https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#picbed-current).", 16 | "config.picBed.proxy": "Proxy for `request`, see [`picBed.proxy`](https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#picbed-proxy) for more detail.", 17 | "config.picBed.smms.description": "SM.MS picBed configuration.", 18 | "config.picBed.weibo.description": "Weibo picBed configuration.", 19 | "config.picBed.qiniu.description": "Qiniu picBed configuration.", 20 | "config.picBed.upyun.description": "Upyun picBed configuration.", 21 | "config.picBed.tcyun.description": "Tencent COS picBed configuration.", 22 | "config.picBed.github.description": "GitHub configuration.", 23 | "config.picBed.github.repo.description": "`Username/Repo`. For example, PicGo/Images", 24 | "config.picBed.aliyun.description": "Aliyun OSS configuration.", 25 | "config.picBed.imgur.description": "Imgur picBed configuration." 26 | } 27 | -------------------------------------------------------------------------------- /esbuild.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import globby from 'globby' 3 | import esbuild from 'esbuild' 4 | import fse from 'fs-extra' 5 | import minimist from 'minimist' 6 | import { lessLoader } from 'esbuild-plugin-less' 7 | import inlineImportPlugin from 'esbuild-plugin-inline-import' 8 | 9 | const args = minimist(process.argv.slice(2)) 10 | const isWatch = args.watch || args.w 11 | const isTest = args.test || args.t 12 | const isProduction = args.production 13 | 14 | // Following the log format of https://github.com/connor4312/esbuild-problem-matchers 15 | const status = (msg) => console.log(`${isWatch ? '[watch] ' : ''}${msg}`) 16 | 17 | const firstBuildFinished = new Set() 18 | let buildStartTime 19 | 20 | /** @type {import('esbuild').Plugin} */ 21 | const watchPlugin = (type) => ({ 22 | name: 'watcher', 23 | setup(build) { 24 | build.onStart(() => { 25 | buildStartTime = Date.now() 26 | status(`${type} build started.`) 27 | }) 28 | build.onEnd((result) => { 29 | result.errors.forEach((error) => 30 | console.error( 31 | `> ${error.location.file}:${error.location.line}:${error.location.column}: error: ${error.text}` 32 | ) 33 | ) 34 | firstBuildFinished.add(type) 35 | status(`${type} build finished in ${Date.now() - buildStartTime} ms.`) 36 | if (firstBuildFinished.size === 2) { 37 | // esbuild problem matcher extension is listening for this log, once this is logged, it will open the Extension Host 38 | // So we have to assure only printing this when both extension and webview have been built 39 | status(`build finished in ${Date.now() - buildStartTime} ms.`) 40 | } 41 | }) 42 | } 43 | }) 44 | const resultHandler = async (result) => { 45 | result.metafile && 46 | console.log( 47 | await esbuild.analyzeMetafile(result.metafile, { 48 | verbose: true 49 | }) 50 | ) 51 | } 52 | 53 | const outdir = './dist' 54 | 55 | // clean old built files 56 | fse.rmdirSync(outdir, { recursive: true }) 57 | 58 | /** @type {import('esbuild').BuildOptions} */ 59 | const commonOptions = { 60 | bundle: true, 61 | sourcemap: isProduction ? false : 'inline', 62 | watch: isWatch, 63 | loader: { 64 | '.js': 'jsx', 65 | '.png': 'dataurl', 66 | '.jpg': 'dataurl', 67 | '.svg': 'dataurl', 68 | '.woff': 'dataurl', 69 | '.woff2': 'dataurl' 70 | }, 71 | define: { 72 | 'process.env.NODE_ENV': isProduction ? '"production"' : '"development"' 73 | }, 74 | minify: isProduction 75 | // metafile: true 76 | } 77 | 78 | // build extension (node app) 79 | esbuild 80 | .build({ 81 | ...commonOptions, 82 | outdir, 83 | entryPoints: isTest ? globby.sync('test/**/*.ts') : ['src/extension.ts'], 84 | external: isTest 85 | ? ['vscode', 'mocha', 'istanbul', 'electron'] 86 | : ['vscode', 'electron'], 87 | format: 'cjs', 88 | platform: 'node', 89 | mainFields: ['module', 'main'], 90 | plugins: [watchPlugin('extension'), inlineImportPlugin()] 91 | }) 92 | .then(resultHandler) 93 | .catch(() => { 94 | process.exit(1) 95 | }) 96 | 97 | // build webview (web app) 98 | esbuild 99 | .build({ 100 | ...commonOptions, 101 | outdir: `${outdir}/webview`, 102 | entryPoints: ['src/webview/pages/index.tsx'], 103 | target: ['chrome58'], 104 | format: 'esm', 105 | plugins: [lessLoader(), watchPlugin('webview')] 106 | // publicPath: 'https://www.example.com/v1', 107 | }) 108 | .then(resultHandler) 109 | .catch(() => { 110 | process.exit(1) 111 | }) 112 | -------------------------------------------------------------------------------- /src/vscode/PanelManager.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode' 2 | import pupa from 'pupa' 3 | import templateHtml from 'inline:./template.html' 4 | import logo from '../images/squareLogo.png' 5 | import Channel from '@luozhu/vscode-channel' 6 | import path from 'path' 7 | import { PageId, pageMap } from '../utils/page' 8 | import { getChannel } from './utils/channel' 9 | 10 | /** 11 | * Type of variables that will be passed to the html template 12 | */ 13 | export interface IHtmlConfig { 14 | pageId: PageId 15 | jsSrc: string 16 | cssHref: string 17 | } 18 | 19 | /** 20 | * Manage all webview pages in one panel manager 21 | */ 22 | export class PanelManager { 23 | static panelManager: PanelManager 24 | static bindContext(context: vscode.ExtensionContext) { 25 | this.panelManager = new PanelManager(context) 26 | } 27 | 28 | context: vscode.ExtensionContext 29 | pageId2WebviewPanel: Map 30 | static WEBVIEW_FOLDER = 'dist/webview' 31 | static DIST_FOLDER = 'dist' 32 | private constructor(context: vscode.ExtensionContext) { 33 | this.context = context 34 | this.pageId2WebviewPanel = new Map() 35 | } 36 | 37 | /** 38 | * 39 | * @param webview The webview container 40 | * @param pageId The id of the page respect to in webview/pages/pageId.tsx 41 | * @returns Html constructed for this page 42 | */ 43 | getPageHtml(webview: vscode.Webview, pageId: PageId) { 44 | const webviewIndex = path.join( 45 | this.context.extensionPath, 46 | PanelManager.WEBVIEW_FOLDER, 47 | 'index' 48 | ) 49 | const getUriStr = (type: string) => 50 | webview 51 | .asWebviewUri(vscode.Uri.file(`${webviewIndex}.${type}`)) 52 | .toString() 53 | const htmlConfig: IHtmlConfig = { 54 | pageId: pageId, 55 | jsSrc: getUriStr('js'), 56 | cssHref: getUriStr('css') 57 | } 58 | return pupa(templateHtml, htmlConfig) 59 | } 60 | 61 | getViewType(pageId: PageId) { 62 | return `vs-picgo:${pageId}` 63 | } 64 | 65 | getTitle(pageId: PageId) { 66 | return pageMap[pageId] 67 | } 68 | 69 | createOrShowWebviewPanel(pageId: PageId) { 70 | const column = vscode.window.activeTextEditor 71 | ? vscode.window.activeTextEditor.viewColumn 72 | : undefined 73 | 74 | // If we already have a panel, show it. 75 | if (this.pageId2WebviewPanel.has(pageId)) { 76 | this.pageId2WebviewPanel.get(pageId)?.[0].reveal(column) 77 | return 78 | } 79 | // Otherwise, create a new panel. 80 | const panel = vscode.window.createWebviewPanel( 81 | this.getViewType(pageId), 82 | this.getTitle(pageId), 83 | column ?? vscode.ViewColumn.One, 84 | { 85 | // Enable javascript in the webview 86 | enableScripts: true, 87 | 88 | // And restrict the webview to only loading content from our extension's `dist` directory. 89 | localResourceRoots: [ 90 | vscode.Uri.joinPath( 91 | this.context.extensionUri, 92 | PanelManager.DIST_FOLDER 93 | ) 94 | ], 95 | retainContextWhenHidden: true 96 | } 97 | ) 98 | panel.iconPath = vscode.Uri.parse(logo) 99 | panel.webview.html = this.getPageHtml(panel.webview, pageId) 100 | panel.onDidDispose( 101 | () => { 102 | this.pageId2WebviewPanel.delete(pageId) 103 | }, 104 | null, 105 | this.context.subscriptions 106 | ) 107 | 108 | this.pageId2WebviewPanel.set(pageId, [ 109 | panel, 110 | // Each Webview has a associated channel 111 | getChannel(this.context, panel) 112 | ]) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/webview/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | /* eslint-disable @typescript-eslint/triple-slash-reference */ 3 | /// 4 | /// 5 | /// 6 | /// 7 | 8 | declare namespace NodeJS { 9 | interface ProcessEnv { 10 | readonly NODE_ENV: 'development' | 'production' | 'test' 11 | readonly PUBLIC_URL: string 12 | } 13 | } 14 | declare module '*.html' { 15 | const html: string 16 | export default html 17 | } 18 | declare module '*.css' { 19 | const css: string 20 | export default css 21 | } 22 | declare module '*.scss' { 23 | const css: string 24 | export default css 25 | } 26 | declare module '*.sass' { 27 | const css: string 28 | export default css 29 | } 30 | declare module '*.less' { 31 | const css: string 32 | export default css 33 | } 34 | declare module '*.styl' { 35 | const css: string 36 | export default css 37 | } 38 | declare module '*.stylus' { 39 | const css: string 40 | export default css 41 | } 42 | declare module '*.pcss' { 43 | const css: string 44 | export default css 45 | } 46 | 47 | // Built-in asset types 48 | // see `src/constants.ts` 49 | 50 | // images 51 | declare module '*.jpg' { 52 | const src: string 53 | export default src 54 | } 55 | declare module '*.jpeg' { 56 | const src: string 57 | export default src 58 | } 59 | declare module '*.png' { 60 | const src: string 61 | export default src 62 | } 63 | declare module '*.gif' { 64 | const src: string 65 | export default src 66 | } 67 | declare module '*.svg' { 68 | const src: string 69 | export default src 70 | } 71 | declare module '*.ico' { 72 | const src: string 73 | export default src 74 | } 75 | declare module '*.webp' { 76 | const src: string 77 | export default src 78 | } 79 | declare module '*.avif' { 80 | const src: string 81 | export default src 82 | } 83 | 84 | // media 85 | declare module '*.mp4' { 86 | const src: string 87 | export default src 88 | } 89 | declare module '*.webm' { 90 | const src: string 91 | export default src 92 | } 93 | declare module '*.ogg' { 94 | const src: string 95 | export default src 96 | } 97 | declare module '*.mp3' { 98 | const src: string 99 | export default src 100 | } 101 | declare module '*.wav' { 102 | const src: string 103 | export default src 104 | } 105 | declare module '*.flac' { 106 | const src: string 107 | export default src 108 | } 109 | declare module '*.aac' { 110 | const src: string 111 | export default src 112 | } 113 | 114 | // fonts 115 | declare module '*.woff' { 116 | const src: string 117 | export default src 118 | } 119 | declare module '*.woff2' { 120 | const src: string 121 | export default src 122 | } 123 | declare module '*.eot' { 124 | const src: string 125 | export default src 126 | } 127 | declare module '*.ttf' { 128 | const src: string 129 | export default src 130 | } 131 | declare module '*.otf' { 132 | const src: string 133 | export default src 134 | } 135 | 136 | // other 137 | declare module '*.wasm' { 138 | const src: string 139 | export default src 140 | } 141 | declare module '*.webmanifest' { 142 | const src: string 143 | export default src 144 | } 145 | declare module '*.pdf' { 146 | const src: string 147 | export default src 148 | } 149 | 150 | declare module '*?raw' { 151 | const src: string 152 | export default src 153 | } 154 | 155 | declare module '*?url' { 156 | const src: string 157 | export default src 158 | } 159 | 160 | declare module '*?inline' { 161 | const src: string 162 | export default src 163 | } 164 | -------------------------------------------------------------------------------- /src/vscode/PicgoAddon.ts: -------------------------------------------------------------------------------- 1 | import { IPicGo, IImgInfo, ILifecyclePlugins } from 'picgo' 2 | import { PicgoAPI } from './PicgoAPI' 3 | import path from 'path' 4 | import { Editor } from './Editor' 5 | import { handleUrlEncode } from '../utils' 6 | 7 | export interface IUploadName { 8 | date: string 9 | dateTime: string 10 | fileName: string 11 | extName: string 12 | mdFileName: string 13 | editorSelectionText: string 14 | imgIdx: string 15 | } 16 | 17 | export interface IOutputUrl { 18 | uploadedName: string 19 | url: string 20 | } 21 | 22 | /** 23 | * Some helpful plugins or functions to enhance picgo core 24 | */ 25 | export class PicgoAddon { 26 | static picgoAddon = new PicgoAddon() 27 | // eslint-disable-next-line no-useless-constructor 28 | private constructor() {} 29 | 30 | get editorSelectionText() { 31 | let selectedString = 32 | Editor.editor?.document.getText(Editor.editor.selection) ?? '' 33 | const nameReg = /[:/?$]+/g // limitations of name 34 | selectedString = selectedString?.replace(nameReg, () => '') 35 | return selectedString 36 | } 37 | 38 | get mdFilePath() { 39 | return Editor.editor?.document.fileName ?? '' 40 | } 41 | 42 | get mdFileName() { 43 | return path.basename(this.mdFilePath, path.extname(this.mdFilePath)) 44 | } 45 | 46 | formatParam( 47 | file: string, 48 | editorSelectionText: string, 49 | mdFileName: string, 50 | imgIdx: string 51 | ): IUploadName { 52 | const dt = new Date() 53 | const y = dt.getFullYear() 54 | const m = dt.getMonth() + 1 55 | const d = dt.getDate() 56 | const h = dt.getHours() 57 | const mm = dt.getMinutes() 58 | const s = dt.getSeconds() 59 | 60 | const pad = function (x: number) { 61 | return `00${x}`.slice(-2) 62 | } 63 | 64 | const date = `${y}-${pad(m)}-${pad(d)}` 65 | const extName = path.extname(file) 66 | 67 | return { 68 | date, 69 | dateTime: `${date}-${pad(h)}-${pad(mm)}-${pad(s)}`, 70 | fileName: path.basename(file, extName), 71 | extName, 72 | editorSelectionText, 73 | mdFileName, 74 | imgIdx 75 | } 76 | } 77 | 78 | formatString(tplString: string, data: IUploadName | IOutputUrl): string { 79 | const keys = Object.keys(data) as Array 80 | const values = keys.map((k) => data[k]) 81 | // eslint-disable-next-line no-new-func, @typescript-eslint/no-implied-eval 82 | return new Function(keys.join(','), 'return `' + tplString + '`').apply( 83 | null, 84 | values 85 | ) 86 | } 87 | 88 | beforeUploadPlugin(): Parameters { 89 | return [ 90 | 'VspicgoBeforeUploadPlugin', 91 | { 92 | handle: (ctx: IPicGo) => { 93 | const customUploadNameTemplate = PicgoAPI.picgoAPI.getConfig( 94 | 'settings.vsPicgo.customUploadName' 95 | ) 96 | ctx.output.forEach((imgInfo: IImgInfo, index: number, imgs) => { 97 | imgInfo.fileName = this.formatString( 98 | customUploadNameTemplate, 99 | this.formatParam( 100 | imgInfo.fileName ?? '', 101 | this.editorSelectionText, 102 | this.mdFileName, 103 | imgs.length > 1 ? String(index) : '' 104 | ) 105 | ) 106 | }) 107 | } 108 | } 109 | ] 110 | } 111 | 112 | /** 113 | * Return uploaded name according to `imgInfo.fileName`, 114 | * extname will be removed for the sake of simplicity when used as alt. 115 | * @param imgInfo 116 | */ 117 | getUploadedName(imgInfo: IImgInfo): string { 118 | const fullName = imgInfo.fileName ?? '' 119 | const basename = path.basename(fullName, path.extname(fullName)) 120 | return basename 121 | } 122 | 123 | outputToString(output: IImgInfo[]) { 124 | const customOutputFormatTemplate = PicgoAPI.picgoAPI.getConfig( 125 | 'settings.vsPicgo.customOutputFormat' 126 | ) 127 | return output 128 | .map((imgInfo) => 129 | this.formatString(customOutputFormatTemplate, { 130 | uploadedName: this.getUploadedName(imgInfo), 131 | url: handleUrlEncode(imgInfo.imgUrl ?? '') 132 | }) 133 | ) 134 | .join('\n') 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/webview/pages/PicGoControlPanel/PicGoSettings/PicGoPluginConfigForm.tsx: -------------------------------------------------------------------------------- 1 | import { IPluginConfig } from 'picgo' 2 | import React from 'react' 3 | import { 4 | Grid, 5 | Paper, 6 | TextField, 7 | Select, 8 | MenuItem, 9 | Typography, 10 | Box, 11 | Checkbox, 12 | FormControlLabel 13 | } from '@mui/material' 14 | 15 | export interface IPicGoPluginConfigFormProps { 16 | pluginId: string 17 | pluginName: string 18 | pluginConfigList?: IPluginConfig[] 19 | onConfigChange: (configName: string, value: V) => void 20 | currentPluginConfig: Record 21 | } 22 | 23 | export const PicGoPluginConfigForm: React.FC = ({ 24 | pluginConfigList, 25 | currentPluginConfig, 26 | onConfigChange, 27 | pluginName, 28 | pluginId 29 | }) => { 30 | return ( 31 | 40 | 44 | {/* Handle different type of configurations: 45 | - input: use input component 46 | - list: use select component 47 | - ... 48 | */} 49 | 50 | Settings of {pluginName} 51 | 52 | {pluginConfigList?.map((config) => { 53 | switch (config.type) { 54 | case 'input': 55 | case 'password': 56 | return ( 57 | { 62 | onConfigChange(config.name, e.target.value) 63 | }} 64 | placeholder={config.message ?? config.name} 65 | required={config.required} 66 | sx={{ 67 | my: 1 68 | }} 69 | type={config.type} 70 | value={currentPluginConfig[config.name] ?? ''} 71 | /> 72 | ) 73 | case 'confirm': 74 | return ( 75 | { 81 | onConfigChange(config.name, e.target.value) 82 | }} 83 | required={config.required} 84 | /> 85 | } 86 | label={config.name} 87 | /> 88 | ) 89 | case 'list': 90 | case 'checkbox': 91 | return ( 92 | 114 | ) 115 | default: 116 | return ( 117 | 123 | Unsupported config: 124 | 125 | {JSON.stringify(config, null, 2)} 126 | 127 | 128 | ) 129 | } 130 | })} 131 | 132 | 133 | ) 134 | } 135 | -------------------------------------------------------------------------------- /src/webview/pages/PicGoControlPanel/PicGoUpload/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useDropzone } from 'react-dropzone' 3 | import { 4 | Paper, 5 | Typography, 6 | Grid, 7 | MenuList, 8 | MenuItem, 9 | ListItemText, 10 | Divider, 11 | Box 12 | } from '@mui/material' 13 | import { 14 | showMessage, 15 | uploadFiles, 16 | executeCommand 17 | } from '../../../utils/channel' 18 | import * as MuiIconsMaterial from '@mui/icons-material' 19 | import { contributes, getNLSText } from '../../../../utils/meta' 20 | 21 | export const PicGoUpload = () => { 22 | const { 23 | getRootProps, 24 | getInputProps, 25 | isDragActive, 26 | isDragAccept, 27 | isDragReject 28 | } = useDropzone({ 29 | async onDropAccepted(files) { 30 | try { 31 | const outputs = await uploadFiles(files.map((file) => file.path)) 32 | showMessage({ 33 | type: 'info', 34 | message: `successfully uploaded ${outputs.length} files` 35 | }) 36 | } catch (err) { 37 | showMessage({ 38 | type: 'error', 39 | message: `upload failed: ${String(err)}` 40 | }) 41 | } 42 | } 43 | }) 44 | const rootProps = getRootProps() 45 | 46 | const activeStyle = { 47 | borderColor: 'primary.main', 48 | backgroundColor: 'action.hover' 49 | } 50 | 51 | const acceptStyle = { 52 | borderColor: 'info.main' 53 | } 54 | 55 | const rejectStyle = { 56 | borderColor: 'error.main' 57 | } 58 | 59 | const uploadCommands = contributes.commands.filter((cmd) => 60 | cmd.command.startsWith('picgo.uploadImage') 61 | ) 62 | const height = 300 63 | 64 | return ( 65 | 66 | 67 | 89 | 90 | 95 | 96 | 97 | Drag and drop some files here to upload, 98 | {' '} 99 | 100 | or click to select files 101 | 102 | 103 | 104 | 105 | 106 | 107 | 114 | 115 | Quick Upload 116 | 117 | 125 | 129 | 130 | {uploadCommands.map((cmd) => ( 131 | 132 | executeCommand(cmd.command)}> 133 | 134 | {getNLSText(cmd.title.slice(1, -1) as any)} 135 | 136 | {contributes.keybindings 137 | .find((k) => k.command === cmd.command) 138 | ?.key.replace('ctrl', '⌘') 139 | .replace('alt', '⌥') 140 | .split('+') 141 | .map((key) => ( 142 | 152 | {key} 153 | 154 | ))} 155 | 156 | 157 | 158 | ))} 159 | 160 | 161 | 162 | 163 | 164 | ) 165 | } 166 | -------------------------------------------------------------------------------- /test/utils/constants-and-interfaces.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-template-curly-in-string */ 2 | import * as path from 'path' 3 | import { Selection } from 'vscode' 4 | 5 | export interface IVSPicgoConfiguration { 6 | 'picgo.configPath': string | undefined 7 | 'picgo.dataPath': string | undefined 8 | 'picgo.customUploadName': string | undefined 9 | 'picgo.customOutputFormat': string | undefined 10 | 'picgo.picBed.current': string | undefined 11 | 12 | // aliyun picBed 13 | 'picgo.picBed.aliyun.accessKeyId': string | undefined 14 | 'picgo.picBed.aliyun.accessKeySecret': string | undefined 15 | 'picgo.picBed.aliyun.bucket': string | undefined 16 | 'picgo.picBed.aliyun.area': string | undefined 17 | 'picgo.picBed.aliyun.path': string | undefined 18 | 'picgo.picBed.aliyun.customUrl': string | undefined 19 | 20 | // github picBed 21 | 'picgo.picBed.github.repo': string | undefined 22 | 'picgo.picBed.github.token': string | undefined 23 | 'picgo.picBed.github.path': string | undefined 24 | 'picgo.picBed.github.customUrl': string | undefined 25 | 'picgo.picBed.github.branch': string | undefined 26 | 27 | // imgur picBed 28 | 'picgo.picBed.imgur.clientId': string | undefined 29 | 'picgo.picBed.imgur.proxy': string | undefined 30 | 31 | // qiniu picBed 32 | 'picgo.picBed.qiniu.accessKey': string | undefined 33 | 'picgo.picBed.qiniu.secretKey': string | undefined 34 | 'picgo.picBed.qiniu.bucket': string | undefined 35 | 'picgo.picBed.qiniu.url': string | undefined 36 | 'picgo.picBed.qiniu.area': string | undefined 37 | 'picgo.picBed.qiniu.options': string | undefined 38 | 'picgo.picBed.qiniu.path': string | undefined 39 | 40 | // sm.ms picBed 41 | 'picgo.picBed.smms.token': string | undefined 42 | 43 | // tcyun picBed 44 | 'picgo.picBed.tcyun.version': string | undefined 45 | 'picgo.picBed.tcyun.secretId': string | undefined 46 | 'picgo.picBed.tcyun.secretKey': string | undefined 47 | 'picgo.picBed.tcyun.bucket': string | undefined 48 | 'picgo.picBed.tcyun.appId': string | undefined 49 | 'picgo.picBed.tcyun.area': string | undefined 50 | 'picgo.picBed.tcyun.path': string | undefined 51 | 'picgo.picBed.tcyun.customUrl': string | undefined 52 | 53 | // upyun picBed 54 | 'picgo.picBed.upyun.bucket': string | undefined 55 | 'picgo.picBed.upyun.operator': string | undefined 56 | 'picgo.picBed.upyun.password': string | undefined 57 | 'picgo.picBed.upyun.options': string | undefined 58 | 'picgo.picBed.upyun.path': string | undefined 59 | 'picgo.picBed.upyun.url': string | undefined 60 | 61 | // weibo picBed 62 | 'picgo.picBed.weibo.chooseCookie': boolean | undefined 63 | 'picgo.picBed.weibo.username': string | undefined 64 | 'picgo.picBed.weibo.quality': string | undefined 65 | 'picgo.picBed.weibo.cookie': string | undefined 66 | } 67 | 68 | export type IVSPicgoConfigurationKeys = keyof IVSPicgoConfiguration 69 | 70 | export interface IVSPicgoUploadStarterOptions { 71 | args4uploader: string[] // arguments sent to func, 72 | editor: { 73 | content: string 74 | selection: Selection 75 | } 76 | } 77 | 78 | export const TEST_MD_FILE_PATH = path.join(__dirname, '../../assets/test.md') 79 | export const TEST_PICTURE_PATH = path.join(__dirname, '../../assets/test.png') 80 | 81 | export const DEFAULT_CONFIGS: IVSPicgoConfiguration = { 82 | 'picgo.configPath': '', 83 | 'picgo.dataPath': '', 84 | 'picgo.customUploadName': '${fileName}${extName}', 85 | 'picgo.customOutputFormat': '![${uploadedName}](${url})', 86 | 'picgo.picBed.current': 'smms', 87 | // 'picgo.picBed.current': 'github', 88 | 89 | // aliyun picBed 90 | 'picgo.picBed.aliyun.accessKeyId': '', 91 | 'picgo.picBed.aliyun.accessKeySecret': '', 92 | 'picgo.picBed.aliyun.bucket': '', 93 | 'picgo.picBed.aliyun.area': '', 94 | 'picgo.picBed.aliyun.path': '', 95 | 'picgo.picBed.aliyun.customUrl': '', 96 | 97 | // github picBed 98 | 'picgo.picBed.github.repo': '', 99 | 'picgo.picBed.github.token': '', 100 | 'picgo.picBed.github.path': '', 101 | 'picgo.picBed.github.customUrl': '', 102 | 'picgo.picBed.github.branch': '', 103 | 104 | // imgur picBed 105 | 'picgo.picBed.imgur.clientId': '', 106 | 'picgo.picBed.imgur.proxy': '', 107 | 108 | // qiniu picBed 109 | 'picgo.picBed.qiniu.accessKey': '', 110 | 'picgo.picBed.qiniu.secretKey': '', 111 | 'picgo.picBed.qiniu.bucket': '', 112 | 'picgo.picBed.qiniu.url': '', 113 | 'picgo.picBed.qiniu.area': 'z0', 114 | 'picgo.picBed.qiniu.options': '', 115 | 'picgo.picBed.qiniu.path': '', 116 | 117 | // sm.ms picBed 118 | 'picgo.picBed.smms.token': 'JxUI4p3alQ8QviKAd4wmQByitBufRqJS', // only for test 119 | 120 | // tcyun picBed 121 | 'picgo.picBed.tcyun.version': 'v5', 122 | 'picgo.picBed.tcyun.secretId': '', 123 | 'picgo.picBed.tcyun.secretKey': '', 124 | 'picgo.picBed.tcyun.bucket': '', 125 | 'picgo.picBed.tcyun.appId': '', 126 | 'picgo.picBed.tcyun.area': '', 127 | 'picgo.picBed.tcyun.path': '', 128 | 'picgo.picBed.tcyun.customUrl': '', 129 | 130 | // upyun picBed 131 | 'picgo.picBed.upyun.bucket': '', 132 | 'picgo.picBed.upyun.operator': '', 133 | 'picgo.picBed.upyun.password': '', 134 | 'picgo.picBed.upyun.options': '', 135 | 'picgo.picBed.upyun.path': '', 136 | 'picgo.picBed.upyun.url': '', 137 | 138 | // weibo picBed 139 | 'picgo.picBed.weibo.chooseCookie': true, 140 | 'picgo.picBed.weibo.username': '', 141 | 'picgo.picBed.weibo.quality': 'large', 142 | 'picgo.picBed.weibo.cookie': '' 143 | } 144 | -------------------------------------------------------------------------------- /src/vscode/PicgoAPI.ts: -------------------------------------------------------------------------------- 1 | import { IConfig, PicGo, IHelper, LifecyclePlugins, IPluginConfig } from 'picgo' 2 | import { DataStore } from './DataStore' 3 | import vscode from 'vscode' 4 | import { decorateMessage, showError, showInfo } from './utils' 5 | import { defaultSettings } from './settings' 6 | import _ from 'lodash-es' 7 | import { Get } from 'type-fest' 8 | export type GetConfig = Get 9 | 10 | export interface IUploaderConfig { 11 | uploaderName: string 12 | uploaderID: string 13 | configList?: IPluginConfig[] 14 | } 15 | 16 | export class PicgoAPI { 17 | static picgoAPI = new PicgoAPI() 18 | 19 | private readonly picgo: PicGo 20 | helper: IHelper 21 | constructor() { 22 | this.picgo = new PicGo(DataStore.dataStore.configPath) 23 | this.picgo.saveConfig({ 24 | debug: true 25 | }) 26 | this.initConfig() 27 | this.helper = this.picgo.helper 28 | } 29 | 30 | initConfig() { 31 | this.setConfigIfNotExist( 32 | 'settings.vsPicgo.customOutputFormat', 33 | defaultSettings.settings.vsPicgo.customOutputFormat 34 | ) 35 | this.setConfigIfNotExist( 36 | 'settings.vsPicgo.customUploadName', 37 | defaultSettings.settings.vsPicgo.customUploadName 38 | ) 39 | } 40 | 41 | setConfigIfNotExist(configName: T, value: GetConfig) { 42 | const config = this.picgo.getConfig>(configName) 43 | if (!config) { 44 | this.picgo.saveConfig({ 45 | [configName]: value 46 | }) 47 | } 48 | return config ?? value 49 | } 50 | 51 | getConfig(configName: T): GetConfig { 52 | return this.setConfigIfNotExist( 53 | configName, 54 | _.get(defaultSettings, configName) 55 | ) 56 | } 57 | 58 | setConfig(configName: T, value: GetConfig) { 59 | this.picgo.saveConfig({ 60 | [configName]: value 61 | }) 62 | } 63 | 64 | getAllUploaders() { 65 | return this.picgo.helper.uploader.getIdList() 66 | } 67 | 68 | handleConfigWithFunction = (configList?: IPluginConfig[]) => { 69 | if (!configList) return 70 | for (const config of configList) { 71 | if (typeof config.default === 'function') { 72 | config.default = config.default() 73 | } 74 | if (typeof config.choices === 'function') { 75 | config.choices = config.choices() 76 | } 77 | } 78 | return configList 79 | } 80 | 81 | getAllUploaderConfigs(): IUploaderConfig[] { 82 | return this.getAllUploaders().map((uploaderID) => { 83 | const uploader = this.picgo.helper.uploader.get(uploaderID) 84 | const uploaderName = uploader?.name ?? uploaderID 85 | const configList = this.handleConfigWithFunction( 86 | uploader?.config?.(this.picgo) 87 | ) 88 | return { 89 | uploaderID, 90 | uploaderName, 91 | configList 92 | } 93 | }) 94 | } 95 | 96 | /** 97 | * @param input This image file paths to be uploaded, will upload from clipboard if no input specified 98 | */ 99 | async upload(input?: string[]) { 100 | // uploading progress, must be parallel with `picgo.upload` to catch events 101 | vscode.window.withProgress( 102 | { 103 | location: vscode.ProgressLocation.Notification, 104 | title: decorateMessage('image uploading...'), 105 | cancellable: false 106 | }, 107 | async (progress) => { 108 | return await new Promise((resolve, reject) => { 109 | const onUploadProgress = (p: number) => { 110 | progress.report({ increment: p }) 111 | if (p === 100) { 112 | cancelListeners.call(this) 113 | resolve() 114 | } 115 | } 116 | const onFailed = (error: Error) => { 117 | const errorReason = error.message || 'Unknown error' 118 | cancelListeners.call(this) 119 | showError(errorReason) 120 | resolve() 121 | } 122 | const onNotification = (notice: INotice) => { 123 | const errorReason = `${notice.title}! ${notice.body || ''}${ 124 | notice.text || '' 125 | }` 126 | cancelListeners.call(this) 127 | showError(errorReason) 128 | resolve() 129 | } 130 | this.picgo.on('uploadProgress', onUploadProgress) 131 | this.picgo.on('failed', onFailed) 132 | this.picgo.on('notification', onNotification) 133 | function cancelListeners(this: PicgoAPI) { 134 | this.picgo.off('uploadProgress', onUploadProgress) 135 | this.picgo.off('failed', onFailed) 136 | this.picgo.off('notification', onNotification) 137 | } 138 | }) 139 | } 140 | ) 141 | 142 | // Error has been handled in on 'failed', so we just catch error to avoid unhandled rejected promise 143 | // Note that all unhandled promise in extension will be caught by vscode and show a warning like "command ran failed", which is not what we want 144 | return await this.picgo 145 | .upload(input) 146 | .catch(() => {}) 147 | .then((res) => { 148 | if (res instanceof Error) return void 0 149 | else if (res) { 150 | DataStore.dataStore.db.insertMany(res) 151 | showInfo(`${res.length} image uploaded successfully.`) 152 | return res 153 | } 154 | }) 155 | } 156 | 157 | setCurrentPluginName(name: string) { 158 | LifecyclePlugins.currentPlugin = name 159 | } 160 | } 161 | 162 | export interface INotice { 163 | body: string 164 | text: string 165 | title: string 166 | } 167 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## :tada: 2.1.6 (2021-08-21) 2 | 3 | 4 | ### :bug: Bug Fixes 5 | 6 | * **ci:** fix changelog link ([fcd7c0d](https://github.com/PicGo/vs-picgo/commit/fcd7c0d)) 7 | 8 | 9 | 10 | ## :tada: 2.1.6-alpha.0 (2021-08-21) 11 | 12 | 13 | ### :bug: Bug Fixes 14 | 15 | * **ci:** vsce-action only supported on linux ([62a2d34](https://github.com/PicGo/vs-picgo/commit/62a2d34)) 16 | 17 | 18 | 19 | ## :tada: 2.1.5 (2021-08-21) 20 | 21 | 22 | ### :bug: Bug Fixes 23 | 24 | * **ci:** change runs-on to macos-latest to avoid vscode install error ([f2e2715](https://github.com/PicGo/vs-picgo/commit/f2e2715)) 25 | 26 | 27 | 28 | ## :tada: 2.1.4 (2021-08-21) 29 | 30 | 31 | ### :sparkles: Features 32 | 33 | * **error:** throw error when no url found in output ([#101](https://github.com/PicGo/vs-picgo/issues/101)) ([e141afe](https://github.com/PicGo/vs-picgo/commit/e141afe)) 34 | 35 | 36 | ### :bug: Bug Fixes 37 | 38 | * **changelog:** fix changelog ([5560298](https://github.com/PicGo/vs-picgo/commit/5560298)) 39 | * **ci:** fix release-tag.yml ([bd72133](https://github.com/PicGo/vs-picgo/commit/bd72133)) 40 | 41 | 42 | ### :package: Chore 43 | 44 | * **ci:** auto release to marketplace & update picgo core to 1.4.25 ([0940b69](https://github.com/PicGo/vs-picgo/commit/0940b69)) 45 | 46 | 47 | ### :pencil: Documentation 48 | 49 | * **changelog:** fix changelog history ([90665eb](https://github.com/PicGo/vs-picgo/commit/90665eb)) 50 | 51 | 52 | 53 | ## :tada: 2.1.3 (2021-08-13) 54 | 55 | 56 | ### :sparkles: Features 57 | 58 | * **:arrow_up:** Upgrade(picgo-core): update picgo core to 1.4.24 (#97) 59 | 60 | ## :tada: 2.1.2 (2021-07-25) 61 | 62 | 63 | ### :bug: Bug Fixes 64 | 65 | * **dep:** bundle picgo-core to dist ([5c523ed](https://github.com/PicGo/vs-picgo/commit/5c523ed)) 66 | 67 | 68 | 69 | ## :tada: 2.1.1 (2021-07-25) 70 | 71 | ### :bug: Bug Fixes 72 | 73 | * add tencent cos path option ([606b9d4](https://github.com/PicGo/vs-picgo/commit/606b9d4)) 74 | * azure pipeline + coveralls ([c8129ed](https://github.com/PicGo/vs-picgo/commit/c8129ed)) 75 | * **ci:** github actions use yarn to lock versions ([#77](https://github.com/PicGo/vs-picgo/issues/77)) ([3347690](https://github.com/PicGo/vs-picgo/commit/3347690)) 76 | * **clipboard:** set `PICGO_ENV` to `CLI`, fixes [#75](https://github.com/PicGo/vs-picgo/issues/75) ([#78](https://github.com/PicGo/vs-picgo/issues/78)) ([9aff38e](https://github.com/PicGo/vs-picgo/commit/9aff38e)) 77 | * **proxy:** add proxy config, fixes [#79](https://github.com/PicGo/vs-picgo/issues/79) ([5cd019d](https://github.com/PicGo/vs-picgo/commit/5cd019d)) 78 | 79 | 80 | ### :package: Chore 81 | 82 | * **.vscode:** update .vscode ([65f8157](https://github.com/PicGo/vs-picgo/commit/65f8157)) 83 | * add bump version ([#32](https://github.com/PicGo/vs-picgo/issues/32)) ([4ec0218](https://github.com/PicGo/vs-picgo/commit/4ec0218)) 84 | * add coverage collector ([e8b30b7](https://github.com/PicGo/vs-picgo/commit/e8b30b7)) 85 | * azure -> github actions ([5ae4ec2](https://github.com/PicGo/vs-picgo/commit/5ae4ec2)) 86 | * migrate to standardjs ([#83](https://github.com/PicGo/vs-picgo/issues/83)) ([75c8c97](https://github.com/PicGo/vs-picgo/commit/75c8c97)) 87 | * replace tslint with eslint ([ce3fe27](https://github.com/PicGo/vs-picgo/commit/ce3fe27)) 88 | 89 | ## 2.1.0 90 | 91 | * upgrade dependencies, support sm.ms V2 92 | 93 | ## 2.0.4 94 | 95 | * fix bugs when upload images from clipboard in Windows 7 ([#34](https://github.com/PicGo/vs-picgo/issues/34)) 96 | * currect azure project name ([#33](https://github.com/PicGo/vs-picgo/issues/33)) ([7082558](https://github.com/PicGo/vs-picgo/commit/7082558)) 97 | * add bump version ([#32](https://github.com/PicGo/vs-picgo/issues/32)) ([4ec0218](https://github.com/PicGo/vs-picgo/commit/4ec0218)) 98 | * Setup continuous integration appveyor & azure ([#31](https://github.com/PicGo/vs-picgo/issues/31)) ([1b6de45](https://github.com/PicGo/vs-picgo/commit/1b6de45)) 99 | 100 | ## 2.0.3 101 | 102 | * Update README: optimize the images 103 | * Delete `username` property in picgo.picBed.github 104 | 105 | ## 2.0.2 106 | 107 | * Fix README format at vscode market. 108 | * Change the description, add some keywords. 109 | 110 | ## 2.0.1 111 | 112 | * Update README.md. 113 | 114 | ## 2.0.0 115 | 116 | * Upgrade PicGo-Core to 1.3.4 117 | * Add the data file which contains the info of images uploaded by `vs-picgo`, which can be used by [picgo-plugin-vscode-migrator](https://github.com/upupming/picgo-plugin-vscode-migrator) 118 | * Updated: better settings description and let user configure in settings directly. 119 | * Change `external configuration file property` in PicGo config object. 120 | * Added: custom upload name & output format [#21](https://github.com/PicGo/vs-picgo/pull/21). 121 | 122 | ## 1.0.6 123 | 124 | * Upgrade PicGo-Core to fix errors caused by comments in a json file. 125 | * Update README. 126 | 127 | ## 1.0.5 128 | 129 | * Fix spelling errors in README.md 130 | * Update the regular expression of the image name. 131 | 132 | ## 1.0.4 133 | 134 | * Add English docs. 135 | * Change the image name filters. 136 | 137 | ## 1.0.3 138 | 139 | * Update dependencies: PicGo-Core 1.10 -> PicGo-Core 1.16 etc. 140 | 141 | ## 1.0.2 142 | 143 | * Update README. 144 | 145 | ## 1.0.1 146 | 147 | * Fix a bug caused by settings.json with comments. 148 | 149 | ## 1.0.0 150 | 151 | * Change repository's url and issue's url. 152 | * Upgrade PicGo-Core dependency: from ^1.1.5 to ^1.1.9. 153 | * Add notice in README.md. 154 | 155 | ## 0.0.3 156 | 157 | * Display a progress bar when uploading the image(s). 158 | 159 | ## 0.0.2 160 | 161 | * Support uploading images from Explorer/InputBox. 162 | 163 | ## 0.0.1 164 | 165 | * Support uploading images from Clipboard. 166 | -------------------------------------------------------------------------------- /src/webview/components/PicGoPanelWrapper.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { styled } from '@mui/material/styles' 3 | import { 4 | Drawer as MuiDrawer, 5 | AppBar as MuiAppBar, 6 | AppBarProps as MuiAppBarProps, 7 | Box, 8 | Toolbar, 9 | Typography, 10 | IconButton, 11 | Container, 12 | Paper, 13 | Divider 14 | } from '@mui/material' 15 | import * as MuiIconsMaterial from '@mui/icons-material' 16 | import logo from '../../images/squareLogo.png' 17 | import { Copyright } from './Copyright' 18 | 19 | interface IAppBarProps extends MuiAppBarProps { 20 | open?: boolean 21 | } 22 | 23 | const AppBar = styled(MuiAppBar, { 24 | shouldForwardProp: (prop) => prop !== 'open' 25 | })(({ theme, open }) => ({ 26 | zIndex: theme.zIndex.drawer + 1, 27 | transition: theme.transitions.create(['width', 'margin'], { 28 | easing: theme.transitions.easing.sharp, 29 | duration: theme.transitions.duration.leavingScreen 30 | }), 31 | ...(open && { 32 | transition: theme.transitions.create(['width', 'margin'], { 33 | easing: theme.transitions.easing.sharp, 34 | duration: theme.transitions.duration.enteringScreen 35 | }) 36 | }) 37 | })) 38 | 39 | const drawerWidth: number = 240 40 | const drawerScrollbarWidth = 8 41 | 42 | const Drawer = styled(MuiDrawer, { 43 | shouldForwardProp: (prop) => prop !== 'open' 44 | })(({ theme, open }) => ({ 45 | '& .MuiDrawer-paper': { 46 | position: 'relative', 47 | whiteSpace: 'nowrap', 48 | width: drawerWidth, 49 | // the scrollbar occupies no width but overlaying in the right padding of the drawer. To make the drawer looks nicer, we should apply the same left padding as the right padding, ref: the MUI docs page's left sidebar 50 | overflow: 'overlay', 51 | transition: theme.transitions.create('width', { 52 | easing: theme.transitions.easing.sharp, 53 | duration: theme.transitions.duration.enteringScreen 54 | }), 55 | boxSizing: 'border-box', 56 | ...(!open && { 57 | overflowX: 'hidden', 58 | transition: theme.transitions.create('width', { 59 | easing: theme.transitions.easing.sharp, 60 | duration: theme.transitions.duration.leavingScreen 61 | }), 62 | width: theme.spacing(7), 63 | [theme.breakpoints.up('sm')]: { 64 | width: theme.spacing(9) 65 | } 66 | }) 67 | }, 68 | '& .MuiDrawer-paper::-webkit-scrollbar': { 69 | width: drawerScrollbarWidth 70 | } 71 | })) 72 | 73 | export interface IPicGoControlPanelWrapperProps { 74 | drawerList: React.ReactNode 75 | } 76 | 77 | export const PicGoControlPanelWrapper: React.FC = ({ 78 | children, 79 | drawerList 80 | }) => { 81 | const [open, setOpen] = React.useState(true) 82 | const toggleDrawer = () => { 83 | setOpen(!open) 84 | } 85 | 86 | return ( 87 | 88 | 89 | 97 | 105 | 113 | vs-picgo 114 | 115 | 120 | 121 | 122 | 123 | 124 | 128 | {drawerList} 129 | 130 | 131 | 135 | theme.palette.mode === 'light' 136 | ? theme.palette.grey[100] 137 | : theme.palette.grey[900], 138 | flexGrow: 1, 139 | height: '100vh', 140 | overflow: 'auto', 141 | display: 'flex', 142 | flexDirection: 'column' 143 | }}> 144 | 145 | 149 | 158 | 159 | 160 | 166 | Control Panel 167 | 168 | 172 | 173 | 174 | 175 | 176 | 183 | {children} 184 | 185 | 191 | 196 | 197 | 198 | 199 | ) 200 | } 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vs-picgo 2 | 3 | > The VSCode extension of [PicGo](https://github.com/PicGo). 4 | 5 | [![version](https://img.shields.io/vscode-marketplace/v/Spades.vs-picgo.svg?style=flat-square&label=vscode%20marketplace)](https://marketplace.visualstudio.com/items?itemName=Spades.vs-picgo) 6 | ![Visual Studio Marketplace Rating](https://img.shields.io/visual-studio-marketplace/r/Spades.vs-picgo?style=flat-square) 7 | [![installs](https://img.shields.io/vscode-marketplace/d/Spades.vs-picgo.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=Spades.vs-picgo) 8 | [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2FPicGo%2Fvs-picgo%2Fbadge%3Fref%3Ddev&style=flat-square)](https://actions-badge.atrox.dev/PicGo/vs-picgo/goto?ref=dev) 9 | [![Coveralls github branch](https://img.shields.io/coveralls/github/PicGo/vs-picgo/refs/heads/dev.svg?style=flat-square)](https://coveralls.io/github/PicGo/vs-picgo?branch=refs/heads/dev) 10 | [![GitHub stars](https://img.shields.io/github/stars/PicGo/vs-picgo.svg?style=flat-square&label=github%20stars)](https://github.com/PicGo/vs-picgo) 11 | [![PicGo Convention](https://img.shields.io/badge/picgo-convention-blue.svg?style=flat-square)](https://github.com/PicGo/bump-version) 12 | [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](https://standardjs.com) 13 | 14 | ## Overview 15 | 16 | `vs-picgo` is a VSCode extension for uploading images to a remote image hosting service and insert the url into the current editing file. It's much more efficient than other tools. And it can give us the better experience of uploading images. `vs-picgo` supports 8 kinds of image hosting services: [weibo](https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E5%BE%AE%E5%8D%9A%E5%9B%BE%E5%BA%8A), [qiniu](https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E4%B8%83%E7%89%9B%E5%9B%BE%E5%BA%8A), [tcyun](https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E8%85%BE%E8%AE%AF%E4%BA%91cos), [upyun](https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E5%8F%88%E6%8B%8D%E4%BA%91), [github](https://picgo.github.io/PicGo-Doc/zh/guide/config.html#github%E5%9B%BE%E5%BA%8A), [aliyun](https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E9%98%BF%E9%87%8C%E4%BA%91oss), [imgur](https://picgo.github.io/PicGo-Doc/zh/guide/config.html#imgur%E5%9B%BE%E5%BA%8A) and [SM.MS](https://sm.ms/), which are supported by [PicGo-Core](https://github.com/PicGo/PicGo-Core). And the plugin feature of PicGo-Core is working in progress. 17 | 18 | ## Features 19 | 20 |
21 | Uploading an image from clipboard 22 | clipboard.gif 23 |
24 | 25 |
26 | Uploading images from explorer 27 | explorer.gif 28 |
29 | 30 |
31 | Uploading images from input box 32 | input box.gif 33 |
34 | 35 |
36 | Use selection text as the uploaded fileName 37 | selection.gif 38 | Notice: These characters: \$, :, /, ? and newline will be ignored in the image name. (Because they are invalid for file names.) 39 |
40 | 41 | ## Keyboard shortcuts 42 | 43 | **You can change all the shortcuts below as you wish.** 44 | 45 | | OS | Uploading an image from clipboard | Uploading images from explorer | Uploading an image from input box | 46 | | ------------ | ----------------------------------------------- | ----------------------------------------------- | ----------------------------------------------- | 47 | | Windows/Unix | Ctrl + Alt + U | Ctrl + Alt + E | Ctrl + Alt + O | 48 | | OsX | Cmd + Opt + U | Cmd + Opt + E | Cmd + Opt + O | 49 | 50 | ## Settings 51 | 52 | - Default 53 | - The default image hosting is [SM.MS](https://sm.ms/). 54 | 55 | - Custom 56 | 57 |
58 | BIG NEWS: from 2.0.0, We can customize the settings in VSCode settings 59 | vscode-setting.png 60 |
61 | 62 | - Use an external configuration file 63 | 64 |
65 | Enter the path of the configuration file 66 | external-config.png 67 |
68 | 69 | - Use VSCode settings 70 | 71 |
72 | First, choose the current PicBed 73 | current-picbed.png 74 |
75 | 76 |
77 | Then, input all the info the current PicBed needs 78 | picbed-info.png 79 |
80 | 81 |
82 | Customize the name of the image to be uploaded 83 | Notice: If you selected some text before uploading, the selection will become the fileName of the image to be uploaded. 84 | image-name.png 85 |
86 | 87 |
88 | Customize the output format of the uploaded image 89 | output-format.png 90 |
91 | 92 |
93 | 94 | Suggested settings for PicGo-electron users (See PicGo configuration path for more information): 95 | 96 |
97 | 98 | **Notice: `YOUR_HOME_DIR` should be replaced by the path of your current user path.** 99 | 100 | ```json 101 | // Windows 102 | { 103 | "picgo.configPath":"YOUR_HOME_DIR\\AppData\\Roaming\\PicGo\\data.json", 104 | "picgo.dataPath": "YOUR_HOME_DIR\\AppData\\Roaming\\PicGo\\data.json" 105 | } 106 | 107 | // macOS 108 | { 109 | "picgo.configPath": "YOUR_HOME_DIR/Library/Application Support/picgo/data.json", 110 | "picgo.dataPath": "YOUR_HOME_DIR/Library/Application Support/picgo/data.json" 111 | } 112 | 113 | // Linux 114 | { 115 | "picgo.configPath": "YOUR_HOME_DIR/.config/picgo/data.json", 116 | "picgo.dataPath": "YOUR_HOME_DIR/.config/picgo/data.json" 117 | } 118 | ``` 119 | 120 |
121 | picgo.configPath and picgo.dataInfoPath can be set in vscode settings 122 | for-picgo-user.png 123 |
124 | 125 | In this way: 126 | 127 | 1. `vs-picgo` will use the same configuration as `PicGo-electron`. 128 | 2. `PicGo-electron` will display all the uploaded images by `vs-picgo` in its gallery. 129 | 130 | 131 | 132 | ## Migration 133 | 134 | - From ^1.0.0 to ^2.x 135 | - External configuration file property has changed, from `picgo.path` to `picgo.configPath`. 136 | 137 | ## Versioning 138 | 139 | For the versions available, see the [tags on PicGo/vs-picgo](https://github.com/PicGo/vs-picgo/tags). ChangeLogs can be found at [CHANGELOG.md](CHANGELOG.md). All the dev builds can be found on [GitHub Actions](https://github.com/PicGo/vs-picgo/actions/), and you can just open the build of a specific commit, and go to the Summary tab to download the artifacts. 140 | 141 | ## Contributing 142 | 143 | Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. 144 | 145 | ## Contributors 146 | 147 | - [Spades-S](https://github.com/Spades-S) 148 | - [Molunerfinn](https://github.com/Molunerfinn) 149 | - [upupming](https://github.com/upupming) 150 | 151 | ## Thanks 152 | 153 | - [PicGo-Core](https://github.com/PicGo/PicGo-Core) 154 | 155 | **Enjoy!** 156 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vs-picgo", 3 | "displayName": "%ext.displayName%", 4 | "description": "%ext.description%", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/PicGo/vs-picgo.git" 8 | }, 9 | "issues": "https://github.com/PicGo/vs-picgo/issues", 10 | "keywords": [ 11 | "image", 12 | "picture", 13 | "upload", 14 | "image upload", 15 | "picture upload" 16 | ], 17 | "version": "2.1.6", 18 | "publisher": "Spades", 19 | "engines": { 20 | "vscode": "^1.60.0" 21 | }, 22 | "categories": [ 23 | "Other" 24 | ], 25 | "icon": "logo.png", 26 | "activationEvents": [ 27 | "onCommand:picgo.uploadImageFromClipboard", 28 | "onCommand:picgo.uploadImageFromExplorer", 29 | "onCommand:picgo.uploadImageFromInputBox", 30 | "onCommand:picgo.webviewDemo", 31 | "onCommand:picgo.webviewPicGoControlPanel", 32 | "workspaceContains:vs-picgo-auto-launch.txt" 33 | ], 34 | "main": "./dist/extension", 35 | "contributes": { 36 | "commands": [ 37 | { 38 | "command": "picgo.uploadImageFromClipboard", 39 | "title": "%command.upload.clipboard.title%", 40 | "category": "PicGo" 41 | }, 42 | { 43 | "command": "picgo.uploadImageFromExplorer", 44 | "title": "%command.upload.explorer.title%", 45 | "category": "PicGo" 46 | }, 47 | { 48 | "command": "picgo.uploadImageFromInputBox", 49 | "title": "%command.upload.inputBox.title%", 50 | "category": "PicGo" 51 | }, 52 | { 53 | "command": "picgo.webviewDemo", 54 | "title": "%command.webview.demo.title%", 55 | "category": "PicGo" 56 | }, 57 | { 58 | "command": "picgo.webviewPicGoControlPanel", 59 | "title": "%command.webview.picGoControlPanel.title%", 60 | "category": "PicGo" 61 | } 62 | ], 63 | "keybindings": [ 64 | { 65 | "command": "picgo.uploadImageFromClipboard", 66 | "key": "ctrl+alt+U", 67 | "mac": "cmd+alt+U" 68 | }, 69 | { 70 | "command": "picgo.uploadImageFromExplorer", 71 | "key": "ctrl+alt+E", 72 | "mac": "cmd+alt+E" 73 | }, 74 | { 75 | "command": "picgo.uploadImageFromInputBox", 76 | "key": "ctrl+alt+O", 77 | "mac": "cmd+alt+O" 78 | } 79 | ], 80 | "configuration": { 81 | "type": "object", 82 | "title": "%config.title%", 83 | "properties": { 84 | "picgo.configPath": { 85 | "type": "string", 86 | "markdownDescription": "%config.configPath.description%", 87 | "default": "" 88 | }, 89 | "picgo.dataPath": { 90 | "type": "string", 91 | "markdownDescription": "%config.dataPath.description%", 92 | "default": "" 93 | }, 94 | "picgo.customUploadName": { 95 | "type": "string", 96 | "markdownDescription": "%config.customUploadName.description%", 97 | "default": "${fileName}${extName}" 98 | }, 99 | "picgo.customOutputFormat": { 100 | "type": "string", 101 | "markdownDescription": "%config.customOutputFormat.description%", 102 | "default": "![${uploadedName}](${url})" 103 | }, 104 | "picgo.picBed.uploader": { 105 | "type": "string", 106 | "enum": [ 107 | "smms", 108 | "aliyun", 109 | "github", 110 | "imgur", 111 | "qiniu", 112 | "tcyun", 113 | "upyun", 114 | "weibo" 115 | ], 116 | "default": "", 117 | "markdownDescription": "%config.picBed.uploader%" 118 | }, 119 | "picgo.picBed.current": { 120 | "type": "string", 121 | "enum": [ 122 | "smms", 123 | "aliyun", 124 | "github", 125 | "imgur", 126 | "qiniu", 127 | "tcyun", 128 | "upyun", 129 | "weibo" 130 | ], 131 | "default": "smms", 132 | "markdownDescription": "%config.picBed.current%" 133 | }, 134 | "picgo.picBed.proxy": { 135 | "type": "string", 136 | "default": "", 137 | "markdownDescription": "%config.picBed.proxy%" 138 | }, 139 | "picgo.picBed.smms.token": { 140 | "type": "string", 141 | "default": "" 142 | }, 143 | "picgo.picBed.aliyun.accessKeyId": { 144 | "type": "string", 145 | "default": "" 146 | }, 147 | "picgo.picBed.aliyun.accessKeySecret": { 148 | "type": "string", 149 | "default": "" 150 | }, 151 | "picgo.picBed.aliyun.bucket": { 152 | "type": "string", 153 | "default": "" 154 | }, 155 | "picgo.picBed.aliyun.area": { 156 | "type": "string", 157 | "default": "" 158 | }, 159 | "picgo.picBed.aliyun.path": { 160 | "type": "string", 161 | "default": "" 162 | }, 163 | "picgo.picBed.aliyun.customUrl": { 164 | "type": "string", 165 | "default": "" 166 | }, 167 | "picgo.picBed.github.repo": { 168 | "type": "string", 169 | "default": "", 170 | "markdownDescription": "%config.picBed.github.repo.description%" 171 | }, 172 | "picgo.picBed.github.token": { 173 | "type": "string", 174 | "default": "" 175 | }, 176 | "picgo.picBed.github.path": { 177 | "type": "string", 178 | "default": "" 179 | }, 180 | "picgo.picBed.github.customUrl": { 181 | "type": "string", 182 | "default": "" 183 | }, 184 | "picgo.picBed.github.branch": { 185 | "type": "string", 186 | "default": "" 187 | }, 188 | "picgo.picBed.imgur.clientId": { 189 | "type": "string", 190 | "default": "" 191 | }, 192 | "picgo.picBed.imgur.proxy": { 193 | "type": "string", 194 | "default": "" 195 | }, 196 | "picgo.picBed.qiniu.accessKey": { 197 | "type": "string", 198 | "default": "" 199 | }, 200 | "picgo.picBed.qiniu.secretKey": { 201 | "type": "string", 202 | "default": "" 203 | }, 204 | "picgo.picBed.qiniu.bucket": { 205 | "type": "string", 206 | "default": "" 207 | }, 208 | "picgo.picBed.qiniu.url": { 209 | "type": "string", 210 | "default": "" 211 | }, 212 | "picgo.picBed.qiniu.area": { 213 | "type": "string", 214 | "enum": [ 215 | "z0", 216 | "z1", 217 | "z2", 218 | "na0", 219 | "as0" 220 | ], 221 | "default": "z0" 222 | }, 223 | "picgo.picBed.qiniu.options": { 224 | "type": "string", 225 | "default": "" 226 | }, 227 | "picgo.picBed.qiniu.path": { 228 | "type": "string", 229 | "default": "" 230 | }, 231 | "picgo.picBed.tcyun.version": { 232 | "type": "string", 233 | "enum": [ 234 | "v4", 235 | "v5" 236 | ], 237 | "default": "v5" 238 | }, 239 | "picgo.picBed.tcyun.secretId": { 240 | "type": "string", 241 | "default": "" 242 | }, 243 | "picgo.picBed.tcyun.secretKey": { 244 | "type": "string", 245 | "default": "" 246 | }, 247 | "picgo.picBed.tcyun.bucket": { 248 | "type": "string", 249 | "default": "" 250 | }, 251 | "picgo.picBed.tcyun.appId": { 252 | "type": "string", 253 | "default": "" 254 | }, 255 | "picgo.picBed.tcyun.area": { 256 | "type": "string", 257 | "default": "" 258 | }, 259 | "picgo.picBed.tcyun.path": { 260 | "type": "string", 261 | "default": "" 262 | }, 263 | "picgo.picBed.tcyun.customUrl": { 264 | "type": "string", 265 | "default": "" 266 | }, 267 | "picgo.picBed.upyun.bucket": { 268 | "type": "string", 269 | "default": "" 270 | }, 271 | "picgo.picBed.upyun.operator": { 272 | "type": "string", 273 | "default": "" 274 | }, 275 | "picgo.picBed.upyun.password": { 276 | "type": "string", 277 | "default": "" 278 | }, 279 | "picgo.picBed.upyun.options": { 280 | "type": "string", 281 | "default": "" 282 | }, 283 | "picgo.picBed.upyun.path": { 284 | "type": "string", 285 | "default": "" 286 | }, 287 | "picgo.picBed.upyun.url": { 288 | "type": "string", 289 | "default": "" 290 | }, 291 | "picgo.picBed.weibo.chooseCookie": { 292 | "type": "boolean", 293 | "default": true 294 | }, 295 | "picgo.picBed.weibo.username": { 296 | "type": "string", 297 | "default": "" 298 | }, 299 | "picgo.picBed.weibo.quality": { 300 | "type": "string", 301 | "enum": [ 302 | "thumbnail", 303 | "mw690", 304 | "large" 305 | ], 306 | "default": "large" 307 | }, 308 | "picgo.picBed.weibo.cookie": { 309 | "type": "string", 310 | "default": "" 311 | } 312 | } 313 | } 314 | }, 315 | "extensionKind": ["ui"], 316 | "scripts": { 317 | "vscode:prepublish": "yarn && yarn build:prod", 318 | "build": "node esbuild.mjs", 319 | "watch": "node esbuild.mjs --watch", 320 | "build:test": "node esbuild.mjs --test", 321 | "build:prod": "node esbuild.mjs --production", 322 | "audit:fix": "yarn-audit-fix", 323 | "test": "yarn build:test && node dist/run-test.js", 324 | "lint": "eslint --fix --ext .js,.jsx,.ts,.tsx .", 325 | "cz": "lint-staged && git-cz", 326 | "release": "bump-version", 327 | "package": "yarn vsce package --yarn" 328 | }, 329 | "husky": { 330 | "hooks": { 331 | "pre-commit": "lint-staged", 332 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 333 | "post-merge": "sh .scripts/update_dependencies.sh" 334 | } 335 | }, 336 | "lint-staged": { 337 | "*.{js,jsx,ts,tsx,mjs,mjsx,cjs,cjsx}": [ 338 | "eslint --fix --color", 339 | "git add" 340 | ] 341 | }, 342 | "config": { 343 | "commitizen": { 344 | "path": "./node_modules/cz-customizable" 345 | }, 346 | "cz-customizable": { 347 | "config": "./node_modules/@picgo/bump-version/.cz-config.js" 348 | } 349 | }, 350 | "commitlint": { 351 | "extends": [ 352 | "./node_modules/@picgo/bump-version/commitlint-picgo" 353 | ] 354 | }, 355 | "devDependencies": { 356 | "@commitlint/cli": "^8.3.5", 357 | "@picgo/bump-version": "1.1.2", 358 | "@types/glob": "^7.1.1", 359 | "@types/inquirer": "^6.5.0", 360 | "@types/istanbul": "^0.4.30", 361 | "@types/mocha": "^7.0.1", 362 | "@types/node": "16.10.2", 363 | "@types/react": "17.0.22", 364 | "@types/react-dom": "17.0.9", 365 | "@types/react-router-dom": "5.3.0", 366 | "@types/request-promise-native": "^1.0.17", 367 | "@types/vscode": "1.60.0", 368 | "@types/vscode-webview": "1.57.0", 369 | "@typescript-eslint/eslint-plugin": "4.33.0", 370 | "cz-conventional-changelog": "^3.1.0", 371 | "cz-customizable": "6.2.0", 372 | "decache": "^4.5.1", 373 | "electron": "15.1.0", 374 | "esbuild": "0.13.3", 375 | "esbuild-plugin-inline-import": "1.0.1", 376 | "esbuild-plugin-less": "1.1.0", 377 | "eslint": "7.32.0", 378 | "eslint-config-prettier": "8.3.0", 379 | "eslint-config-prettier-standard": "4.0.1", 380 | "eslint-config-standard-with-typescript": "21.0.1", 381 | "eslint-plugin-import": "2.24.2", 382 | "eslint-plugin-node": "11.1.0", 383 | "eslint-plugin-prettier": "4.0.0", 384 | "eslint-plugin-promise": "5.1.0", 385 | "eslint-plugin-react": "7.26.1", 386 | "eslint-plugin-standard": "5.0.0", 387 | "fs-extra": "10.0.0", 388 | "glob": "^7.1.6", 389 | "globby": "11.0.4", 390 | "husky": "7.0.2", 391 | "istanbul": "^0.4.5", 392 | "lint-staged": "10.5.4", 393 | "lodash-es": "4.17.21", 394 | "minimist": "1.2.5", 395 | "mocha": "^7.0.1", 396 | "prettier": "2.2.1", 397 | "prettier-config-standard": "4.0.0", 398 | "remap-istanbul": "^0.13.0", 399 | "type-fest": "2.3.4", 400 | "typescript": "4.4.3", 401 | "vsce": "1.83.0", 402 | "vscode-test": "^1.3.0", 403 | "yarn-audit-fix": "7.0.4" 404 | }, 405 | "dependencies": { 406 | "@emotion/react": "11.4.1", 407 | "@emotion/styled": "11.3.0", 408 | "@luozhu/vscode-channel": "0.8.0", 409 | "@mui/icons-material": "5.0.1", 410 | "@mui/material": "5.0.1", 411 | "@picgo/store": "1.0.3", 412 | "@rematch/core": "2.1.1", 413 | "@rematch/immer": "2.1.2", 414 | "@rematch/loading": "2.1.1", 415 | "@rematch/select": "3.1.1", 416 | "@rematch/updated": "2.1.1", 417 | "appdata-path": "1.0.0", 418 | "immer": "9.0.6", 419 | "picgo": "1.5.0-alpha.0", 420 | "pupa": "3.1.0", 421 | "react": "17.0.2", 422 | "react-dom": "17.0.2", 423 | "react-dropzone": "11.4.2", 424 | "react-redux": "7.2.5", 425 | "react-router-dom": "5.3.0", 426 | "react-use": "17.3.1", 427 | "redux": "4.1.1" 428 | }, 429 | "license": "MIT" 430 | } 431 | --------------------------------------------------------------------------------