├── .eslintrc.json ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .vscodeignore ├── CHANGELOG.md ├── README.md ├── extension.js ├── jsconfig.json ├── lib ├── icon.ico ├── linux.sh ├── mac.applescript ├── pc.ps1 └── upload.js ├── package.json ├── screenshot └── screenshot.gif └── test ├── extension.test.js └── index.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "jsx": true 11 | }, 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "no-const-assign": "warn", 16 | "no-this-before-super": "warn", 17 | "no-undef": "warn", 18 | "no-unreachable": "warn", 19 | "no-unused-vars": "warn", 20 | "constructor-super": "warn", 21 | "valid-typeof": "warn" 22 | } 23 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | paste-image-to-qiniu-* -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false 12 | }, 13 | { 14 | "name": "Launch Tests", 15 | "type": "extensionHost", 16 | "request": "launch", 17 | "runtimeExecutable": "${execPath}", 18 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/test" ], 19 | "stopOnEntry": false 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | test/** 4 | .gitignore 5 | jsconfig.json 6 | vsc-extension-quickstart.md 7 | .eslintrc.json 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to the "paste-image-to-qiniu" extension will be documented in this file. 3 | 4 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 5 | 6 | ## [Unreleased] 7 | - Initial release -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vscode-paste-image-to-qiniu 2 | 3 | 一个可以支持截图粘贴上传图片到七牛、让你写用vscode写markdown有更好的体验。 4 | 5 | ![screenshot](./screenshot/screenshot.gif) 6 | 7 | ## 安装 8 | 输入命令: 9 | ```bash 10 | ext install paste-image-to-qiniu 11 | ``` 12 | 或者在插件应用商店搜索paste-image-to-qiniu安装 13 | 14 | ## 参数设置 15 | ```js 16 | { 17 | // 有效的七牛 AccessKey 签名授权 18 | "pasteImageToQiniu.access_key": "*****************************************", 19 | 20 | // 有效的七牛 SecretKey 签名授权 21 | "pasteImageToQiniu.secret_key": "*****************************************", 22 | 23 | // 七牛图片上传空间 24 | "pasteImageToQiniu.bucket": "blog", 25 | 26 | // 七牛图片上传路径,参数化命名,暂时支持 ${fileName}、${mdFileName}、${date}、${dateTime} 27 | // 示例: 28 | // ${fileName}-${date} -> picName-20160725.jpg 29 | // ${mdFileName}-${dateTime} -> markdownName-20170412222810.jpg 30 | "pasteImageToQiniu.remotePath": "${fileName}", 31 | 32 | // 七牛图床域名 33 | "pasteImageToQiniu.domain": "http://xxxxx.xxxx.com", 34 | 35 | // 本地储存位置 36 | "pasteImageToQiniu.localPath":"./img" 37 | } 38 | ``` 39 | 40 | 一直使用vscode来开发、写笔记文章、但是用了几款vscode的图床插件都不是很符合我的需求。今天本来想看看书写点笔记、但是发现截图了去处理图片太过于麻烦、于是有了这个插件、仅仅是想体验一下vscode插件的开发流程、也可以方便自己的写体验。 41 | 42 | 将[vscode-paste-image](https://github.com/mushanshitiancai/vscode-paste-image)和[vscode-qiniu-upload-image](https://github.com/yscoder/vscode-qiniu-upload-image)综合改成了现在这个插件。 43 | 44 | 如果用的开心给个star也不错! -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | const vscode = require('vscode'); 2 | const path = require('path'); 3 | const moment = require('moment'); 4 | const fs = require('fs'); 5 | const { spawn } = require('child_process'); 6 | const qnUpload = require('./lib/upload'); 7 | 8 | exports.activate = (context) => { 9 | const disposable = vscode.commands.registerCommand('extension.pasteImageToQiniu', () => { 10 | start(); 11 | }); 12 | context.subscriptions.push(disposable); 13 | } 14 | 15 | // this method is called when your extension is deactivated 16 | exports.deactivate = () => { } 17 | 18 | function start() { 19 | // 获取当前编辑文件 20 | let editor = vscode.window.activeTextEditor; 21 | if (!editor) return; 22 | 23 | let fileUri = editor.document.uri; 24 | if (!fileUri) return; 25 | 26 | if (fileUri.scheme === 'untitled') { 27 | vscode.window.showInformationMessage('Before paste image, you need to save current edit file first.'); 28 | return; 29 | } 30 | 31 | let selection = editor.selection; 32 | let selectText = editor.document.getText(selection); 33 | 34 | if (selectText && !/^[\w\-.]+$/.test(selectText)) { 35 | vscode.window.showInformationMessage('Your selection is not a valid file name!'); 36 | return; 37 | } 38 | let config = vscode.workspace.getConfiguration('pasteImageToQiniu'); 39 | let localPath = config['localPath']; 40 | if (localPath && (localPath.length !== localPath.trim().length)) { 41 | vscode.window.showErrorMessage('The specified path is invalid. "' + localPath + '"'); 42 | return; 43 | } 44 | 45 | let filePath = fileUri.fsPath; 46 | let imagePath = getImagePath(filePath, selectText, localPath); 47 | const mdFilePath = editor.document.fileName; 48 | const mdFileName = path.basename(mdFilePath, path.extname(mdFilePath)); 49 | 50 | createImageDirWithImagePath(imagePath).then(imagePath => { 51 | saveClipboardImageToFileAndGetPath(imagePath, (imagePath) => { 52 | if (!imagePath) return; 53 | if (imagePath === 'no image') { 54 | vscode.window.showInformationMessage('There is not a image in clipboard.'); 55 | return; 56 | } 57 | qnUpload(config, imagePath, mdFilePath).then(({ name, url }) => { 58 | vscode.window.showInformationMessage('上传成功'); 59 | const img = `![${name}](${url})`; 60 | editor.edit(textEditorEdit => { 61 | textEditorEdit.insert(editor.selection.active, img) 62 | }); 63 | }).catch((err) => { 64 | vscode.window.showErrorMessage('Upload error.'); 65 | }); 66 | }); 67 | }).catch(err => { 68 | vscode.window.showErrorMessage('Failed make folder.'); 69 | return; 70 | }); 71 | } 72 | 73 | function getImagePath(filePath, selectText, localPath) { 74 | // 图片名称 75 | let imageFileName = ''; 76 | if (!selectText) { 77 | imageFileName = moment().format("Y-MM-DD-HH-mm-ss") + '.png'; 78 | } else { 79 | imageFileName = selectText + '.png'; 80 | } 81 | 82 | // 图片本地保存路径 83 | let folderPath = path.dirname(filePath); 84 | let imagePath = ''; 85 | if (path.isAbsolute(localPath)) { 86 | imagePath = path.join(localPath, imageFileName); 87 | } else { 88 | imagePath = path.join(folderPath, localPath, imageFileName); 89 | } 90 | 91 | return imagePath; 92 | } 93 | 94 | function createImageDirWithImagePath(imagePath) { 95 | return new Promise((resolve, reject) => { 96 | let imageDir = path.dirname(imagePath); 97 | fs.exists(imageDir, (exists) => { 98 | if (exists) { 99 | resolve(imagePath); 100 | return; 101 | } 102 | fs.mkdir(imageDir, (err) => { 103 | if (err) { 104 | reject(err); 105 | return; 106 | } 107 | resolve(imagePath); 108 | }); 109 | }); 110 | }); 111 | } 112 | 113 | function saveClipboardImageToFileAndGetPath(imagePath, cb) { 114 | if (!imagePath) return; 115 | let platform = process.platform; 116 | if (platform === 'win32') { 117 | // Windows 118 | const scriptPath = path.join(__dirname, './lib/pc.ps1'); 119 | const powershell = spawn('powershell', [ 120 | '-noprofile', 121 | '-noninteractive', 122 | '-nologo', 123 | '-sta', 124 | '-executionpolicy', 'unrestricted', 125 | '-windowstyle', 'hidden', 126 | '-file', scriptPath, 127 | imagePath 128 | ]); 129 | powershell.on('exit', function (code, signal) { 130 | 131 | }); 132 | powershell.stdout.on('data', function (data) { 133 | cb(data.toString().trim()); 134 | }); 135 | } else if (platform === 'darwin') { 136 | // Mac 137 | let scriptPath = path.join(__dirname, './lib/mac.applescript'); 138 | 139 | let ascript = spawn('osascript', [scriptPath, imagePath]); 140 | ascript.on('exit', function (code, signal) { 141 | 142 | }); 143 | 144 | ascript.stdout.on('data', function (data) { 145 | cb(data.toString().trim()); 146 | }); 147 | } else { 148 | // Linux 149 | 150 | let scriptPath = path.join(__dirname, './lib/linux.sh'); 151 | 152 | let ascript = spawn('sh', [scriptPath, imagePath]); 153 | ascript.on('exit', function (code, signal) { 154 | 155 | }); 156 | 157 | ascript.stdout.on('data', function (data) { 158 | let result = data.toString().trim(); 159 | if (result == "no xclip") { 160 | vscode.window.showInformationMessage('You need to install xclip command first.'); 161 | return; 162 | } 163 | cb(result); 164 | }); 165 | } 166 | } -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "lib": [ 6 | "es6" 7 | ] 8 | }, 9 | "exclude": [ 10 | "node_modules" 11 | ] 12 | } -------------------------------------------------------------------------------- /lib/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huichi2028/vscode-qiniu-upload-image/f76826e91939cbd047579a6de0291ed77d0d8622/lib/icon.ico -------------------------------------------------------------------------------- /lib/linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # require xclip(see http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script/677212#677212) 4 | command -v xclip >/dev/null 2>&1 || { echo >&1 "no xclip"; exit 1; } 5 | 6 | # write image in clipboard to file (see http://unix.stackexchange.com/questions/145131/copy-image-from-clipboard-to-file) 7 | if 8 | xclip -selection clipboard -target image/png -o >/dev/null 2>&1 9 | then 10 | xclip -selection clipboard -target image/png -o >$1 2>/dev/null 11 | echo $1 12 | else 13 | echo "no image" 14 | fi -------------------------------------------------------------------------------- /lib/mac.applescript: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huichi2028/vscode-qiniu-upload-image/f76826e91939cbd047579a6de0291ed77d0d8622/lib/mac.applescript -------------------------------------------------------------------------------- /lib/pc.ps1: -------------------------------------------------------------------------------- 1 | param($imagePath) 2 | 3 | # Adapted from https://github.com/octan3/img-clipboard-dump/blob/master/dump-clipboard-png.ps1 4 | 5 | Add-Type -Assembly PresentationCore 6 | $img = [Windows.Clipboard]::GetImage() 7 | 8 | if ($img -eq $null) { 9 | "no image" 10 | Exit 1 11 | } 12 | 13 | if (-not $imagePath) { 14 | "no image" 15 | Exit 1 16 | } 17 | 18 | $fcb = new-object Windows.Media.Imaging.FormatConvertedBitmap($img, [Windows.Media.PixelFormats]::Rgb24, $null, 0) 19 | $stream = [IO.File]::Open($imagePath, "OpenOrCreate") 20 | $encoder = New-Object Windows.Media.Imaging.PngBitmapEncoder 21 | $encoder.Frames.Add([Windows.Media.Imaging.BitmapFrame]::Create($fcb)) | out-null 22 | $encoder.Save($stream) | out-null 23 | $stream.Dispose() | out-null 24 | 25 | $imagePath -------------------------------------------------------------------------------- /lib/upload.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu') 2 | const path = require('path') 3 | const url = require('url') 4 | 5 | const PutPolicy = qiniu.rs.PutPolicy 6 | const PutExtra = qiniu.io.PutExtra 7 | 8 | // 上传策略函数 9 | const uptoken = (bucket, key) => new PutPolicy(`${bucket}:${key}`).token() 10 | 11 | // 默认参数 12 | const formatParam = (file, mdFileName) => { 13 | const dt = new Date() 14 | const y = dt.getFullYear() 15 | const m = dt.getMonth() + 1 16 | const d = dt.getDate() 17 | const h = dt.getHours() 18 | const mm = dt.getMinutes() 19 | const s = dt.getSeconds() 20 | 21 | const date = `${y}${m}${d}` 22 | var ext = path.extname(file) 23 | 24 | return { 25 | date, 26 | dateTime: `${date}${h}${mm}${s}`, 27 | fileName: path.basename(file, ext), 28 | ext, 29 | mdFileName 30 | } 31 | } 32 | 33 | const formatString = (tplString, data) => { 34 | const keys = Object.keys(data) 35 | const values = keys.map(k => data[k]) 36 | 37 | return new Function(keys.join(','), 'return `' + tplString + '`').apply(null, values) 38 | } 39 | 40 | module.exports = (config, file, mdFile) => { 41 | let access_key = config.access_key; 42 | let secret_key = config.secret_key; 43 | let bucket = config.bucket; 44 | let domain = config.domain; 45 | let remotePath = config.remotePath; 46 | 47 | qiniu.conf.ACCESS_KEY = access_key 48 | qiniu.conf.SECRET_KEY = secret_key 49 | 50 | let localFile = file 51 | if (/^".+"$/.test(localFile)) { 52 | localFile = file.substring(1, file.length - 1) 53 | } 54 | 55 | // 预设参数值 56 | const param = formatParam(localFile, mdFile) 57 | //上传到七牛后保存的文件名 58 | const saveFile = formatString(remotePath + '${ext}', param) 59 | //生成上传 Token 60 | const token = uptoken(bucket, saveFile) 61 | 62 | return new Promise((resolve, reject) => { 63 | 64 | const extra = new PutExtra() 65 | 66 | qiniu.io.putFile(token, saveFile, localFile, extra, (err, { key }) => { 67 | 68 | if (!err) { 69 | // 上传成功, 处理返回值 70 | resolve({ 71 | name: path.basename(key, param.ext), 72 | url: url.resolve(domain, saveFile) 73 | }) 74 | } else { 75 | // 上传失败, 处理返回代码 76 | reject(err) 77 | } 78 | }) 79 | }) 80 | } 81 | 82 | 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paste-image-to-qiniu", 3 | "displayName": "paste image to qiniu", 4 | "description": "截图上传七牛云插件", 5 | "version": "0.0.1", 6 | "publisher": "favers", 7 | "author": { 8 | "name": "favers", 9 | "email": "favers0601@gmail.com" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/favers/vscode-qiniu-upload-image.git" 14 | }, 15 | "homepage": "https://github.com/favers/vscode-qiniu-upload-image/blob/master/README.md", 16 | "bugs": { 17 | "url": "https://github.com/favers/vscode-qiniu-upload-image/issues" 18 | }, 19 | "engines": { 20 | "vscode": "^1.11.0" 21 | }, 22 | "categories": [ 23 | "Other" 24 | ], 25 | "activationEvents": [ 26 | "onCommand:extension.pasteImageToQiniu" 27 | ], 28 | "main": "./extension", 29 | "contributes": { 30 | "commands": [ 31 | { 32 | "command": "extension.pasteImageToQiniu", 33 | "title": "Paste Image To Qiniu" 34 | } 35 | ], 36 | "keybindings": [ 37 | { 38 | "command": "extension.pasteImageToQiniu", 39 | "key": "ctrl+alt+v", 40 | "mac": "cmd+alt+v", 41 | "when": "editorTextFocus" 42 | } 43 | ], 44 | "configuration": { 45 | "type": "object", 46 | "title": "pasteImageToQiniu configuration", 47 | "properties": { 48 | "pasteImageToQiniu.access_key": { 49 | "type": "string", 50 | "default": "", 51 | "description": "一个有效的七牛 AccessKey 签名授权。" 52 | }, 53 | "pasteImageToQiniu.secret_key": { 54 | "type": "string", 55 | "default": "", 56 | "description": "一个有效的七牛 SecretKey 签名授权。" 57 | }, 58 | "pasteImageToQiniu.bucket": { 59 | "type": "string", 60 | "default": "", 61 | "description": "七牛图片上传空间。" 62 | }, 63 | "pasteImageToQiniu.remotePath": { 64 | "type": "string", 65 | "default": "${fileName}", 66 | "description": "七牛图片上传路径,参数化命名。" 67 | }, 68 | "pasteImageToQiniu.domain": { 69 | "type": "string", 70 | "default": "", 71 | "description": "七牛图床域名。" 72 | }, 73 | "pasteImageToQiniu.localPath": { 74 | "type": "string", 75 | "default": "./img", 76 | "description": "图片本地保存位置" 77 | } 78 | } 79 | } 80 | }, 81 | "scripts": { 82 | "postinstall": "node ./node_modules/vscode/bin/install", 83 | "test": "node ./node_modules/vscode/bin/test" 84 | }, 85 | "devDependencies": { 86 | "typescript": "^2.0.3", 87 | "vscode": "^1.0.0", 88 | "mocha": "^2.3.3", 89 | "eslint": "^3.6.0", 90 | "@types/node": "^6.0.40", 91 | "@types/mocha": "^2.2.32" 92 | }, 93 | "dependencies": { 94 | "moment": "^2.18.1", 95 | "qiniu": "^6.1.13" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /screenshot/screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huichi2028/vscode-qiniu-upload-image/f76826e91939cbd047579a6de0291ed77d0d8622/screenshot/screenshot.gif -------------------------------------------------------------------------------- /test/extension.test.js: -------------------------------------------------------------------------------- 1 | /* global suite, test */ 2 | 3 | // 4 | // Note: This example test is leveraging the Mocha test framework. 5 | // Please refer to their documentation on https://mochajs.org/ for help. 6 | // 7 | 8 | // The module 'assert' provides assertion methods from node 9 | var assert = require('assert'); 10 | 11 | // You can import and use all API from the 'vscode' module 12 | // as well as import your extension to test it 13 | var vscode = require('vscode'); 14 | var myExtension = require('../extension'); 15 | 16 | // Defines a Mocha test suite to group tests of similar kind together 17 | suite("Extension Tests", function() { 18 | 19 | // Defines a Mocha unit test 20 | test("Something 1", function() { 21 | assert.equal(-1, [1, 2, 3].indexOf(5)); 22 | assert.equal(-1, [1, 2, 3].indexOf(0)); 23 | }); 24 | }); -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | var testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.js (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; --------------------------------------------------------------------------------