import * as vscode from 'vscode'; import { spawn, exec } from 'child_process'; import { tmpdir } from 'os'; import * as fs from 'fs'; import * as path from 'path'; import * as packages from '../../package.json'; import * as crypto from 'crypto'; import Uploads from './uploads'; import i18n from '../i18n/index'; import * as TurndownService from 'turndown'; import { gfm } from 'turndown-plugin-gfm'; let pkg = packages as any; let locale = i18n(); function getUpload(config: Config) : Upload | null { switch(config.base.uploadMethod) { case 'Local': return new Uploads.Local(config); case 'Coding': return new Uploads.Coding(config); case 'GitHub': return new Uploads.GitHub(config); case 'Imgur': return new Uploads.Imgur(config); case 'SM.MS': return new Uploads.SM_MS(config); case 'Data URL': return new Uploads.DataUrl(config); case 'Qiniu': return new Uploads.Qiniu(config); case 'DIY': return new Uploads.Define(config); case '本地': return new Uploads.Local(config); case '七牛': return new Uploads.Qiniu(config); case '自定义': return new Uploads.Define(config); case '自定義': return new Uploads.Define(config); case 'Cloudinary': return new Uploads.Cloudinary(config); case 'Cloudflare': return new Uploads.Cloudflare(config); } return null; } function showProgress(message: string) { let show = true; function stopProgress() { show = false; } vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: message, cancellable: false }, (progress, token) => { return new Promise(resolve => { let timer = setInterval(() => { if (show) { return; } clearInterval(timer); resolve(show); }, 100); }); }); return stopProgress; } function editorEdit(selection: vscode.Selection | vscode.Position | undefined | null, text: string) :Promise<boolean> { return new Promise((resolve, reject) => { vscode.window.activeTextEditor?.edit(editBuilder => { if(selection) { editBuilder.replace(selection, text); } }).then(resolve); }); } function insertToEnd(text: string) :Promise<boolean> { return new Promise((resolve, reject) => { let linenumber = vscode.window.activeTextEditor?.document.lineCount || 1; let pos = vscode.window.activeTextEditor?.document.lineAt(linenumber - 1).range.end || new vscode.Position(0, 0); vscode.window.activeTextEditor?.edit(editBuilder => { editBuilder.insert(pos, text); }).then(resolve); }); } function getSelections() : vscode.Selection[] | null{ let editor = vscode.window.activeTextEditor; if (!editor) { return null; // No open text editor } let selections = editor.selections; return selections; } function formatCode(filePath: string, selection: string, maxWidth: number, codeType: string): string { if (codeType === "Markdown") { return `![${selection}](${filePath}${maxWidth > 0 ? ` =${maxWidth}x` : ''}) \n`; } return `<img alt="${selection}" src="${filePath}" ${maxWidth > 0 ? `width="${maxWidth}" ` : ''}/> \n`; } async function formatName(format: string, filePath: string, isPaste: boolean): Promise<string> { let saveName = format; let variables = [ 'filename', 'mdname', 'path', 'hash', 'timestramp', 'timestamp', 'YY', 'MM', 'DD', 'HH', 'hh', 'mm', 'ss', 'mss', 'rand,(\\d+)', 'prompt' ]; for (let i = 0; i < variables.length; i++) { let reg = new RegExp(`\\$\\{${variables[i]}\\}`, 'g'); let mat = format.match(reg); if (!mat) { continue; } switch(variables[i]) { case 'filename': { let data = !isPaste ? path.basename(filePath, path.extname(filePath)) : (path.basename((await prompt(locale['named_paste'], path.basename(filePath, '.png'))) || '') || ''); saveName = saveName.replace(reg, data); break; } case 'mdname': { let data = path.basename(getCurrentFilePath(), path.extname(getCurrentFilePath())); saveName = saveName.replace(reg, data); break; } case 'path': { let data = path.dirname(getCurrentFilePath()).replace(getCurrentRoot(), '').slice(1); saveName = saveName.replace(reg, data); break; } case 'hash': { let data = hash(fs.readFileSync(filePath)); saveName = saveName.replace(reg, data); break; } case 'timestramp': case 'timestamp': { let data = new Date().getTime().toString(); saveName = saveName.replace(reg, data); break; } case 'YY': { let data = new Date().getFullYear().toString(); saveName = saveName.replace(reg, data); break; } case 'MM': { let data = (new Date().getMonth() + 1).toString().padStart(2, '0'); saveName = saveName.replace(reg, data); break; } case 'DD': { let data = (new Date().getDate()).toString().padStart(2, '0'); saveName = saveName.replace(reg, data); break; } case 'hh': { let hours = new Date().getHours(); let data = (hours > 12 ? hours - 12 : hours).toString().padStart(2, '0'); saveName = saveName.replace(reg, data); break; } case 'HH': { let data = new Date().getHours().toString().padStart(2, '0'); saveName = saveName.replace(reg, data); break; } case 'mm': { let data = new Date().getMinutes().toString().padStart(2, '0'); saveName = saveName.replace(reg, data); break; } case 'ss': { let data = new Date().getSeconds().toString().padStart(2, '0'); saveName = saveName.replace(reg, data); break; } case 'mss': { let data = new Date().getMilliseconds().toString().padStart(3, '0'); saveName = saveName.replace(reg, data); break; } case 'rand,(\\d+)': { for(let j = 0; j < mat.length; j++) { let numberMat = mat[j].match(/\d+/); if (!numberMat) { continue; } let n = parseInt(numberMat[0]); let data = parseInt((Math.random() * n).toString()).toString(); saveName = saveName.replace(mat[j], data); } break; } case 'prompt': { let promptInput = await vscode.window.showInputBox({ prompt: `${locale['prompt_name_component']}${saveName}` }); // TODO: make sure that promptInput is a filename-safe string (i.e., does not include special characters, etc.) if (promptInput) saveName = saveName.replace(reg, promptInput); else throw Error(locale['user_did_not_answer_prompt']); break; } } } return saveName; } function mkdirs(dirname: string) { //console.debug(dirname); if (fs.existsSync(dirname)) { return true; } else { if (mkdirs(path.dirname(dirname))) { fs.mkdirSync(dirname); return true; } } } function html2Markdown(data: string) : string { let turndownService = new TurndownService({ codeBlockStyle: 'fenced', headingStyle: 'atx', }); turndownService.use(gfm); return turndownService.turndown(data); } function getConfig() { let keys: string[] = Object.keys(pkg.contributes.configuration.properties); let values: Config = {}; function toVal(str: string, val: string|undefined, cfg: Config) : string | Config { let keys = str.split('.'); if (keys.length === 1) { cfg[keys[0]] = val; } else { cfg[keys[0]] = toVal(keys.slice(1).join('.'), val, cfg[keys[0]] || {}); } return cfg; } keys.forEach(k => toVal(k.split('.').slice(1).join('.'), vscode.workspace.getConfiguration().get(k), values)); return values; } function getPasteImage(imagePath: string) : Promise<string[]>{ return new Promise((resolve, reject) => { if (!imagePath) { return; } let platform = process.platform; if (platform === 'win32') { // Windows const scriptPath = path.join(__dirname, '..', '..' , 'asserts/pc.ps1'); let command = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"; let powershellExisted = fs.existsSync(command); let output = ''; if (!powershellExisted) { command = "powershell"; } const powershell = spawn(command, [ '-noprofile', '-noninteractive', '-nologo', '-sta', '-executionpolicy', 'unrestricted', '-windowstyle', 'hidden', '-file', scriptPath, imagePath ]); // the powershell can't auto exit in windows 7 . let timer = setTimeout(() => powershell.kill(), 2000); powershell.on('error', (e: any) => { if (e.code === 'ENOENT') { vscode.window.showErrorMessage(locale['powershell_not_found']); } else { vscode.window.showErrorMessage(e); } }); powershell.on('exit', function (code, signal) { // console.debug('exit', code, signal); }); powershell.stdout.on('data', (data) => { data.toString().split('\n').forEach(d => output += (d.indexOf('Active code page:') < 0 ? d + '\n' : '')); clearTimeout(timer); timer = setTimeout(() => powershell.kill(), 2000); }); powershell.on('close', (code) => { resolve(output.trim().split('\n').map(i => i.trim())); }); } else if (platform === 'darwin') { // Mac let scriptPath = path.join(__dirname, '..', '..' , 'asserts/mac.applescript'); let ascript = spawn('osascript', [scriptPath, imagePath]); ascript.on('error', (e: any) => { vscode.window.showErrorMessage(e); }); ascript.on('exit', (code, signal) => { // console.debug('exit', code, signal); }); ascript.stdout.on('data', (data) => { resolve(data.toString().trim().split('\n')); }); } else { // Linux let scriptPath = path.join(__dirname, '..', '..' , 'asserts/linux.sh'); let ascript = spawn('sh', [scriptPath, imagePath]); ascript.on('error', (e: any) => { vscode.window.showErrorMessage(e); }); ascript.on('exit', (code, signal) => { // console.debug('exit',code,signal); }); ascript.stdout.on('data', (data) => { let result = data.toString().trim(); if (result === "no xclip") { vscode.window.showInformationMessage(locale['install_xclip']); return; } let match = decodeURI(result).trim().match(/((\/[^\/]+)+\/[^\/]*?\.(jpg|jpeg|gif|bmp|png))/g); resolve(match || []); }); } }); } function getRichText() : Promise<string>{ return new Promise((resolve, reject) => { let platform = process.platform; if (platform === 'win32') { // Windows const scriptPath = path.join(__dirname, '..', '..' , 'asserts/rtf.ps1'); let command = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"; let powershellExisted = fs.existsSync(command); let output = ''; if (!powershellExisted) { command = "powershell"; } const powershell = spawn(command, [ '-noprofile', '-noninteractive', '-nologo', '-sta', '-executionpolicy', 'unrestricted', '-windowstyle', 'hidden', '-file', scriptPath, ]); // the powershell can't auto exit in windows 7 . let timer = setTimeout(() => powershell.kill(), 2000); let buffers:any = []; let size = 0; powershell.on('error', (e: any) => { if (e.code === 'ENOENT') { vscode.window.showErrorMessage(locale['powershell_not_found']); } else { vscode.window.showErrorMessage(e); } }); powershell.on('exit', function (code, signal) { // console.debug('exit', code, signal); }); powershell.stdout.on('data', (data) => { buffers.push(data); size += data.length; clearTimeout(timer); timer = setTimeout(() => powershell.kill(), 2000); }); powershell.on('close', (code) => { Buffer.concat(buffers, size).toString() .split('\n').forEach(d => output += (d.indexOf('Active code page:') < 0 ? d : '')); resolve(output.replace('</html>', '').replace('</body>', '').split('<body>').slice(-1)[0].trim()); }); } else if (platform === 'darwin') { // Mac vscode.window.showInformationMessage('Not support in macos yet.'); resolve(''); } else { // Linux let scriptPath = path.join(__dirname, '..', '..' , 'asserts/rtf.sh'); let result = ''; let ascript = spawn('sh', [scriptPath]); ascript.on('error', (e: any) => { vscode.window.showErrorMessage(e); }); ascript.on('exit', (code, signal) => { if (result === "no xclip") { vscode.window.showInformationMessage(locale['install_xclip']); return; } resolve(result); }); ascript.stdout.on('data', (data) => { result += data.toString().trim(); }); } }); } function getCurrentRoot() : string { const editor = vscode.window.activeTextEditor; if (!editor || !vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length < 1) { return ''; } const resource = editor.document.uri; if (resource.scheme == 'vscode-notebook-cell') { let filePath = resource.fsPath; let root = vscode.workspace.workspaceFolders.find(f => filePath.indexOf(f.uri.fsPath) >= 0); if (root) return root.uri.fsPath; else return ''; } if (resource.scheme !== 'file') { return ''; } const folder = vscode.workspace.getWorkspaceFolder(resource); if (!folder) { return ''; } return folder.uri.fsPath; } function getCurrentFilePath() : string { const editor = vscode.window.activeTextEditor; if (!editor || !vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length < 1) { return ''; } return editor.document.uri.fsPath; } function getTmpFolder() { let savePath = path.join(tmpdir(), pkg.name); if (!fs.existsSync(savePath)) { fs.mkdirSync(savePath); } return savePath; } function sleep (time: number) { return new Promise((resolve) => setTimeout(resolve, time)); } function confirm(message: string, options: string[]) : Promise<string|undefined> { return new Promise((resolve, reject) => { return vscode.window.showInformationMessage(message, ...options).then(resolve); }); } function prompt(message: string, defaultVal: string = '') : Promise<string|undefined> { return new Promise((resolve, reject) => { return vscode.window.showInputBox({ value: defaultVal, prompt: message }).then(resolve); }); } function hash(buffer:Buffer): string { let sha256 = crypto.createHash('sha256'); let hash = sha256.update(buffer).digest('hex'); return hash; } function getOpenCmd(): string { let cmd = 'start'; if (process.platform === 'win32') { cmd = 'start'; } else if (process.platform === 'linux') { cmd = 'xdg-open'; } else if (process.platform === 'darwin') { cmd = 'open'; } return cmd; } function noticeComment(context: vscode.ExtensionContext) { let notice = context.globalState.get('notice'); let usetimes: number = context.globalState.get('usetimes') || 0; if (!notice && usetimes > 100) { confirm(locale['like.extension'], [locale['like.ok'], locale['like.no'], locale['like.later']]) .then((option) => { switch(option) { case locale['like.ok']: exec(`${getOpenCmd()} https://marketplace.visualstudio.com/items?itemName=hancel.markdown-image`); context.globalState.update('notice', true); break; case locale['like.no']: context.globalState.update('notice', true); break; case locale['like.later']: usetimes = 50; context.globalState.update('usetimes', usetimes); context.globalState.update('notice', false); break; } }) .catch(e => console.debug(e)); } else if(!notice) { context.globalState.update('usetimes', ++usetimes); } } export default { getUpload, showProgress, editorEdit, insertToEnd, formatCode, formatName, mkdirs, html2Markdown, getConfig, getSelections, getPasteImage, getRichText, getCurrentRoot, getCurrentFilePath, getTmpFolder, noticeComment, sleep, confirm, prompt, hash, }; export { locale };