├── .gitignore ├── .npmignore ├── .prettierrc ├── LICENSE ├── README.md ├── images ├── clipboard.gif ├── inputbox.gif └── selection.gif ├── package.json ├── package.nls.json ├── src ├── index.ts ├── picgo.ts └── utils.ts ├── tsconfig.json ├── tslint.json ├── webpack.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .vim/ 2 | lib/ 3 | node_modules 4 | 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib 2 | images/ 3 | src/ 4 | node_modules/ 5 | sconfig.json 6 | *.map 7 | .tags 8 | .DS_Store 9 | webpack.config.js 10 | yarn.lock 11 | yarn-error.log 12 | .github 13 | .eslintrc.js 14 | .prettierrc 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "useTabs": false, 6 | "singleQuote": true, 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid" 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 PLDaily 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coc-picgo 2 | 3 | [PicGo](https://github.com/Molunerfinn/PicGo) extension for coc.nvim, forked from [vs-picgo](https://github.com/PicGo/vs-picgo) 4 | 5 | ## Install 6 | 7 | `:CocInstall coc-picgo` 8 | 9 | ## Features 10 | 11 |
12 | Uploading an image from clipboard 13 | clipboard.gif 14 |
15 | 16 |
17 | Uploading images from input box 18 | inputbox.gif 19 |
20 | 21 |
22 | Use selection text as the uploaded fileName 23 | selection.gif 24 | Notice: These characters: \$, :, /, ? and newline will be ignored in the image name. (Because they are invalid for file names.) 25 |
26 | 27 | ## Usage 28 | 29 | ``` 30 | xmap a (coc-codeaction-selected) 31 | nmap a (coc-codeaction-selected) 32 | ``` 33 | 34 | ## License 35 | 36 | MIT 37 | 38 | --- 39 | 40 | > This extension is created by [create-coc-extension](https://github.com/fannheyward/create-coc-extension) 41 | -------------------------------------------------------------------------------- /images/clipboard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLDaily/coc-picgo/9bbd636371384c7f7c51d530d398d9c35bc2101a/images/clipboard.gif -------------------------------------------------------------------------------- /images/inputbox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLDaily/coc-picgo/9bbd636371384c7f7c51d530d398d9c35bc2101a/images/inputbox.gif -------------------------------------------------------------------------------- /images/selection.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLDaily/coc-picgo/9bbd636371384c7f7c51d530d398d9c35bc2101a/images/selection.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coc-picgo", 3 | "version": "1.0.2", 4 | "description": "PicGo extension for coc.nvim, forked from vs-picgo", 5 | "author": "PLDaily ", 6 | "license": "MIT", 7 | "main": "lib/index.js", 8 | "keywords": [ 9 | "coc.nvim", 10 | "vim", 11 | "picgo" 12 | ], 13 | "engines": { 14 | "coc": "^0.0.70" 15 | }, 16 | "scripts": { 17 | "clean": "rimraf lib", 18 | "watch": "webpack --watch", 19 | "build": "webpack", 20 | "lint": "tslint -c tslint.json -p tsconfig.json --fix", 21 | "prepare": "npm-run-all clean build" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^14.0.13", 25 | "coc.nvim": "^0.0.77", 26 | "npm-run-all": "^4.1.5", 27 | "prettier": "^2.0.5", 28 | "rimraf": "^3.0.2", 29 | "ts-loader": "^7.0.5", 30 | "tslint": "^6.1.2", 31 | "tslint-plugin-prettier": "^2.3.0", 32 | "typescript": "^3.9.5", 33 | "webpack": "^4.43.0", 34 | "webpack-cli": "^3.3.11" 35 | }, 36 | "activationEvents": [ 37 | "*" 38 | ], 39 | "contributes": { 40 | "configuration": { 41 | "type": "object", 42 | "title": "coc-picgo configuration", 43 | "properties": { 44 | "picgo.configPath": { 45 | "type": "string", 46 | "markdownDescription": "%config.configPath.description%", 47 | "default": "" 48 | }, 49 | "picgo.dataPath": { 50 | "type": "string", 51 | "markdownDescription": "%config.dataPath.description%", 52 | "default": "" 53 | }, 54 | "picgo.customUploadName": { 55 | "type": "string", 56 | "markdownDescription": "%config.customUploadName.description%", 57 | "default": "${fileName}${extName}" 58 | }, 59 | "picgo.customOutputFormat": { 60 | "type": "string", 61 | "markdownDescription": "%config.customOutputFormat.description%", 62 | "default": "![${uploadedName}](${url})" 63 | }, 64 | "picgo.picBed.current": { 65 | "type": "string", 66 | "enum": [ 67 | "smms", 68 | "aliyun", 69 | "github", 70 | "imgur", 71 | "qiniu", 72 | "tcyun", 73 | "upyun", 74 | "weibo" 75 | ], 76 | "default": "smms", 77 | "markdownDescription": "%config.picBed.description%" 78 | }, 79 | "picgo.picBed.smms.token": { 80 | "type": "string", 81 | "default": "" 82 | }, 83 | "picgo.picBed.aliyun.accessKeyId": { 84 | "type": "string", 85 | "default": "" 86 | }, 87 | "picgo.picBed.aliyun.accessKeySecret": { 88 | "type": "string", 89 | "default": "" 90 | }, 91 | "picgo.picBed.aliyun.bucket": { 92 | "type": "string", 93 | "default": "" 94 | }, 95 | "picgo.picBed.aliyun.area": { 96 | "type": "string", 97 | "default": "" 98 | }, 99 | "picgo.picBed.aliyun.path": { 100 | "type": "string", 101 | "default": "" 102 | }, 103 | "picgo.picBed.aliyun.customUrl": { 104 | "type": "string", 105 | "default": "" 106 | }, 107 | "picgo.picBed.github.repo": { 108 | "type": "string", 109 | "default": "", 110 | "markdownDescription": "%config.picBed.github.repo.description%" 111 | }, 112 | "picgo.picBed.github.token": { 113 | "type": "string", 114 | "default": "" 115 | }, 116 | "picgo.picBed.github.path": { 117 | "type": "string", 118 | "default": "" 119 | }, 120 | "picgo.picBed.github.customUrl": { 121 | "type": "string", 122 | "default": "" 123 | }, 124 | "picgo.picBed.github.branch": { 125 | "type": "string", 126 | "default": "" 127 | }, 128 | "picgo.picBed.imgur.clientId": { 129 | "type": "string", 130 | "default": "" 131 | }, 132 | "picgo.picBed.imgur.proxy": { 133 | "type": "string", 134 | "default": "" 135 | }, 136 | "picgo.picBed.qiniu.accessKey": { 137 | "type": "string", 138 | "default": "" 139 | }, 140 | "picgo.picBed.qiniu.secretKey": { 141 | "type": "string", 142 | "default": "" 143 | }, 144 | "picgo.picBed.qiniu.bucket": { 145 | "type": "string", 146 | "default": "" 147 | }, 148 | "picgo.picBed.qiniu.url": { 149 | "type": "string", 150 | "default": "" 151 | }, 152 | "picgo.picBed.qiniu.area": { 153 | "type": "string", 154 | "enum": [ 155 | "z0", 156 | "z1", 157 | "z2", 158 | "na0", 159 | "as0" 160 | ], 161 | "default": "z0" 162 | }, 163 | "picgo.picBed.qiniu.options": { 164 | "type": "string", 165 | "default": "" 166 | }, 167 | "picgo.picBed.qiniu.path": { 168 | "type": "string", 169 | "default": "" 170 | }, 171 | "picgo.picBed.tcyun.version": { 172 | "type": "string", 173 | "enum": [ 174 | "v4", 175 | "v5" 176 | ], 177 | "default": "v5" 178 | }, 179 | "picgo.picBed.tcyun.secretId": { 180 | "type": "string", 181 | "default": "" 182 | }, 183 | "picgo.picBed.tcyun.secretKey": { 184 | "type": "string", 185 | "default": "" 186 | }, 187 | "picgo.picBed.tcyun.bucket": { 188 | "type": "string", 189 | "default": "" 190 | }, 191 | "picgo.picBed.tcyun.appId": { 192 | "type": "string", 193 | "default": "" 194 | }, 195 | "picgo.picBed.tcyun.area": { 196 | "type": "string", 197 | "default": "" 198 | }, 199 | "picgo.picBed.tcyun.path": { 200 | "type": "string", 201 | "default": "" 202 | }, 203 | "picgo.picBed.tcyun.customUrl": { 204 | "type": "string", 205 | "default": "" 206 | }, 207 | "picgo.picBed.upyun.bucket": { 208 | "type": "string", 209 | "default": "" 210 | }, 211 | "picgo.picBed.upyun.operator": { 212 | "type": "string", 213 | "default": "" 214 | }, 215 | "picgo.picBed.upyun.password": { 216 | "type": "string", 217 | "default": "" 218 | }, 219 | "picgo.picBed.upyun.options": { 220 | "type": "string", 221 | "default": "" 222 | }, 223 | "picgo.picBed.upyun.path": { 224 | "type": "string", 225 | "default": "" 226 | }, 227 | "picgo.picBed.upyun.url": { 228 | "type": "string", 229 | "default": "" 230 | }, 231 | "picgo.picBed.weibo.chooseCookie": { 232 | "type": "boolean", 233 | "default": true 234 | }, 235 | "picgo.picBed.weibo.username": { 236 | "type": "string", 237 | "default": "" 238 | }, 239 | "picgo.picBed.weibo.quality": { 240 | "type": "string", 241 | "enum": [ 242 | "thumbnail", 243 | "mw690", 244 | "large" 245 | ], 246 | "default": "large" 247 | }, 248 | "picgo.picBed.weibo.cookie": { 249 | "type": "string", 250 | "default": "" 251 | } 252 | } 253 | }, 254 | "commands": [ 255 | { 256 | "command": "picgo.uploadImageFromClipboard", 257 | "title": "picgo.uploadImageFromClipboard" 258 | }, 259 | { 260 | "command": "picgo.uploadImageFromInputBox", 261 | "title": "picgo.uploadImageFromInputBox" 262 | } 263 | ] 264 | }, 265 | "dependencies": { 266 | "lodash": "^4.17.19", 267 | "lodash-id": "^0.14.0", 268 | "picgo": "^1.4.10" 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /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 | "ext.keywords": ["image", "picture", "upload", "image upload", "picture upload"], 5 | "command.upload.clipboard.title": "Upload image from clipboard", 6 | "command.upload.explorer.title": "Upload image from explorer", 7 | "command.upload.inputBox.title": "Upload image from inputBox", 8 | "config.title": "PicGo", 9 | "config.configPath.description": "The path to your PicGo-Core configuration. PicGo will use `#picgo.picBed#` if this is not specified.", 10 | "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.", 11 | "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`", 12 | "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\"`", 13 | "config.picBed.description": "PicGo-Core configuration, please see [PicGo Docs](https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E5%9B%BE%E5%BA%8A%E5%8C%BA).", 14 | "config.picBed.smms.description": "SM.MS picBed configuration.", 15 | "config.picBed.weibo.description": "Weibo picBed configuration.", 16 | "config.picBed.qiniu.description": "Qiniu picBed configuration.", 17 | "config.picBed.upyun.description": "Upyun picBed configuration.", 18 | "config.picBed.tcyun.description": "Tencent COS picBed configuration.", 19 | "config.picBed.github.description": "GitHub configuration.", 20 | "config.picBed.github.repo.description": "`Username/Repo`. For example, PicGo/Images", 21 | "config.picBed.aliyun.description": "Aliyun OSS configuration.", 22 | "config.picBed.imgur.description": "Imgur picBed configuration." 23 | } 24 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CodeActionProvider, 3 | languages, 4 | commands, 5 | ExtensionContext, 6 | Uri, 7 | workspace, 8 | } from 'coc.nvim'; 9 | import * as fs from 'fs'; 10 | import * as path from 'path'; 11 | import { Command, Range, TextDocument } from 'vscode-languageserver-protocol'; 12 | import VSPicgo from './picgo'; 13 | 14 | function uploadImageFromClipboard( 15 | vspicgo: VSPicgo, 16 | ): Promise { 17 | return vspicgo.upload(); 18 | } 19 | 20 | async function uploadImageFromInputBox( 21 | vspicgo: VSPicgo, 22 | ): Promise { 23 | const doc = await workspace.document; 24 | if (!doc) return; 25 | let result = await workspace.requestInput( 26 | 'Please input an image location path', 27 | ); 28 | if (!result) return; 29 | 30 | // check if `result` is a path of image file 31 | const imageReg = /\.(png|jpg|jpeg|webp|gif|bmp|tiff|ico)$/; 32 | if (result && imageReg.test(result)) { 33 | result = path.isAbsolute(result) 34 | ? result 35 | : path.join(Uri.parse(doc.uri).fsPath, '../', result); 36 | if (fs.existsSync(result)) { 37 | return vspicgo.upload([result]); 38 | } else { 39 | workspace.showMessage('No such image.'); 40 | } 41 | } else { 42 | workspace.showMessage('No such image.'); 43 | } 44 | } 45 | 46 | class ReactRefactorCodeActionProvider implements CodeActionProvider { 47 | async provideCodeActions( 48 | document: TextDocument, 49 | range: Range, 50 | ): Promise { 51 | const codeActions: Command[] = []; 52 | const selectedText = document.getText(range); 53 | if (selectedText) { 54 | codeActions.push({ 55 | command: 'picgo.uploadImageFromClipboard', 56 | title: 'picgo.uploadImageFromClipboard', 57 | arguments: ['v'], 58 | }); 59 | codeActions.push({ 60 | command: 'picgo.uploadImageFromInputBox', 61 | title: 'picgo.uploadImageFromInputBox', 62 | arguments: ['v'], 63 | }); 64 | } 65 | return codeActions; 66 | } 67 | } 68 | 69 | export async function activate(context: ExtensionContext): Promise { 70 | const vspicgo = new VSPicgo(); 71 | const disposable = [ 72 | languages.registerCodeActionProvider( 73 | [{ scheme: 'file', pattern: '**/*.{md,markdown}' }], 74 | new ReactRefactorCodeActionProvider(), 75 | 'coc-picgo', 76 | ), 77 | commands.registerCommand('picgo.uploadImageFromClipboard', async mode => { 78 | vspicgo.mode = mode; 79 | uploadImageFromClipboard(vspicgo); 80 | }), 81 | commands.registerCommand('picgo.uploadImageFromInputBox', mode => { 82 | vspicgo.mode = mode; 83 | uploadImageFromInputBox(vspicgo); 84 | }), 85 | ]; 86 | context.subscriptions.push(...disposable); 87 | } 88 | -------------------------------------------------------------------------------- /src/picgo.ts: -------------------------------------------------------------------------------- 1 | import { Uri, workspace } from 'coc.nvim'; 2 | import { EventEmitter } from 'events'; 3 | import * as fs from 'fs'; 4 | import * as os from 'os'; 5 | import * as path from 'path'; 6 | import PicGo from 'picgo'; 7 | import { IImgInfo, IPlugin } from 'picgo/dist/src/utils/interfaces'; 8 | import { promisify } from 'util'; 9 | import { TextEdit } from 'vscode-languageserver-protocol'; 10 | import { 11 | formatParam, 12 | formatString, 13 | getUploadedName, 14 | showError, 15 | showInfo, 16 | } from './utils'; 17 | 18 | const _ = require('lodash'); 19 | const _db = require('lodash-id'); 20 | _.mixin(_db); 21 | 22 | const writeFileP = promisify(fs.writeFile); 23 | const readFileP = promisify(fs.readFile); 24 | 25 | export interface INotice { 26 | body: string; 27 | text: string; 28 | title: string; 29 | } 30 | 31 | export interface IUploadName { 32 | date: string; 33 | dateTime: string; 34 | fileName: string; 35 | extName: string; 36 | mdFileName: string; 37 | [key: string]: string; 38 | } 39 | 40 | export interface IOutputUrl { 41 | uploadedName: string; 42 | url: string; 43 | [key: string]: string; 44 | } 45 | 46 | export enum EVSPicgoHooks { 47 | updated = 'updated', 48 | } 49 | 50 | export default class VSPicgo extends EventEmitter { 51 | private static picgo: PicGo = new PicGo(); 52 | public mode: string = 'v'; 53 | 54 | constructor() { 55 | super(); 56 | this.configPicgo(); 57 | // Before upload, we change names of the images. 58 | this.registerRenamePlugin(); 59 | // After upload, we use the custom output format. 60 | this.addGenerateOutputListener(); 61 | } 62 | 63 | configPicgo() { 64 | let config = workspace.getConfiguration('picgo'); 65 | const picgoConfigPath = config.get('configPath'); 66 | if (picgoConfigPath) { 67 | VSPicgo.picgo.setConfig( 68 | JSON.parse( 69 | fs.readFileSync(picgoConfigPath, { 70 | encoding: 'utf-8', 71 | }), 72 | ), 73 | ); 74 | } else { 75 | const picBed = config.get('picBed'); 76 | VSPicgo.picgo.setConfig({ picBed }); 77 | } 78 | } 79 | 80 | addGenerateOutputListener() { 81 | VSPicgo.picgo.on('finished', async (ctx: PicGo) => { 82 | let urlText = ''; 83 | const outputFormatTemplate = 84 | workspace.getConfiguration('picgo').get('customOutputFormat') || 85 | '![${uploadedName}](${url})'; 86 | try { 87 | urlText = ctx.output.reduce( 88 | (acc: string, imgInfo: IImgInfo): string => { 89 | return `${acc}${formatString(outputFormatTemplate, { 90 | uploadedName: getUploadedName(imgInfo), 91 | url: imgInfo.imgUrl, 92 | })}\n`; 93 | }, 94 | '', 95 | ); 96 | urlText = urlText.trim(); 97 | await this.updateData(ctx.output); 98 | } catch (err) { 99 | if (err instanceof SyntaxError) { 100 | showError( 101 | `the data file ${this.dataPath} has syntax error, ` + 102 | `please fix the error by yourself or delete the data file and vs-picgo will recreate for you.`, 103 | ); 104 | } else { 105 | showError( 106 | `failed to read from data file ${this.dataPath}: ${err || ''}`, 107 | ); 108 | } 109 | return; 110 | } 111 | const doc = await workspace.document; 112 | if (!doc) return; 113 | let edits: TextEdit[] = []; 114 | if (!this.mode) { 115 | const position = await workspace.getCursorPosition(); 116 | edits = [TextEdit.insert(position, urlText)]; 117 | } else { 118 | const mode = await workspace.nvim.call('mode'); 119 | const range = await workspace.getSelectedRange(mode, doc); 120 | if (!range) return; 121 | edits = [TextEdit.replace(range, urlText)]; 122 | } 123 | await doc.applyEdits(edits); 124 | showInfo(`image uploaded successfully.`); 125 | this.emit(EVSPicgoHooks.updated, urlText); 126 | }); 127 | } 128 | 129 | registerRenamePlugin() { 130 | let beforeUploadPlugin: IPlugin = { 131 | handle: async (ctx: PicGo) => { 132 | const uploadNameTemplate = 133 | workspace.getConfiguration('picgo').get('customUploadName') || 134 | '${fileName}'; 135 | if (ctx.output.length === 1) { 136 | ctx.output[0].fileName = await this.changeFilename( 137 | ctx.output[0].fileName || '', 138 | uploadNameTemplate, 139 | undefined, 140 | ); 141 | } else { 142 | for (let index = 0; index < ctx.output.length; index++) { 143 | ctx.output[index].fileName = await this.changeFilename( 144 | ctx.output[index].filename || '', 145 | uploadNameTemplate, 146 | index, 147 | ); 148 | } 149 | } 150 | }, 151 | }; 152 | if (VSPicgo.picgo.helper.beforeUploadPlugins.get('vsPicgoRenamePlugin')) { 153 | VSPicgo.picgo.helper.beforeUploadPlugins.unregister( 154 | 'vsPicgoRenamePlugin', 155 | ); 156 | } 157 | VSPicgo.picgo.helper.beforeUploadPlugins.register( 158 | 'vsPicgoRenamePlugin', 159 | beforeUploadPlugin, 160 | ); 161 | } 162 | 163 | /** 164 | * Returns the modified file name as per `customUploadName` setting 165 | * @param original The filename of the original image file. 166 | * @param template The template string. 167 | */ 168 | async changeFilename( 169 | original: string, 170 | template: string, 171 | index: number | undefined, 172 | ) { 173 | const doc = await workspace.document; 174 | if (!doc) return; 175 | let selectedString: string; 176 | if (!this.mode) { 177 | selectedString = ''; 178 | } else { 179 | const m = await workspace.nvim.call('visualmode'); 180 | const range = await workspace.getSelectedRange(m, doc); 181 | if (!range) return; 182 | selectedString = doc.textDocument.getText(range); 183 | } 184 | const nameReg = /[:\/\?\$]+/g; // limitations of name 185 | const userDefineName = selectedString.replace(nameReg, () => ''); 186 | if (userDefineName) { 187 | original = userDefineName + (index || '') + path.extname(original); 188 | } 189 | const mdFilePath = Uri.parse(doc.uri).fsPath; 190 | const mdFileName = path.basename(mdFilePath, path.extname(mdFilePath)); 191 | let uploadNameData = formatParam(original, mdFileName); 192 | return formatString(template, uploadNameData); 193 | } 194 | 195 | get dataPath(): string { 196 | const picgoConfig = workspace.getConfiguration('picgo'); 197 | return ( 198 | picgoConfig.dataPath || path.resolve(os.homedir(), 'vs-picgo-data.json') 199 | ); 200 | } 201 | 202 | async initDataFile(dataPath: string) { 203 | if (!fs.existsSync(dataPath)) { 204 | await writeFileP( 205 | dataPath, 206 | JSON.stringify({ uploaded: [] }, null, 2), 207 | 'utf8', 208 | ); 209 | } 210 | } 211 | 212 | async upload(input?: string[]): Promise { 213 | // This is necessary, because user may have changed settings 214 | this.configPicgo(); 215 | 216 | // uploading progress 217 | VSPicgo.picgo.on('uploadProgress', (p: number) => { 218 | showInfo(`image uploading ${p}% ...`); 219 | }); 220 | VSPicgo.picgo.on('notification', (notice: INotice) => { 221 | showError(`${notice.title}! ${notice.body || ''}${notice.text || ''}`); 222 | }); 223 | VSPicgo.picgo.on('failed', () => { 224 | showError(`image upload failed`); 225 | }); 226 | 227 | return VSPicgo.picgo.upload(input); 228 | } 229 | 230 | async updateData(picInfos: Array) { 231 | const dataPath = this.dataPath; 232 | if (!fs.existsSync(dataPath)) { 233 | await this.initDataFile(dataPath); 234 | showInfo('data file created at ${dataPath}.'); 235 | } 236 | const dataRaw = await readFileP(dataPath, 'utf8'); 237 | const data = JSON.parse(dataRaw); 238 | if (!data.uploaded) { 239 | data.uploaded = []; 240 | } 241 | picInfos.forEach(picInfo => { 242 | _.insert(data['uploaded'], picInfo); 243 | }); 244 | await writeFileP(dataPath, JSON.stringify(data, null, 2), 'utf8'); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'coc.nvim'; 2 | import * as path from 'path'; 3 | import { IImgInfo } from 'picgo/dist/src/utils/interfaces'; 4 | import { IOutputUrl, IUploadName } from './picgo'; 5 | 6 | export function formatParam(file: string, mdFileName: string): IUploadName { 7 | const dt = new Date(); 8 | const y = dt.getFullYear(); 9 | const m = dt.getMonth() + 1; 10 | const d = dt.getDate(); 11 | const h = dt.getHours(); 12 | const mm = dt.getMinutes(); 13 | const s = dt.getSeconds(); 14 | 15 | let pad = function (x: number) { 16 | return ('00' + x).slice(-2); 17 | }; 18 | 19 | const date = `${y}-${pad(m)}-${pad(d)}`; 20 | var extName = path.extname(file); 21 | 22 | return { 23 | date, 24 | dateTime: `${date}-${pad(h)}-${pad(mm)}-${pad(s)}`, 25 | fileName: path.basename(file, extName), 26 | extName, 27 | mdFileName, 28 | }; 29 | } 30 | 31 | export function formatString( 32 | tplString: string, 33 | data: IUploadName | IOutputUrl, 34 | ) { 35 | const keys = Object.keys(data); 36 | const values = keys.map(k => data[k]); 37 | return new Function(keys.join(','), 'return `' + tplString + '`').apply( 38 | null, 39 | values, 40 | ); 41 | } 42 | 43 | import nls = require('../package.nls.json'); 44 | 45 | function addPeriod(messgae: string) { 46 | if (!messgae.endsWith('.') && !messgae.endsWith('!')) { 47 | messgae = messgae + '.'; 48 | } 49 | return messgae; 50 | } 51 | 52 | export function showWarning(messgae: string) { 53 | messgae = addPeriod(messgae); 54 | workspace.showMessage(`${nls['ext.displayName']}: ${messgae}`); 55 | } 56 | 57 | export function showError(messgae: string) { 58 | messgae = addPeriod(messgae); 59 | workspace.showMessage(`${nls['ext.displayName']}: ${messgae}`); 60 | } 61 | 62 | export function showInfo(messgae: string) { 63 | messgae = addPeriod(messgae); 64 | workspace.showMessage(`${nls['ext.displayName']}: ${messgae}`); 65 | } 66 | 67 | /** 68 | * Return uploaded name accrding to `imgInfo.fileName`, 69 | * extname will be removed for the sake of simplicity when used as alt. 70 | * @param imgInfo 71 | */ 72 | export function getUploadedName(imgInfo: IImgInfo): string { 73 | let fullName; 74 | if (!imgInfo.fileName) { 75 | fullName = ''; 76 | } else { 77 | fullName = imgInfo.fileName as string; 78 | } 79 | let basename = path.basename(fullName, path.extname(fullName)); 80 | return basename; 81 | } 82 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "lib": ["es2017", "es2018"], 5 | "module": "commonjs", 6 | "declaration": false, 7 | "sourceMap": true, 8 | "outDir": "lib", 9 | "strict": true, 10 | "moduleResolution": "node", 11 | "resolveJsonModule": true, 12 | "noImplicitAny": false, 13 | "esModuleInterop": true 14 | }, 15 | "include": ["src"] 16 | } 17 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-plugin-prettier"], 3 | "rules": { 4 | "prettier": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | target: 'node', 6 | mode: 'none', 7 | resolve: { 8 | mainFields: ['module', 'main'], 9 | extensions: ['.js', '.ts', '.json'] 10 | }, 11 | externals: { 12 | 'coc.nvim': 'commonjs coc.nvim', 13 | 'picgo': 'commonjs picgo' 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.ts$/, 19 | include: [path.resolve(__dirname, 'src')], 20 | use: [ 21 | { 22 | loader: 'ts-loader', 23 | options: { 24 | compilerOptions: { 25 | sourceMap: true 26 | } 27 | } 28 | } 29 | ] 30 | } 31 | ] 32 | }, 33 | output: { 34 | path: path.join(__dirname, 'lib'), 35 | filename: 'index.js', 36 | libraryTarget: 'commonjs' 37 | }, 38 | plugins: [], 39 | node: { 40 | __dirname: false, 41 | __filename: false 42 | } 43 | }; 44 | --------------------------------------------------------------------------------