From 39dded9b0b2d9aae207167b075e036f0b1eaccd4 Mon Sep 17 00:00:00 2001 From: pegasusknight Date: Sat, 4 Feb 2023 00:28:58 +0800 Subject: [PATCH 1/6] +function: right-click the image and upload it. --- src/main.ts | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/main.ts b/src/main.ts index 35fba7c..a4355d2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ import { Editor, MarkdownView, EditorPosition, + Menu } from "obsidian"; import axios from "axios"; @@ -24,6 +25,8 @@ interface ImageUploaderSettings { enableResize: boolean; } +import * as FS from 'fs'; + const DEFAULT_SETTINGS: ImageUploaderSettings = { apiEndpoint: null, uploadHeader: null, @@ -61,6 +64,10 @@ export default class ImageUploader extends Plugin { console.log("paste event is canceled"); return; } + if (ev.clipboardData.files.length==0) { + console.log("no file is pasted") + return + } let file = ev.clipboardData.files[0]; const imageType = /image.*/; @@ -128,10 +135,68 @@ export default class ImageUploader extends Plugin { this.registerEvent( this.app.workspace.on('editor-paste', this.pasteFunction) ); + + this.registerEvent( + this.app.workspace.on('editor-menu',this.addRightMenu.bind(this)) + ) + } + + addRightMenu(menu:Menu,editor:Editor):void{ + const selection = editor.getSelection(); + + if(selection){ + menu.addItem((item)=>{ + item + .setTitle("Upload Image") + .onClick((evt)=>{ + if(selection.lastIndexOf('.')==-1){ + new Notice('[Image Uploader] No correct image file chosen', 3000) + console.log('no image found') + return + } + const ext = selection.substr(selection.lastIndexOf('.')) + if(!FS.existsSync(selection)){ + new Notice('[Image Uploader] No correct image file chosen', 3000) + console.log('no image found') + return + } + //todo: 是否要应用它的resize方法? + const blob = new Blob([FS.readFileSync(selection)]) + + // upload the image + const formData = new FormData() + const uploadBody = JSON.parse(this.settings.uploadBody) + + for (const key in uploadBody) { + if (uploadBody[key] == "$FILE") { + formData.append(key, blob,`tmp${ext}`) + } + else { + formData.append(key, uploadBody[key]) + } + } + + axios.post(this.settings.apiEndpoint, formData, { + "headers": JSON.parse(this.settings.uploadHeader) + }).then(res => { + const url = objectPath.get(res.data, this.settings.imageUrlPath) + const imgMarkdownText = `${url}` + this.replaceText(editor, selection, imgMarkdownText) + }, err => { + new Notice('[Image Uploader] Upload failed', 5000) + console.log(err) + // this.replaceText(editor, pastePlaceText, ""); + + }) + + }) + }) + } } onunload(): void { this.app.workspace.off('editor-paste', this.pasteFunction); + this.app.workspace.off('editor-menu', this.addRightMenu.bind(this)); console.log("unloading Image Uploader"); } From dab06d1c71b461c38b8229a698bc8edb9ce71356 Mon Sep 17 00:00:00 2001 From: pegasusknight Date: Tue, 7 Feb 2023 15:23:25 +0800 Subject: [PATCH 2/6] =?UTF-8?q?+=20=E5=BF=BD=E7=95=A5=E7=9A=84=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 67947e1..7f1eedf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ package-lock.json yarn.lock .npmrc dist/ -.vscode/ \ No newline at end of file +.vscode/ +/*.sublime-project +/*.sublime-workspace From 65c66a3ea9bf6f181c3db3f9bc015909e2fd59d7 Mon Sep 17 00:00:00 2001 From: pegasusknight Date: Tue, 7 Feb 2023 15:29:33 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E5=88=A0=E9=99=A4gitignore=E7=9A=84?= =?UTF-8?q?=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7f1eedf..0000000 --- a/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -node_modules -.env -*-error.log -package-lock.json -yarn.lock -.npmrc -dist/ -.vscode/ -/*.sublime-project -/*.sublime-workspace From 2e1fcb5e286f9793942105fc8269f3b5fda03112 Mon Sep 17 00:00:00 2001 From: pegasusknight Date: Tue, 14 Feb 2023 14:06:56 +0800 Subject: [PATCH 4/6] modify the function: upload images in files --- src/main.ts | 505 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 295 insertions(+), 210 deletions(-) diff --git a/src/main.ts b/src/main.ts index a4355d2..07fbe60 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,210 +1,295 @@ -import { - Notice, - Plugin, - Editor, - MarkdownView, - EditorPosition, - Menu -} from "obsidian"; - -import axios from "axios"; -import objectPath from 'object-path'; -import ImageUploaderSettingTab from './settings-tab'; -import Compressor from 'compressorjs'; - -import { - PasteEventCopy, -} from './custom-events'; - -interface ImageUploaderSettings { - apiEndpoint: string; - uploadHeader: string; - uploadBody: string; - imageUrlPath: string; - maxWidth: number; - enableResize: boolean; -} - -import * as FS from 'fs'; - -const DEFAULT_SETTINGS: ImageUploaderSettings = { - apiEndpoint: null, - uploadHeader: null, - uploadBody: "{\"image\": \"$FILE\"}", - imageUrlPath: null, - maxWidth: 4096, - enableResize: false, -}; - -interface pasteFunction { - (this: HTMLElement, event: ClipboardEvent): void; -} - -export default class ImageUploader extends Plugin { - settings: ImageUploaderSettings; - pasteFunction: pasteFunction; - - private replaceText(editor: Editor, target: string, replacement: string): void { - target = target.trim() - const lines = editor.getValue().split("\n"); - for (let i = 0; i < lines.length; i++) { - const ch = lines[i].indexOf(target) - if (ch !== -1) { - const from = { line: i, ch: ch } as EditorPosition; - const to = { line: i, ch: ch + target.length } as EditorPosition; - editor.setCursor(from); - editor.replaceRange(replacement, from, to); - break; - } - } - } - - async pasteHandler(ev: ClipboardEvent, editor: Editor, mkView: MarkdownView): Promise { - if (ev.defaultPrevented) { - console.log("paste event is canceled"); - return; - } - if (ev.clipboardData.files.length==0) { - console.log("no file is pasted") - return - } - - let file = ev.clipboardData.files[0]; - const imageType = /image.*/; - if (file.type.match(imageType)) { - - ev.preventDefault(); - - // set the placeholder text - const randomString = (Math.random() * 10086).toString(36).substring(0, 8); - const pastePlaceText = `![uploading...](${randomString})\n` - editor.replaceSelection(pastePlaceText) - - // resize the image - if (this.settings.enableResize) { - const maxWidth = this.settings.maxWidth - const compressedFile = await new Promise((resolve, reject) => { - new Compressor(file, { - maxWidth: maxWidth, - success: resolve, - error: reject, - }) - }) - file = compressedFile as File - } - - // upload the image - const formData = new FormData() - const uploadBody = JSON.parse(this.settings.uploadBody) - - for (const key in uploadBody) { - if (uploadBody[key] == "$FILE") { - formData.append(key, file, file.name) - } - else { - formData.append(key, uploadBody[key]) - } - } - - axios.post(this.settings.apiEndpoint, formData, { - "headers": JSON.parse(this.settings.uploadHeader) - }).then(res => { - const url = objectPath.get(res.data, this.settings.imageUrlPath) - const imgMarkdownText = `![](${url})` - this.replaceText(editor, pastePlaceText, imgMarkdownText) - }, err => { - new Notice('[Image Uploader] Upload unsuccessfully, fall back to default paste!', 5000) - console.log(err) - this.replaceText(editor, pastePlaceText, ""); - console.log(mkView.currentMode) - mkView.currentMode.clipboardManager.handlePaste( - new PasteEventCopy(ev) - ); - }) - } - } - - async onload(): Promise { - console.log("loading Image Uploader"); - await this.loadSettings(); - // this.setupPasteHandler() - this.addSettingTab(new ImageUploaderSettingTab(this.app, this)); - - this.pasteFunction = this.pasteHandler.bind(this); - - this.registerEvent( - this.app.workspace.on('editor-paste', this.pasteFunction) - ); - - this.registerEvent( - this.app.workspace.on('editor-menu',this.addRightMenu.bind(this)) - ) - } - - addRightMenu(menu:Menu,editor:Editor):void{ - const selection = editor.getSelection(); - - if(selection){ - menu.addItem((item)=>{ - item - .setTitle("Upload Image") - .onClick((evt)=>{ - if(selection.lastIndexOf('.')==-1){ - new Notice('[Image Uploader] No correct image file chosen', 3000) - console.log('no image found') - return - } - const ext = selection.substr(selection.lastIndexOf('.')) - if(!FS.existsSync(selection)){ - new Notice('[Image Uploader] No correct image file chosen', 3000) - console.log('no image found') - return - } - //todo: 是否要应用它的resize方法? - const blob = new Blob([FS.readFileSync(selection)]) - - // upload the image - const formData = new FormData() - const uploadBody = JSON.parse(this.settings.uploadBody) - - for (const key in uploadBody) { - if (uploadBody[key] == "$FILE") { - formData.append(key, blob,`tmp${ext}`) - } - else { - formData.append(key, uploadBody[key]) - } - } - - axios.post(this.settings.apiEndpoint, formData, { - "headers": JSON.parse(this.settings.uploadHeader) - }).then(res => { - const url = objectPath.get(res.data, this.settings.imageUrlPath) - const imgMarkdownText = `${url}` - this.replaceText(editor, selection, imgMarkdownText) - }, err => { - new Notice('[Image Uploader] Upload failed', 5000) - console.log(err) - // this.replaceText(editor, pastePlaceText, ""); - - }) - - }) - }) - } - } - - onunload(): void { - this.app.workspace.off('editor-paste', this.pasteFunction); - this.app.workspace.off('editor-menu', this.addRightMenu.bind(this)); - console.log("unloading Image Uploader"); - } - - async loadSettings(): Promise { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); - } - - async saveSettings(): Promise { - await this.saveData(this.settings); - } -} +import { + Notice, + Plugin, + Editor, + MarkdownView, + EditorPosition, + Menu, + FileSystemAdapter, + addIcon +} from "obsidian"; + +import axios from "axios"; +import objectPath from 'object-path'; +import ImageUploaderSettingTab from './settings-tab'; +import Compressor from 'compressorjs'; + +import { + PasteEventCopy, +} from './custom-events'; + +interface ImageUploaderSettings { + apiEndpoint: string; + uploadHeader: string; + uploadBody: string; + imageUrlPath: string; + maxWidth: number; + enableResize: boolean; +} + +import { existsSync, readFileSync } from 'fs'; +import { basename, extname } from 'path'; + +const DEFAULT_SETTINGS: ImageUploaderSettings = { + apiEndpoint: null, + uploadHeader: null, + uploadBody: "{\"image\": \"$FILE\"}", + imageUrlPath: null, + maxWidth: 4096, + enableResize: false, +}; + +interface pasteFunction { + (this: HTMLElement, event: ClipboardEvent): void; +} + +const REGEX_LINK = /\!\[([^\[]*?|[^\]]*?)\]\((.*?)\)/g; +const REGEX_WIKI_LINK = /\!\[\[(.*?)\s*(\|\s*(.*?)\s*)?\]\]/g; + +export default class ImageUploader extends Plugin { + settings: ImageUploaderSettings; + pasteFunction: pasteFunction; + + private replaceText(editor: Editor, target: string, replacement: string): void { + target = target.trim() + const lines = editor.getValue().split("\n"); + for (let i = 0; i < lines.length; i++) { + const ch = lines[i].indexOf(target) + if (ch !== -1) { + const from = { line: i, ch: ch } as EditorPosition; + const to = { line: i, ch: ch + target.length } as EditorPosition; + editor.setCursor(from); + editor.replaceRange(replacement, from, to); + break; + } + } + } + + async pasteHandler(ev: ClipboardEvent, editor: Editor, mkView: MarkdownView): Promise { + if (ev.defaultPrevented) { + console.log("paste event is canceled"); + return; + } + if (ev.clipboardData.files.length==0) { + console.log("no file is pasted") + return + } + + let file = ev.clipboardData.files[0]; + const imageType = /image.*/; + if (file.type.match(imageType)) { + + ev.preventDefault(); + + // set the placeholder text + const randomString = (Math.random() * 10086).toString(36).substring(0, 8); + const pastePlaceText = `![uploading...](${randomString})\n` + editor.replaceSelection(pastePlaceText) + + // resize the image + if (this.settings.enableResize) { + const maxWidth = this.settings.maxWidth + const compressedFile = await new Promise((resolve, reject) => { + new Compressor(file, { + maxWidth: maxWidth, + success: resolve, + error: reject, + }) + }) + file = compressedFile as File + } + + // upload the image + try{ + const url = await this.uploadImage(editor,file,file.name) + const imgMarkdownText = `![](${url})` + this.replaceText(editor, pastePlaceText, imgMarkdownText) + }catch(e){ + new Notice('[Image Uploader] Upload unsuccessfully, fall back to default paste!', 5000) + this.replaceText(editor,pastePlaceText,'') + console.log(mkView.currentMode) + mkView.currentMode.clipboardManager.handlePaste( + new PasteEventCopy(ev) + ); + } + } + } + + menuHandler(menu:Menu,editor:Editor):void{ + const start = editor.getCursor("from").line; + const end = editor.getCursor('to').line; + menu.addItem((item)=>{ + item + .setTitle('Upload Image') + .setIcon('upload1') + .onClick(()=>{ + this.getImageAndUpload(editor,start,end) + }) + }); + menu.addItem((item)=>{ + item + .setTitle('Upload Images in File') + .setIcon('upload1') + .onClick(()=>{ + this.getImageAndUpload(editor,0,editor.lastLine()) + }) + }) + } + + async getImageAndUpload(editor:Editor,start,end): Promise{ + let success: number = 0, fail: number = 0, ignore: number = 0; + let upload_cache: Map= new Map(); + const file_path = this.app.workspace.getActiveFile().path; + const file_cache = this.app.metadataCache; + const root_path = (this.app.vault.adapter as FileSystemAdapter).getBasePath(); + + // 循环:每一行判断是否有外链存在 + for(let i:number=start; i<=end; i++){ + let value = editor.getLine(i); + const all_matches = [...value.matchAll(REGEX_LINK)].concat([...value.matchAll(REGEX_WIKI_LINK)]) + + for(const link of all_matches){ + let tag: string, path: string, wiki_mode: boolean, ext: string, name: string, url: string, upload_path: string + if(link.length == 3){ + tag = link[1] || ''; //图片名及设置 + path = decodeURI(link[2]); //图片完整链接 + wiki_mode = false; //![](),外链或内链,需要解码 + }else if(link.length == 4){ + tag = link[3] || ''; + path = link[1]; + wiki_mode = true; //![[]],内链,不能解码 + }else { + ignore++ + continue; + } + + const source = link[0]; //原图片链接完整显示的内容 + const idx = editor.getLine(i).indexOf(source); + const from = {line: i,ch: idx} as EditorPosition; + const to = {line: i,ch: idx + source.length} as EditorPosition; + + //判断path是否重复,重复的话直接替换结果,无需上传 + if(upload_cache.has(path)){ + //直接替换缓存 + url = upload_cache.get(path) + editor.replaceRange(`![${tag}](${url})`, from, to); + success++; + continue; + } + + //判断path是否是本地图片,非本地不要 + if(path.startsWith('http')){ + console.log('ignore web image: ' + path); + ignore++; + continue; + } + + //判断path是否存在,不存在不要 + const tfile = file_cache.getFirstLinkpathDest(path,file_path) + if(!tfile){ + if(!wiki_mode && existsSync(path)){ + ext = extname(path) + name = basename(path) + upload_path = path + }else{ + console.log('bad link: ' + path) + ignore++; + continue; + } + }else{ + ext = `.${tfile.extension}` + name = `${tfile.basename}${ext}` + upload_path = `${root_path}/${tfile.path}` + } + + //判断ext是否为图片后缀,不是就排除 + if(!this.isImageFile(ext)){ + console.log('not a image: ' + path) + ignore++; + continue; + } + + //上传图片 + try{ + const blob = new Blob([readFileSync(upload_path)]); + url = await this.uploadImage(editor, blob, `${name}`); + editor.replaceRange(`![${tag}](${url})`, from, to); + upload_cache.set(path,url); + success++; + }catch(e){ + console.log(e) + fail++; + } + } + } + //光标清空 + editor.setCursor(editor.getCursor('head')); + new Notice(`[Image Uploader] Upload Results:\n${success} successed\n${fail} failed\n${ignore} ignored`, 5000) + } + + isImageFile(ext: string): boolean { + return ['.png','.jpg','.jpeg','.bmp','.gif','.svg','.tiff','.webp'].includes(ext.toLowerCase()) + } + + uploadImage(editor: Editor, file: File | Blob, filename: string): Promise{ + const formData = new FormData() + const uploadBody = JSON.parse(this.settings.uploadBody) + + for (const key in uploadBody) { + if (uploadBody[key] == "$FILE") { + formData.append(key, file, filename) + } + else { + formData.append(key, uploadBody[key]) + } + } + return new Promise((resolve,reject)=>{ + axios.post(this.settings.apiEndpoint, formData, { + "headers": JSON.parse(this.settings.uploadHeader) + }).then(res => { + const url = objectPath.get(res.data, this.settings.imageUrlPath) + resolve(url); + }, err => { + reject(err); + }) + }) + } + + async onload(): Promise { + await this.loadSettings(); + // this.setupPasteHandler() + this.addSettingTab(new ImageUploaderSettingTab(this.app, this)); + + this.pasteFunction = this.pasteHandler.bind(this); + this.menuFunction = this.menuHandler.bind(this); + + this.registerEvent( + this.app.workspace.on('editor-paste', this.pasteFunction) + ); + this.registerEvent( + this.app.workspace.on('editor-menu',this.menuFunction) + ); + + addIcon( + 'upload1', + ` + + ` + ) + console.log("loading Image Uploader"); + } + + onunload(): void { + this.app.workspace.off('editor-paste', this.pasteFunction); + this.app.workspace.off('editor-menu', this.menuFunction); + console.log("unloading Image Uploader"); + } + + async loadSettings(): Promise { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + + async saveSettings(): Promise { + await this.saveData(this.settings); + } +} From 5bbc43173ba5f197b336b5a380545ef6f5c65608 Mon Sep 17 00:00:00 2001 From: pegasusknight Date: Wed, 15 Feb 2023 14:30:29 +0800 Subject: [PATCH 5/6] tweak some detail --- src/main.ts | 69 +++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/src/main.ts b/src/main.ts index 07fbe60..a99e710 100644 --- a/src/main.ts +++ b/src/main.ts @@ -43,9 +43,6 @@ interface pasteFunction { (this: HTMLElement, event: ClipboardEvent): void; } -const REGEX_LINK = /\!\[([^\[]*?|[^\]]*?)\]\((.*?)\)/g; -const REGEX_WIKI_LINK = /\!\[\[(.*?)\s*(\|\s*(.*?)\s*)?\]\]/g; - export default class ImageUploader extends Plugin { settings: ImageUploaderSettings; pasteFunction: pasteFunction; @@ -116,24 +113,27 @@ export default class ImageUploader extends Plugin { } menuHandler(menu:Menu,editor:Editor):void{ - const start = editor.getCursor("from").line; - const end = editor.getCursor('to').line; - menu.addItem((item)=>{ - item - .setTitle('Upload Image') - .setIcon('upload1') - .onClick(()=>{ - this.getImageAndUpload(editor,start,end) - }) - }); - menu.addItem((item)=>{ - item - .setTitle('Upload Images in File') - .setIcon('upload1') - .onClick(()=>{ - this.getImageAndUpload(editor,0,editor.lastLine()) - }) - }) + if(editor.somethingSelected()){ + const start = editor.getCursor("from").line; + const end = editor.getCursor('to').line; + menu.addItem((item)=>{ + item + .setTitle('Upload Image in Selection') + .setIcon('upload1') + .onClick(()=>{ + this.getImageAndUpload(editor,start,end) + }) + }); + }else{ + menu.addItem((item)=>{ + item + .setTitle('Upload Image in File') + .setIcon('upload1') + .onClick(()=>{ + this.getImageAndUpload(editor,0,editor.lastLine()) + }) + }) + } } async getImageAndUpload(editor:Editor,start,end): Promise{ @@ -142,8 +142,9 @@ export default class ImageUploader extends Plugin { const file_path = this.app.workspace.getActiveFile().path; const file_cache = this.app.metadataCache; const root_path = (this.app.vault.adapter as FileSystemAdapter).getBasePath(); - - // 循环:每一行判断是否有外链存在 + const REGEX_LINK = /\!\[([^\[]*?|[^\]]*?)\]\((.*?)\)/g; + const REGEX_WIKI_LINK = /\!\[\[(.*?)\s*(\|\s*(.*?)\s*)?\]\]/g; + for(let i:number=start; i<=end; i++){ let value = editor.getLine(i); const all_matches = [...value.matchAll(REGEX_LINK)].concat([...value.matchAll(REGEX_WIKI_LINK)]) @@ -151,24 +152,24 @@ export default class ImageUploader extends Plugin { for(const link of all_matches){ let tag: string, path: string, wiki_mode: boolean, ext: string, name: string, url: string, upload_path: string if(link.length == 3){ - tag = link[1] || ''; //图片名及设置 - path = decodeURI(link[2]); //图片完整链接 - wiki_mode = false; //![](),外链或内链,需要解码 + tag = link[1] || ''; + path = decodeURI(link[2]); + wiki_mode = false; //standard markdown link: ![]() }else if(link.length == 4){ tag = link[3] || ''; path = link[1]; - wiki_mode = true; //![[]],内链,不能解码 + wiki_mode = true; //wiki link: ![[]] }else { ignore++ continue; } - const source = link[0]; //原图片链接完整显示的内容 + const source = link[0]; //the full link text const idx = editor.getLine(i).indexOf(source); const from = {line: i,ch: idx} as EditorPosition; const to = {line: i,ch: idx + source.length} as EditorPosition; - //判断path是否重复,重复的话直接替换结果,无需上传 + //if path is cached,just read from cache, no need to upload if(upload_cache.has(path)){ //直接替换缓存 url = upload_cache.get(path) @@ -177,14 +178,14 @@ export default class ImageUploader extends Plugin { continue; } - //判断path是否是本地图片,非本地不要 + //check if path is web link if(path.startsWith('http')){ console.log('ignore web image: ' + path); ignore++; continue; } - //判断path是否存在,不存在不要 + //check if path can be accessed. check both internal and external path const tfile = file_cache.getFirstLinkpathDest(path,file_path) if(!tfile){ if(!wiki_mode && existsSync(path)){ @@ -202,14 +203,14 @@ export default class ImageUploader extends Plugin { upload_path = `${root_path}/${tfile.path}` } - //判断ext是否为图片后缀,不是就排除 + //check if the file is image file if(!this.isImageFile(ext)){ console.log('not a image: ' + path) ignore++; continue; } - //上传图片 + //upload the image try{ const blob = new Blob([readFileSync(upload_path)]); url = await this.uploadImage(editor, blob, `${name}`); @@ -222,7 +223,7 @@ export default class ImageUploader extends Plugin { } } } - //光标清空 + // clear selection editor.setCursor(editor.getCursor('head')); new Notice(`[Image Uploader] Upload Results:\n${success} successed\n${fail} failed\n${ignore} ignored`, 5000) } From 970721c846dd1d040b5dbf20df061cd905af01f8 Mon Sep 17 00:00:00 2001 From: pegasusknight Date: Thu, 22 Jun 2023 20:15:09 +0800 Subject: [PATCH 6/6] add features: upload images from right click menu --- .eslintrc.json | 40 ++++---- LICENSE | 16 +-- README.md | 140 ++++++++++++------------- manifest.json | 18 ++-- package.json | 48 ++++----- src/custom-events.ts | 34 +++--- src/settings-tab.ts | 240 +++++++++++++++++++++---------------------- tsconfig.json | 24 ++--- types.d.ts | 4 +- versions.json | 4 +- 10 files changed, 284 insertions(+), 284 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index c5624c4..22f4bfb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,20 +1,20 @@ -{ - "env": { - "browser": true, - "es2021": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 12, - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint" - ], - "rules": { - } -} +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + } +} diff --git a/LICENSE b/LICENSE index 3f28a5f..d4c5a2e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,9 @@ -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index ef5aad1..c4f289c 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,70 @@ -# Obsidian Image Uploader - -![](https://i.loli.net/2021/07/16/fxWBeLAESNc6tK9.gif) - -This plugin could resize(optional) and upload the image in your clipboard to any image hosting automatically when pasting. - -## Getting started - -### Settings - -1. Api Endpoint: the Endpoint of the image hosting api. -2. Upload Header: the header of upload request in **json** format. -3. Upload Body: the body of upload request in **json** format. Don't change it unless you know what you are doing. -4. Image Url Path: the path to the image url in http response. -5. Enable Resize: whether resizing images before uploading. -6. Max Width: images that wider than this will be resized resized by the natural aspect ratio. - -### Examples - -Take Imgur as an example. The upload request is something like this: - -```shell -curl --location --request POST 'https://api.imgur.com/3/image' \ ---header 'Authorization: Client-ID {{clientId}}' \ ---form 'image="R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"' -``` - -So, `Api Endpoint` should be `https://api.imgur.com/3/image` and `Upload Header` should be `{"Authorization": "Client-ID {{clientId}}"}`. - -The response of the upload request is: -```json -{ - "data": { - "id": "orunSTu", - "title": null, - "description": null, - "datetime": 1495556889, - "type": "image/gif", - "animated": false, - "width": 1, - "height": 1, - "size": 42, - "views": 0, - "bandwidth": 0, - "vote": null, - "favorite": false, - "nsfw": null, - "section": null, - "account_url": null, - "account_id": 0, - "is_ad": false, - "in_most_viral": false, - "tags": [], - "ad_type": 0, - "ad_url": "", - "in_gallery": false, - "deletehash": "x70po4w7BVvSUzZ", - "name": "", - "link": "http://i.imgur.com/orunSTu.gif" - }, - "success": true, - "status": 200 -} -``` - -All you need is the image url `http://i.imgur.com/orunSTu.gif`, so `Image Url Path` should be `data.link`. - -## Thanks -1. [obsidian-imgur-plugin](https://github.com/gavvvr/obsidian-imgur-plugin) -2. [create-obsidian-plugin](https://www.npmjs.com/package/create-obsidian-plugin) +# Obsidian Image Uploader + +![](https://i.loli.net/2021/07/16/fxWBeLAESNc6tK9.gif) + +This plugin could resize(optional) and upload the image in your clipboard to any image hosting automatically when pasting. + +## Getting started + +### Settings + +1. Api Endpoint: the Endpoint of the image hosting api. +2. Upload Header: the header of upload request in **json** format. +3. Upload Body: the body of upload request in **json** format. Don't change it unless you know what you are doing. +4. Image Url Path: the path to the image url in http response. +5. Enable Resize: whether resizing images before uploading. +6. Max Width: images that wider than this will be resized resized by the natural aspect ratio. + +### Examples + +Take Imgur as an example. The upload request is something like this: + +```shell +curl --location --request POST 'https://api.imgur.com/3/image' \ +--header 'Authorization: Client-ID {{clientId}}' \ +--form 'image="R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"' +``` + +So, `Api Endpoint` should be `https://api.imgur.com/3/image` and `Upload Header` should be `{"Authorization": "Client-ID {{clientId}}"}`. + +The response of the upload request is: +```json +{ + "data": { + "id": "orunSTu", + "title": null, + "description": null, + "datetime": 1495556889, + "type": "image/gif", + "animated": false, + "width": 1, + "height": 1, + "size": 42, + "views": 0, + "bandwidth": 0, + "vote": null, + "favorite": false, + "nsfw": null, + "section": null, + "account_url": null, + "account_id": 0, + "is_ad": false, + "in_most_viral": false, + "tags": [], + "ad_type": 0, + "ad_url": "", + "in_gallery": false, + "deletehash": "x70po4w7BVvSUzZ", + "name": "", + "link": "http://i.imgur.com/orunSTu.gif" + }, + "success": true, + "status": 200 +} +``` + +All you need is the image url `http://i.imgur.com/orunSTu.gif`, so `Image Url Path` should be `data.link`. + +## Thanks +1. [obsidian-imgur-plugin](https://github.com/gavvvr/obsidian-imgur-plugin) +2. [create-obsidian-plugin](https://www.npmjs.com/package/create-obsidian-plugin) diff --git a/manifest.json b/manifest.json index 051791e..2c1e5cc 100644 --- a/manifest.json +++ b/manifest.json @@ -1,9 +1,9 @@ -{ - "id": "obsidian-image-uploader", - "name": "Image Uploader", - "description": "This plugin uploads the image in your clipboard to any image hosting automatically when pasting.", - "author": "Creling", - "isDesktopOnly": true, - "minAppVersion": "0.15", - "version": "0.2" -} +{ + "id": "obsidian-image-uploader", + "name": "Image Uploader", + "description": "This plugin uploads the image in your clipboard to any image hosting automatically when pasting.", + "author": "Creling", + "isDesktopOnly": true, + "minAppVersion": "0.15", + "version": "0.2" +} diff --git a/package.json b/package.json index cceeca6..06dada5 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,24 @@ -{ - "name": "obsidian-image-uploader", - "version": "0.0.1", - "description": "This plugin uploads images in your clipboard to any image bed you like.", - "main": "lib/main.js", - "license": "MIT", - "scripts": { - "build": "obsidian-plugin build src/main.ts", - "dev": "obsidian-plugin dev src/main.ts" - }, - "devDependencies": { - "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/parser": "^4.33.0", - "eslint": "^7.32.0", - "obsidian": "^0.15.9", - "obsidian-plugin-cli": "^0.4.5", - "typescript": "^4.7.4" - }, - "dependencies": { - "axios": "^0.21.4", - "compressorjs": "^1.1.1", - "object-path": "^0.11.8" - } -} +{ + "name": "obsidian-image-uploader", + "version": "0.0.1", + "description": "This plugin uploads images in your clipboard to any image bed you like.", + "main": "lib/main.js", + "license": "MIT", + "scripts": { + "build": "obsidian-plugin build src/main.ts", + "dev": "obsidian-plugin dev src/main.ts" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^4.33.0", + "@typescript-eslint/parser": "^4.33.0", + "eslint": "^7.32.0", + "obsidian": "^0.15.9", + "obsidian-plugin-cli": "^0.4.5", + "typescript": "^4.7.4" + }, + "dependencies": { + "axios": "^0.21.4", + "compressorjs": "^1.1.1", + "object-path": "^0.11.8" + } +} diff --git a/src/custom-events.ts b/src/custom-events.ts index 1d8cf18..a495a46 100644 --- a/src/custom-events.ts +++ b/src/custom-events.ts @@ -1,18 +1,18 @@ -/* - * @Author: Creling - * @Date: 2022-08-26 09:52:01 - * @LastEditors: Creling - * @LastEditTime: 2022-08-26 09:52:45 - * @Description: file content - */ - -export class PasteEventCopy extends ClipboardEvent { - constructor(originalEvent: ClipboardEvent) { - const { files } = originalEvent.clipboardData; - const dt = new DataTransfer(); - for (let i = 0; i < files.length; i += 1) { - dt.items.add(files.item(i)); - } - super("paste", { clipboardData: dt }); - } +/* + * @Author: Creling + * @Date: 2022-08-26 09:52:01 + * @LastEditors: Creling + * @LastEditTime: 2022-08-26 09:52:45 + * @Description: file content + */ + +export class PasteEventCopy extends ClipboardEvent { + constructor(originalEvent: ClipboardEvent) { + const { files } = originalEvent.clipboardData; + const dt = new DataTransfer(); + for (let i = 0; i < files.length; i += 1) { + dt.items.add(files.item(i)); + } + super("paste", { clipboardData: dt }); + } } \ No newline at end of file diff --git a/src/settings-tab.ts b/src/settings-tab.ts index 56730f6..8745ba1 100644 --- a/src/settings-tab.ts +++ b/src/settings-tab.ts @@ -1,121 +1,121 @@ -/* - * @Author: Creling - * @Date: 2021-07-15 23:54:03 - * @LastEditors: Creling - * @LastEditTime: 2021-08-04 22:52:34 - * @Description: file content - */ -import { - App, - PluginSettingTab, - Setting, -} from 'obsidian'; - -import ImageUploader from './main' - -export default class ImageUploaderSettingTab extends PluginSettingTab { - plugin: ImageUploader; - constructor(app: App, plugin: ImageUploader) { - super(app, plugin); - this.plugin = plugin; - } - display(): void { - const { containerEl } = this; - - containerEl.empty(); - containerEl.createEl("h3", { text: "Image Hosting Setting" }); - - new Setting(containerEl) - .setName("Api Endpoint") - .setDesc("The endpoint of the image hosting api.") - .addText((text) => { - text - .setPlaceholder("") - .setValue(this.plugin.settings.apiEndpoint) - .onChange(async (value) => { - this.plugin.settings.apiEndpoint = value; - await this.plugin.saveSettings(); - }) - } - ); - - new Setting(containerEl) - .setName("Upload Header") - .setDesc("The header of upload request in json format.") - .addTextArea((text) => { - text - .setPlaceholder("") - .setValue(this.plugin.settings.uploadHeader) - .onChange(async (value) => { - try { - this.plugin.settings.uploadHeader = value; - await this.plugin.saveSettings(); - } - catch (e) { - console.log(e) - } - }) - text.inputEl.rows = 5 - text.inputEl.cols = 40 - }); - - new Setting(containerEl) - .setName("Upload Body") - .setDesc("The body of upload request in json format. Do NOT change it unless you know what you are doing.") - .addTextArea((text) => { - text - .setPlaceholder("") - .setValue(this.plugin.settings.uploadBody) - .onChange(async (value) => { - try { - this.plugin.settings.uploadBody = value; - await this.plugin.saveSettings(); - } - catch (e) { - console.log(e) - } - }) - text.inputEl.rows = 5 - text.inputEl.cols = 40 - }); - - new Setting(containerEl) - .setName("Image Url Path") - .setDesc("The path to the image url in http response.") - .addText((text) => { - text - .setPlaceholder("") - .setValue(this.plugin.settings.imageUrlPath) - .onChange(async (value) => { - this.plugin.settings.imageUrlPath = value; - await this.plugin.saveSettings(); - }) - }); - new Setting(containerEl) - .setName("Enable Resize") - .setDesc("Resize the image before uploading") - .addToggle((toggle) => { - toggle - .setValue(this.plugin.settings.enableResize) - .onChange(async (value) => { - this.plugin.settings.enableResize = value; - this.display(); - }) - }) - - if (this.plugin.settings.enableResize) { - new Setting(containerEl) - .setName("Max Width") - .setDesc("The image wider than this will be resized by the natural aspect ratio") - .addText((text) => { - text - .setPlaceholder("") - .setValue(this.plugin.settings.maxWidth.toString()) - .onChange(async (value) => { - this.plugin.settings.maxWidth = parseInt(value); - await this.plugin.saveSettings(); - }) - }); - } - } +/* + * @Author: Creling + * @Date: 2021-07-15 23:54:03 + * @LastEditors: Creling + * @LastEditTime: 2021-08-04 22:52:34 + * @Description: file content + */ +import { + App, + PluginSettingTab, + Setting, +} from 'obsidian'; + +import ImageUploader from './main' + +export default class ImageUploaderSettingTab extends PluginSettingTab { + plugin: ImageUploader; + constructor(app: App, plugin: ImageUploader) { + super(app, plugin); + this.plugin = plugin; + } + display(): void { + const { containerEl } = this; + + containerEl.empty(); + containerEl.createEl("h3", { text: "Image Hosting Setting" }); + + new Setting(containerEl) + .setName("Api Endpoint") + .setDesc("The endpoint of the image hosting api.") + .addText((text) => { + text + .setPlaceholder("") + .setValue(this.plugin.settings.apiEndpoint) + .onChange(async (value) => { + this.plugin.settings.apiEndpoint = value; + await this.plugin.saveSettings(); + }) + } + ); + + new Setting(containerEl) + .setName("Upload Header") + .setDesc("The header of upload request in json format.") + .addTextArea((text) => { + text + .setPlaceholder("") + .setValue(this.plugin.settings.uploadHeader) + .onChange(async (value) => { + try { + this.plugin.settings.uploadHeader = value; + await this.plugin.saveSettings(); + } + catch (e) { + console.log(e) + } + }) + text.inputEl.rows = 5 + text.inputEl.cols = 40 + }); + + new Setting(containerEl) + .setName("Upload Body") + .setDesc("The body of upload request in json format. Do NOT change it unless you know what you are doing.") + .addTextArea((text) => { + text + .setPlaceholder("") + .setValue(this.plugin.settings.uploadBody) + .onChange(async (value) => { + try { + this.plugin.settings.uploadBody = value; + await this.plugin.saveSettings(); + } + catch (e) { + console.log(e) + } + }) + text.inputEl.rows = 5 + text.inputEl.cols = 40 + }); + + new Setting(containerEl) + .setName("Image Url Path") + .setDesc("The path to the image url in http response.") + .addText((text) => { + text + .setPlaceholder("") + .setValue(this.plugin.settings.imageUrlPath) + .onChange(async (value) => { + this.plugin.settings.imageUrlPath = value; + await this.plugin.saveSettings(); + }) + }); + new Setting(containerEl) + .setName("Enable Resize") + .setDesc("Resize the image before uploading") + .addToggle((toggle) => { + toggle + .setValue(this.plugin.settings.enableResize) + .onChange(async (value) => { + this.plugin.settings.enableResize = value; + this.display(); + }) + }) + + if (this.plugin.settings.enableResize) { + new Setting(containerEl) + .setName("Max Width") + .setDesc("The image wider than this will be resized by the natural aspect ratio") + .addText((text) => { + text + .setPlaceholder("") + .setValue(this.plugin.settings.maxWidth.toString()) + .onChange(async (value) => { + this.plugin.settings.maxWidth = parseInt(value); + await this.plugin.saveSettings(); + }) + }); + } + } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8261fac..9beac3d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,12 @@ -{ - "compilerOptions": { - "target": "es2019", - "module": "commonjs", - "emitDeclarationOnly": true, - "esModuleInterop": true, - "skipLibCheck": true, - "moduleResolution": "node", - "forceConsistentCasingInFileNames": true - }, - "include": ["src/**/*.ts", "types.d.ts"] -} +{ + "compilerOptions": { + "target": "es2019", + "module": "commonjs", + "emitDeclarationOnly": true, + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts", "types.d.ts"] +} diff --git a/types.d.ts b/types.d.ts index 0f3ecf6..edae0f1 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,2 +1,2 @@ -// Empty declaration to allow for css imports -declare module "*.css" {} +// Empty declaration to allow for css imports +declare module "*.css" {} diff --git a/versions.json b/versions.json index b94c0e4..867751b 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ -{ - "0.2": "0.13.19" +{ + "0.2": "0.13.19" } \ No newline at end of file