diff --git a/.vscode/settings.json b/.vscode/settings.json index 67740e9..826250c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,7 +24,7 @@ "editor.formatOnSave": false }, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "eslint.packageManager": "yarn", "typescript.tsdk": "node_modules\\typescript\\lib", diff --git a/.vscodeignore b/.vscodeignore index b9247f8..74dfdbc 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -6,3 +6,4 @@ !LICENSE !package.json !package.nls.json +!package.nls.zh-cn.json diff --git a/README.md b/README.md index e52f742..ebc9dca 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,16 @@ ## Features +
+Uploading all local images in the current file and get those links replaced +replace.gif +
+ +
+Generating uploaded version of the document while keeping the original hierarchy(when the option is enabled) +generate.gif +
+
Uploading an image from clipboard clipboard.gif diff --git a/assets/TestFolder/A/AA/testAA.md b/assets/TestFolder/A/AA/testAA.md new file mode 100644 index 0000000..9a435ed --- /dev/null +++ b/assets/TestFolder/A/AA/testAA.md @@ -0,0 +1,15 @@ +This + +![TEST](../../../test.png "picgo logo") + +is + + + +a + + +![[../../../test.png]] + + +test file \ No newline at end of file diff --git a/assets/TestFolder/A/AB/testAB.md b/assets/TestFolder/A/AB/testAB.md new file mode 100644 index 0000000..9a435ed --- /dev/null +++ b/assets/TestFolder/A/AB/testAB.md @@ -0,0 +1,15 @@ +This + +![TEST](../../../test.png "picgo logo") + +is + + + +a + + +![[../../../test.png]] + + +test file \ No newline at end of file diff --git a/assets/TestFolder/A/testA.md b/assets/TestFolder/A/testA.md new file mode 100644 index 0000000..751b7b8 --- /dev/null +++ b/assets/TestFolder/A/testA.md @@ -0,0 +1,15 @@ +This + +![TEST](../../test.png "picgo logo") + +is + + + +a + + +![[../../test.png]] + + +test file \ No newline at end of file diff --git a/assets/TestFolder/B/testB.md b/assets/TestFolder/B/testB.md new file mode 100644 index 0000000..751b7b8 --- /dev/null +++ b/assets/TestFolder/B/testB.md @@ -0,0 +1,15 @@ +This + +![TEST](../../test.png "picgo logo") + +is + + + +a + + +![[../../test.png]] + + +test file \ No newline at end of file diff --git a/assets/test.md b/assets/test.md deleted file mode 100644 index e69de29..0000000 diff --git a/assets/testRoot.md b/assets/testRoot.md new file mode 100644 index 0000000..3d585ca --- /dev/null +++ b/assets/testRoot.md @@ -0,0 +1,16 @@ +This + +![TEST](test.png "picgo logo") +![TEST]( "picgo logo") + +is + + + +a + + +![[test.png]] + + +test file \ No newline at end of file diff --git a/assets/uploadedVersion/TestFolder/A/AA/testAA.md b/assets/uploadedVersion/TestFolder/A/AA/testAA.md new file mode 100644 index 0000000..9a435ed --- /dev/null +++ b/assets/uploadedVersion/TestFolder/A/AA/testAA.md @@ -0,0 +1,15 @@ +This + +![TEST](../../../test.png "picgo logo") + +is + + + +a + + +![[../../../test.png]] + + +test file \ No newline at end of file diff --git a/assets/uploadedVersion/TestFolder/A/AB/testAB.md b/assets/uploadedVersion/TestFolder/A/AB/testAB.md new file mode 100644 index 0000000..9a435ed --- /dev/null +++ b/assets/uploadedVersion/TestFolder/A/AB/testAB.md @@ -0,0 +1,15 @@ +This + +![TEST](../../../test.png "picgo logo") + +is + + + +a + + +![[../../../test.png]] + + +test file \ No newline at end of file diff --git a/assets/uploadedVersion/TestFolder/A/testA.md b/assets/uploadedVersion/TestFolder/A/testA.md new file mode 100644 index 0000000..751b7b8 --- /dev/null +++ b/assets/uploadedVersion/TestFolder/A/testA.md @@ -0,0 +1,15 @@ +This + +![TEST](../../test.png "picgo logo") + +is + + + +a + + +![[../../test.png]] + + +test file \ No newline at end of file diff --git a/assets/uploadedVersion/TestFolder/B/testB.md b/assets/uploadedVersion/TestFolder/B/testB.md new file mode 100644 index 0000000..751b7b8 --- /dev/null +++ b/assets/uploadedVersion/TestFolder/B/testB.md @@ -0,0 +1,15 @@ +This + +![TEST](../../test.png "picgo logo") + +is + + + +a + + +![[../../test.png]] + + +test file \ No newline at end of file diff --git a/assets/uploadedVersion/testRoot.md b/assets/uploadedVersion/testRoot.md new file mode 100644 index 0000000..c8fd79f --- /dev/null +++ b/assets/uploadedVersion/testRoot.md @@ -0,0 +1,15 @@ +This + +![TEST](test.png "picgo logo") + +is + + + +a + + +![[test.png]] + + +test file \ No newline at end of file diff --git a/esbuild.mjs b/esbuild.mjs index a74d832..9f2b0c9 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -53,7 +53,9 @@ const resultHandler = async (result) => { const outdir = './dist' // clean old built files -fse.rmdirSync(outdir, { recursive: true }) +if (fse.existsSync(outdir)) { + fse.rmdirSync(outdir, { recursive: true }) +} /** @type {import('esbuild').BuildOptions} */ const commonOptions = { diff --git a/package.json b/package.json index 21a9120..c0a5364 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "image upload", "picture upload" ], - "version": "2.1.6", + "version": "2.2.1", "publisher": "Spades", "engines": { "vscode": "^1.60.0" @@ -29,6 +29,8 @@ "onCommand:picgo.uploadImageFromInputBox", "onCommand:picgo.webviewDemo", "onCommand:picgo.webviewPicGoControlPanel", + "onCommand:picgo.generateUploadedImageVersionMarkdown", + "onCommand:picgo.uploadAndReplaceAllImageLinksInTheCurrentFile", "workspaceContains:vs-picgo-auto-launch.txt" ], "main": "./dist/extension", @@ -58,6 +60,16 @@ "command": "picgo.webviewPicGoControlPanel", "title": "%command.webview.picGoControlPanel.title%", "category": "PicGo" + }, + { + "command": "picgo.generateUploadedImageVersionMarkdown", + "title": "%command.generate.uploadedImageVersionMarkdown.title%", + "category": "PicGo" + }, + { + "command": "picgo.uploadAndReplaceAllImageLinksInTheCurrentFile", + "title": "%command.uploadedImageVersionMarkdownInTheSameFile.title%", + "category": "PicGo" } ], "keybindings": [ @@ -75,12 +87,27 @@ "command": "picgo.uploadImageFromInputBox", "key": "ctrl+alt+O", "mac": "cmd+alt+O" + }, + { + "command": "picgo.generateUploadedImageVersionMarkdown", + "key": "ctrl+alt+G", + "mac": "ctrl+alt+G" + }, + { + "command": "picgo.uploadAndReplaceAllImageLinksInTheCurrentFile", + "key": "ctrl+alt+R", + "mac": "ctrl+alt+R" } ], "configuration": { "type": "object", "title": "%config.title%", "properties": { + "picgo.useUploadVersionFolder": { + "type": "boolean", + "default": false, + "description": "Whether to save uploaded version files in a separate 'uploadVersion' folder" + }, "picgo.configPath": { "type": "string", "markdownDescription": "%config.configPath.description%", @@ -310,9 +337,22 @@ "default": "" } } - } + }, + "localizations": [{ + "languageId": "zh-cn", + "languageName": "Chinese (Simplified)", + "localizedLanguageName": "中文(简体)", + "translations": [ + { + "id": "vs-picgo", + "path": "./package.nls.zh-cn.json" + } + ] + }] }, - "extensionKind": ["ui"], + "extensionKind": [ + "ui" + ], "scripts": { "vscode:prepublish": "yarn && yarn build:prod", "build": "node esbuild.mjs", @@ -426,5 +466,6 @@ "react-use": "17.3.1", "redux": "4.1.1" }, - "license": "MIT" + "license": "MIT", + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/package.nls.json b/package.nls.json index f0eb142..7767538 100644 --- a/package.nls.json +++ b/package.nls.json @@ -22,5 +22,8 @@ "config.picBed.github.description": "GitHub configuration.", "config.picBed.github.repo.description": "`Username/Repo`. For example, PicGo/Images", "config.picBed.aliyun.description": "Aliyun OSS configuration.", - "config.picBed.imgur.description": "Imgur picBed configuration." + "config.picBed.imgur.description": "Imgur picBed configuration.", + "config.useUploadVersionFolder.description": "Whether to save uploaded version files in a separate 'uploadVersion' folder", + "command.generate.uploadedImageVersionMarkdown.title": "Generate uploaded image version markdown", + "command.uploadedImageVersionMarkdownInTheSameFile.title": "Upload and replace all image links in the current file" } diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json new file mode 100644 index 0000000..21b9eb7 --- /dev/null +++ b/package.nls.zh-cn.json @@ -0,0 +1,29 @@ +{ + "ext.displayName": "PicGo", + "ext.description": "基于 PicGo 的 VSCode 图片上传插件。支持多种图片格式(png, jpg, gif等)和多个图床服务(sm.ms、七牛云、imgur等)!", + "command.upload.clipboard.title": "从剪贴板上传", + "command.upload.explorer.title": "从资源管理器上传", + "command.upload.inputBox.title": "从输入框上传", + "command.generate.uploadedImageVersionMarkdown.title": "生成已上传图片的版本markdown", + "command.uploadedImageVersionMarkdownInTheSameFile.title": "上传并替换同一文件中的所有图片链接", + "command.webview.demo.title": "打开演示页面", + "command.webview.picGoControlPanel.title": "打开 PicGo 控制面板", + "config.title": "PicGo", + "config.configPath.description": "PicGo-Core 配置文件的路径。如果未指定,PicGo 将使用 `#picgo.picBed#`。更多配置文件相关信息请参考 [PicGo-Core 文档](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) 和 [PicGo 文档](https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E5%9B%BE%E5%BA%8A%E5%8C%BA)", + "config.dataPath.description": "数据文件的路径,包含所有已上传图片的信息。如果未指定,PicGo 将使用 `your_home_dir/vs-picgo-data.json`", + "config.customUploadName.description": "自定义上传图片的名称,图片将在上传前重命名。\n- `${fileName}`: 原始图片的名称,不包含扩展名。\n **注意:如果在上传前选择了文本,选中的文本将作为上传图片的 `fileName`。**\n- `${extName}`: 原始图片的扩展名。\n- `${mdFileName}`: 当前编辑的 markdown 文件名。\n- `${date}`: YY-MM-DD 格式的日期。\n- `${dateTime}`: YY-MM-DD-hh-mm-ss 格式的日期时间。\n\n示例:\n- `${fileName}-${date}${extName}` -> `picName-2016-07-25.jpg`\n- `${mdFileName}-${dateTime}${extName}` -> `markdownName-2017-04-12-22-28-10.jpg`", + "config.customOutputFormat.description": "自定义上传图片的输出格式。\n- `${url}`: 上传后的图片URL。\n- `${uploadedName}`: 上传后的图片名称(不含扩展名),参见 `#picgo.customUploadName#`,注意即使在 `#picgo.customUploadName#` 中使用了 `${extName}`,输出中仍然不会包含扩展名。\n\n示例:\n- `![${uploadedName}](${url})` -> `![picName-2016-07-25](https://example.com/xxx.jpg)`\n- `\"${uploadedName}\"` -> `\"picName-2016-07-25\"`", + "config.picBed.uploader": "请参考 [`picBed.uploader`](https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#picbed-uploader)", + "config.picBed.current": "请参考 [`picBed.current`](https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#picbed-current)", + "config.picBed.proxy": "请求的代理设置,更多详情请参考 [`picBed.proxy`](https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#picbed-proxy)", + "config.picBed.smms.description": "SM.MS 图床配置", + "config.picBed.weibo.description": "微博图床配置", + "config.picBed.qiniu.description": "七牛云图床配置", + "config.picBed.upyun.description": "又拍云图床配置", + "config.picBed.tcyun.description": "腾讯云 COS 图床配置", + "config.picBed.github.description": "GitHub 配置", + "config.picBed.github.repo.description": "`用户名/仓库名`。例如: PicGo/Images", + "config.picBed.aliyun.description": "阿里云 OSS 配置", + "config.picBed.imgur.description": "Imgur 图床配置", + "config.useUploadVersionFolder.description": "是否将上传版本文件保存在单独的 'uploadVersion' 文件夹中" +} diff --git a/src/extension.ts b/src/extension.ts index b6688a3..ca10f6c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -19,6 +19,20 @@ export async function activate(context: vscode.ExtensionContext) { 'picgo.uploadImageFromInputBox', async () => await CommandManager.commandManager.uploadImageFromInputBox() ), + vscode.commands.registerCommand( + 'picgo.generateUploadedImageVersionMarkdown', + async () => + await CommandManager.commandManager.generateUploadedImageVersionMarkdown( + false + ) + ), + vscode.commands.registerCommand( + 'picgo.uploadAndReplaceAllImageLinksInTheCurrentFile', + async () => + await CommandManager.commandManager.generateUploadedImageVersionMarkdown( + true + ) + ), vscode.commands.registerCommand('picgo.webviewDemo', () => panelManager.createOrShowWebviewPanel('Demo') diff --git a/src/vscode/CommandManager.ts b/src/vscode/CommandManager.ts index cbe2165..21c43be 100644 --- a/src/vscode/CommandManager.ts +++ b/src/vscode/CommandManager.ts @@ -4,7 +4,7 @@ import * as vscode from 'vscode' import { Editor } from './Editor' import { PicgoAPI } from './PicgoAPI' import { PicgoAddon } from './PicgoAddon' -import { showError } from './utils' +import { showError, showInfo, showWarning } from './utils' export class CommandManager { static commandManager: CommandManager = new CommandManager() @@ -28,6 +28,228 @@ export class CommandManager { return outputString } + async silentUploadCommand(input?: string[]) { + const pluginName = 'vspicgo' + PicgoAPI.picgoAPI.setCurrentPluginName(pluginName) + const [id, plugin] = PicgoAddon.picgoAddon.beforeUploadPlugin() + PicgoAPI.picgoAPI.helper.beforeUploadPlugins.register(id, plugin) + + const output = await PicgoAPI.picgoAPI.upload(input) + PicgoAPI.picgoAPI.helper.beforeUploadPlugins.unregister(pluginName) + + // error has been handled in picgoAPI.upload + if (!output) return + + return PicgoAddon.picgoAddon.outputToURLs(output) + } + + async generateUploadedImageVersionMarkdown(sameFile: boolean = false) { + // Get current editor + const editor = Editor.editor + if (!editor) { + showError('No active editor') + return + } + + // Get document text and path + const document = editor.document + + let newEditor: vscode.TextEditor + let newDocument: vscode.TextDocument + + if (sameFile) { + // Use existing document and editor + newEditor = editor + newDocument = document + } else { + // Original file handling logic + const originalFilePath = document.uri.fsPath + const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri) + if (!workspaceFolder) { + showError('No workspace folder found') + return + } + + const originalDir = path.dirname(originalFilePath) + const originalFileName = path.basename( + originalFilePath, + path.extname(originalFilePath) + ) + const fileExt = path.extname(originalFilePath) + + // Check setting for upload version folder + const useUploadFolder = vscode.workspace + .getConfiguration('picgo') + .get('useUploadVersionFolder', false) + + // Determine target directory and create if needed + let targetDir + if (useUploadFolder) { + // Get relative path from workspace root to the original file's directory + const relativeDir = path.relative( + workspaceFolder.uri.fsPath, + originalDir + ) + // Create target path in uploadedVersion folder + targetDir = path.join( + workspaceFolder.uri.fsPath, + 'uploadedVersion', + relativeDir + ) + // Create all necessary directories + fs.mkdirSync(targetDir, { recursive: true }) + } else { + targetDir = originalDir + } + + // Create new file path + const newFileName = `${originalFileName}${ + useUploadFolder ? '' : '_uploadedVersion' + }${fileExt}` + const newFilePath = path.join(targetDir, newFileName) + + // Copy original file content + fs.copyFileSync(originalFilePath, newFilePath) + + // Open the new file + newDocument = await vscode.workspace.openTextDocument(newFilePath) + newEditor = await vscode.window.showTextDocument(newDocument) + } + + // Get text from new document + const text = newDocument.getText() + + // Match various markdown image syntax: + // 1. Standard markdown: ![alt](url) or ![alt](url "title") + // 2. HTML: + // 3. Obsidian: ![[filename]] + const mdImageRegex = /!\[([^\]]*)\]\((?:<([^>]+)>|([^)\s]+))\s*(.*)?\)|]+src=["']([^"']+)["'][^>]*>|!\[\[([^\]]+)\]\]/g + let match + let hasLocalImage = false + const replacements: Array<{ original: string; replacement: string }> = [] + + // Add a cache to store path -> url mappings + const pathToUrlCache: Map = new Map() + + // Iterate through all matches + while ((match = mdImageRegex.exec(text)) !== null) { + // Get image URL - Extract according to different formats + let imgUrl = '' + + if (match[2]) { + // Standard markdown + imgUrl = match[2] + } else if (match[3]) { + // Standard markdown + imgUrl = match[3] + } else if (match[4]) { + // HTML format + imgUrl = match[4] + } else if (match[5]) { + // Obsidian format + imgUrl = match[5] + } + + if (!imgUrl) continue + + // Check whether it's a local path or a URL + if (!imgUrl.startsWith('http') && !imgUrl.startsWith('data:')) { + hasLocalImage = true + + // Handle path + let absolutePath = imgUrl + if (!path.isAbsolute(imgUrl)) { + if (match[5]) { + // Obsidian syntax + const attachmentFolders = [ + path.join(path.dirname(document.uri.fsPath), 'attachments'), + path.join(path.dirname(document.uri.fsPath), 'assets'), + path.dirname(document.uri.fsPath) + ] + + // search for the image in the attachment folders (possible when in Obsidian) + for (const folder of attachmentFolders) { + const testPath = path.join(folder, imgUrl) + if (fs.existsSync(testPath)) { + absolutePath = testPath + break + } + } + } else { + absolutePath = path.resolve( + path.dirname(document.uri.fsPath), + imgUrl + ) + } + } + + if (fs.existsSync(absolutePath)) { + let newUrl: string + + // Check if we've already uploaded this image + if (pathToUrlCache.has(absolutePath)) { + newUrl = pathToUrlCache.get(absolutePath) ?? '' + } else { + // Upload image and cache the result + const newUrls = await this.silentUploadCommand([absolutePath]) + if (newUrls && newUrls.length > 0) { + newUrl = newUrls[0] + pathToUrlCache.set(absolutePath, newUrl) + } else { + continue // Skip if upload failed + } + } + + let replacement = newUrl + if (match[2]) { + // Markdown format + const originalStr = match[0] + replacement = originalStr.replace(match[2], newUrl) + } else if (match[3]) { + // Markdown format + const originalStr = match[0] + replacement = originalStr.replace(match[3], newUrl) + } else if (match[4]) { + // HTML format + const originalStr = match[0] + replacement = originalStr.replace(match[4], newUrl) + } else if (match[5]) { + // Obsidian format + const originalStr = match[0] + replacement = originalStr.replace(match[5], newUrl) + } + replacements.push({ + original: match[0], + replacement: replacement + }) + } else { + showError(`Local image not found: ${absolutePath}`) + } + } + } + + if (!hasLocalImage) { + showWarning('No local images found in current document') + return + } + + // Replace all local image links in the new file + newEditor.edit((editBuilder) => { + for (let i = 0; i < replacements.length; i++) { + const { original, replacement } = replacements[i] + const fileText = newDocument.getText() + const startPos = newDocument.positionAt(fileText.indexOf(original)) + const endPos = newDocument.positionAt( + fileText.indexOf(original) + original.length + ) + editBuilder.replace(new vscode.Range(startPos, endPos), replacement) + showInfo( + `Replaced original image link ${original} with uploaded image link ${replacement}.` + ) + } + }) + } + async uploadImageFromClipboard() { this.uploadCommand() } diff --git a/src/vscode/PicgoAddon.ts b/src/vscode/PicgoAddon.ts index 8871a85..81f0f84 100644 --- a/src/vscode/PicgoAddon.ts +++ b/src/vscode/PicgoAddon.ts @@ -133,4 +133,8 @@ export class PicgoAddon { ) .join('\n') } + + outputToURLs(output: IImgInfo[]) { + return output.map((imgInfo) => imgInfo.imgUrl ?? '') + } }