From 23fcd59035a258edc8a72befbdbfdf6a6eb97c8d Mon Sep 17 00:00:00 2001 From: appleex <771992522@qq.com> Date: Sun, 13 Oct 2024 09:55:12 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 9 +- esbuild.config.mjs | 18 +- main.ts | 460 ------------------------------- manifest.json | 6 +- package.json | 1 + src/config.ts | 24 ++ src/lskyUpload.ts | 45 +++ src/main.ts | 253 +++++++++++++++++ src/modal.ts | 23 ++ src/settings-tab.ts | 193 +++++++++++++ src/type.ts | 29 ++ src/uploader.ts | 15 + helper.ts => src/utils/helper.ts | 12 +- utils.ts => src/utils/utils.ts | 0 tsconfig.json | 3 +- 16 files changed, 621 insertions(+), 471 deletions(-) delete mode 100644 main.ts create mode 100644 src/config.ts create mode 100644 src/lskyUpload.ts create mode 100644 src/main.ts create mode 100644 src/modal.ts create mode 100644 src/settings-tab.ts create mode 100644 src/type.ts create mode 100644 src/uploader.ts rename helper.ts => src/utils/helper.ts (94%) rename utils.ts => src/utils/utils.ts (100%) diff --git a/.gitignore b/.gitignore index fa22dbe..de9c0b6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ package-lock.json # Don't include the compiled main.js file in the repo. # They should be uploaded to GitHub releases instead. main.js +dist # Exclude sourcemaps *.map diff --git a/README.md b/README.md index 578b2e2..58f6a6a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Obsidian Sample Plugin +# Obsidian Upload Plugin -This is a sample plugin for Obsidian (https://obsidian.md). +This is a Upload plugin for Obsidian (https://obsidian.md). This project uses TypeScript to provide type checking and documentation. The repo depends on the latest plugin API (obsidian.d.ts) in TypeScript Definition format, which contains TSDoc comments describing what it does. @@ -94,3 +94,8 @@ If you have multiple URLs, you can also do: ## API Documentation See https://github.com/obsidianmd/obsidian-api + +## References + +- [obsidian-image-upload-toolkit](https://github.com/addozhang/obsidian-image-upload-toolkit) +- diff --git a/esbuild.config.mjs b/esbuild.config.mjs index a5de8b8..5f6ee0f 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -1,6 +1,7 @@ import esbuild from "esbuild"; import process from "process"; import builtins from "builtin-modules"; +import copyStaticFiles from "esbuild-copy-static-files"; const banner = `/* @@ -15,7 +16,7 @@ const context = await esbuild.context({ banner: { js: banner, }, - entryPoints: ["main.ts"], + entryPoints: ["./src/main.ts"], bundle: true, external: [ "obsidian", @@ -33,12 +34,25 @@ const context = await esbuild.context({ "@lezer/lr", ...builtins], format: "cjs", + // watch: !prod, target: "es2018", logLevel: "info", sourcemap: prod ? false : "inline", treeShaking: true, - outfile: "main.js", + // outdir: './dist', + // outfile: "main.js", + outfile: prod ? './dist/main.js' : './main.js', minify: prod, + plugins: [ + prod && copyStaticFiles({ + src: './manifest.json', + dest: './dist/manifest.json' + }), + prod && copyStaticFiles({ + src: './styles.css', + dest: './dist/styles.css' + }) + ] }); if (prod) { diff --git a/main.ts b/main.ts deleted file mode 100644 index 49d0d47..0000000 --- a/main.ts +++ /dev/null @@ -1,460 +0,0 @@ -import { - App, - Editor, - MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, - - Menu, - MenuItem, - TFile, - FileSystemAdapter, - addIcon, -} from 'obsidian'; - -import { readFile } from "fs"; -import axios from "axios"; -import objectPath from 'object-path'; -import { resolve, relative, join, parse, posix, basename, dirname } from "path"; - -import Helper from "./helper"; -import { isAssetTypeAnImage, bufferToArrayBuffer } from "./utils"; - -// Remember to rename these classes and interfaces! - -interface MyPluginSettings { - mySetting: string; - uploader: string; - - mode: string; - apiURL: string; - apiReqHeader: string; - apiReqBody: string; - - imgUrlPath: string; - imgUrlPrefix: string; - - lskySetting: ILskySettings; -} - -interface ILskySettings { - endpoint: string; - reqHeader: string; - reqBody: string; - imgUrlPath: string; - imgUrlPrefix: string; -} -interface IImage { - path: string; - name: string; - source: string; -} - -// 默认设置 -const DEFAULT_SETTINGS: MyPluginSettings = { - mySetting: 'default', - uploader: 'lsky', - - mode: 'lsky', - apiURL: '', - apiReqHeader: '', - apiReqBody: "{\"image\": \"$FILE\"}", - - imgUrlPath: '', - imgUrlPrefix: '', - - lskySetting: { - endpoint: '', - reqHeader: '', - reqBody: '', - imgUrlPath: '', - imgUrlPrefix: '', - }, -} - -export default class MyPlugin extends Plugin { - settings: MyPluginSettings; - - // 自定义 - helper: Helper; - editor: Editor; - - async onload() { - await this.loadSettings(); - - // T - this.helper = new Helper(this.app); - - // This creates an icon in the left ribbon. - const ribbonIconEl = this.addRibbonIcon('dice', 'Upload Plugin', (evt: MouseEvent) => { - // Called when the user clicks the icon. - new Notice('This is a notice!'); - }); - // Perform additional things with the ribbon - ribbonIconEl.addClass('my-plugin-ribbon-class'); - - // This adds a status bar item to the bottom of the app. Does not work on mobile apps. - const statusBarItemEl = this.addStatusBarItem(); - statusBarItemEl.setText('Status Bar Text'); - - // This adds a simple command that can be triggered anywhere - this.addCommand({ - id: 'open-sample-modal-simple', - name: 'Open sample modal (simple)', - callback: () => { - new SampleModal(this.app).open(); - } - }); - // This adds an editor command that can perform some operation on the current editor instance - this.addCommand({ - id: 'sample-editor-command', - name: 'Sample editor command', - editorCallback: (editor: Editor, view: MarkdownView) => { - console.log(editor.getSelection()); - editor.replaceSelection('Sample Editor Command'); - } - }); - // This adds a complex command that can check whether the current state of the app allows execution of the command - this.addCommand({ - id: 'open-sample-modal-complex', - name: 'Open sample modal (complex)', - checkCallback: (checking: boolean) => { - // Conditions to check - const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView); - if (markdownView) { - // If checking is true, we're simply "checking" if the command can be run. - // If checking is false, then we want to actually perform the operation. - if (!checking) { - new SampleModal(this.app).open(); - } - - // This command will only show up in Command Palette when the check function returns true - return true; - } - } - }); - - // 注册事件,添加右键菜单项 - this.registerFileMenu(); - - // This adds a settings tab so the user can configure various aspects of the plugin - this.addSettingTab(new SampleSettingTab(this.app, this)); - - // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin) - // Using this function will automatically remove the event listener when this plugin is disabled. - this.registerDomEvent(document, 'click', (evt: MouseEvent) => { - console.log('click', evt); - }); - - // When registering intervals, this function will automatically clear the interval when the plugin is disabled. - this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000)); - } - - onunload() { - - } - - async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); - } - - async saveSettings() { - await this.saveData(this.settings); - } - - // 自定义方法 - // 上传操作 - async uploadFile(image: File): Promise { - return new Promise((resolve, reject) => { - const formData = new FormData(); - const uploadBody = JSON.parse(this.settings.apiReqBody); - - for (const key in uploadBody) { - if (uploadBody[key] == "$FILE") { - formData.append(key, image, image.name) - } - else { - formData.append(key, uploadBody[key]) - } - } - - axios.post(this.settings.apiURL, formData, { - "headers": JSON.parse(this.settings.apiReqHeader) - }).then(res => { - const url = [this.settings.imgUrlPrefix, objectPath.get(res.data, this.settings.imgUrlPath)].join('') - - resolve(url) - }, err => { - reject(err) - }) - }) - } - - async uploadMenuFile(file: TFile) { - let content = this.helper.getValue(); - - const basePath = ( - this.app.vault.adapter as FileSystemAdapter - ).getBasePath(); - - let imageList: IImage[] = []; - const fileArray = this.helper.getAllFiles(); - - for (const match of fileArray) { - const imageName = match.name; - const encodedUri = match.path; - - const fileName = basename(decodeURI(encodedUri)); - - if (file && file.name === fileName) { - const abstractImageFile = join(basePath, file.path); - - if (isAssetTypeAnImage(abstractImageFile)) { - imageList.push({ - path: abstractImageFile, - name: imageName, - source: match.source, - }); - } - } - } - - if (imageList.length === 0) { - new Notice("没有找到文件"); - return; - } - - const fileList = imageList.map(item => item.path); - const files = []; - - for (let i = 0; i < fileList.length; i++) { - const file = fileList[i]; - const buffer: Buffer = await new Promise((resolve, reject) => { - readFile(file, (err, data) => { - if (err) { - reject(err); - } - resolve(data); - }); - }); - const arrayBuffer = bufferToArrayBuffer(buffer); - files.push(new File([arrayBuffer], file)); - } - - if (files.length === 0) { - new Notice("没有找到文件"); - return; - } - - // 上传 - this.uploadFile(files[0]) - .then(res => { - imageList.map(item => { - content = content.replaceAll( - item.source, - `![](${res})` - ); - }); - - this.helper.setValue(content); - - new Notice("上传成功"); - }) - .catch(err => { - new Notice(`上传失败: ${err}`); - }); - } - - // 添加右键菜单项 - registerFileMenu() { - this.registerEvent( - this.app.workspace.on( - "file-menu", - (menu: Menu, file: TFile, source: string, leaf) => { - if (source === "canvas-menu") return false; - if (!isAssetTypeAnImage(file.path)) return false; - - menu.addItem((item: MenuItem) => { - item - .setTitle("上传文件") - .setIcon("upload") - .onClick(() => { - if (!(file instanceof TFile)) { - return false; - } - new Notice('准备上传'); - this.uploadMenuFile(file); - }); - }); - } - ) - ); - } -} - -class SampleModal extends Modal { - constructor(app: App) { - super(app); - } - - onOpen() { - const {contentEl} = this; - contentEl.setText('Woah!'); - } - - onClose() { - const {contentEl} = this; - contentEl.empty(); - } -} - -class SampleSettingTab extends PluginSettingTab { - plugin: MyPlugin; - - constructor(app: App, plugin: MyPlugin) { - super(app, plugin); - this.plugin = plugin; - } - - display(): void { - const { containerEl } = this; - containerEl.empty(); - - let defaultDiv: HTMLDivElement, modeDiv: HTMLDivElement, otherDiv: HTMLDivElement; - - /*大标题*/ - containerEl.createEl("h1", {text: "上传设置"}); - - /*默认配置*/ - defaultDiv = containerEl.createDiv(); - // 模式 - new Setting(defaultDiv) - .setName('模式') - .setDesc('选择一种模式') - .addDropdown(e => { - e.addOption("lsky", "Lsky") - e.addOption("halo", "Halo") - e.setValue(this.plugin.settings.mode) - e.onChange(async value => { - this.plugin.settings.mode = value; - // await this.plugin.saveSettings(); - - await this.selectModeSettings(value, modeDiv); - }) - }) - - /*模式配置*/ - modeDiv = containerEl.createDiv() - this.plugin.settings.mode === 'lsky' && this.selectModeSettings(this.plugin.settings.mode, modeDiv); - - /*其它配置*/ - } - - // 自定义方法 - // 切换模式,不同的模式对应不同的配置 - private async selectModeSettings(value: any, parentEl: HTMLDivElement) { - parentEl && parentEl.empty(); - - switch (value) { - case 'lsky': - this.drawLskySettings(parentEl); - break; - default: - break; - } - } - // Lskypro 设置 - private drawLskySettings(parentEL: HTMLDivElement) { - // Api URL - new Setting(parentEL) - .setName("API 地址") - .setDesc("请求地址") - .addText(text => { - text - .setPlaceholder("") - .setValue(this.plugin.settings.apiURL) - .onChange(async (value) => { - try { - this.plugin.settings.apiURL = value; - await this.plugin.saveSettings(); - } - catch (e) { - console.log(e) - } - }) - }) - - // Api request header - new Setting(parentEL) - .setName("POST Header") - .setDesc("请求头,json 格式") - .addTextArea((text) => { - text.setPlaceholder("输入合法的请求头") - .setValue(this.plugin.settings.apiReqHeader) - .onChange(async (value) => { - try { - this.plugin.settings.apiReqHeader = value; - await this.plugin.saveSettings(); - } - catch (e) { - console.log(e) - } - }) - text.inputEl.rows = 5 - text.inputEl.cols = 40 - }) - - // Api request body - new Setting(parentEL) - .setName("POST Body") - .setDesc("请求体,json 格式") - .addTextArea((text) => { - text.setPlaceholder("输入合法的请求体") - .setValue(this.plugin.settings.apiReqBody) - .onChange(async (value) => { - try { - this.plugin.settings.apiReqBody = value; - await this.plugin.saveSettings(); - } - catch (e) { - console.log(e) - } - }) - text.inputEl.rows = 5 - text.inputEl.cols = 40 - }) - new Setting(parentEL) - .setName("图片 URL 路径") - .setDesc("返回json数据中的图片URL字段的路径,如:data/pathname") - .addText(text => { - text - .setPlaceholder("") - .setValue(this.plugin.settings.imgUrlPath) - .onChange(async (value) => { - try { - this.plugin.settings.imgUrlPath = value; - await this.plugin.saveSettings(); - } - catch (e) { - console.log(e) - } - }) - }) - - new Setting(parentEL) - .setName("图片 URL 前缀") - .setDesc("可选,当填入时,URL = 前缀 + 图片的路径值") - .addText(text => { - text - .setPlaceholder("") - .setValue(this.plugin.settings.imgUrlPrefix) - .onChange(async (value) => { - try { - this.plugin.settings.imgUrlPrefix = value; - await this.plugin.saveSettings(); - } - catch (e) { - console.log(e) - } - }) - }) - } -} diff --git a/manifest.json b/manifest.json index b0df049..e7da68b 100644 --- a/manifest.json +++ b/manifest.json @@ -4,8 +4,8 @@ "version": "0.1.0", "minAppVersion": "0.15.0", "description": "Demonstrates some of the capabilities of the Obsidian API.", - "author": "Appleex", - "authorUrl": "https://obsidian.md", - "fundingUrl": "https://obsidian.md/pricing", + "author": "appleex", + "authorUrl": "https://github.com/Appleec/e-obsidian-upload-plugin", + "fundingUrl": "https://github.com/Appleec/e-obsidian-upload-plugin", "isDesktopOnly": false } diff --git a/package.json b/package.json index 2475dd8..4ef6d19 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@typescript-eslint/parser": "5.29.0", "builtin-modules": "3.3.0", "esbuild": "0.17.3", + "esbuild-copy-static-files": "^0.1.0", "obsidian": "latest", "tslib": "2.4.0", "typescript": "4.7.4" diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..a21cfc6 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,24 @@ +const config = { + modes: [ + { + text: 'Lskypro', + value: 'lsky', + }, + { + text: 'Github', + value: 'github', + }, + ], + reqHeader: { + "Authorization": "Bearer Jq7mTKdjN7BxMjLoG8Jj8eaGGV2GuurwumOGFCBm", + "Accept": "application/json", + "Content-Type": "multipart/form-data" + }, + reqBody: { + "file": "$FILE", + "strategy_id": 2 + } +} + +export const { modes } = config; +export default config; diff --git a/src/lskyUpload.ts b/src/lskyUpload.ts new file mode 100644 index 0000000..fcc9c47 --- /dev/null +++ b/src/lskyUpload.ts @@ -0,0 +1,45 @@ +// Defaults +import { IUploadPluginSettings } from "./type"; +import axios from "axios"; +import objectPath from "object-path"; + +class LskyUpload { + private readonly settings: IUploadPluginSettings; + + constructor(settings: IUploadPluginSettings) { + this.settings = settings; + } + + async upload(file: File, fullPath?: string): Promise { + return new Promise((resolve, reject) => { + const formData = new FormData(); + const uploadBody = JSON.parse(this.settings.lskySetting.apiReqBody); + + 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.lskySetting.apiURL, formData, { + "headers": JSON.parse(this.settings.lskySetting.apiReqHeader) + }).then(res => { + const url = [this.settings.lskySetting.imgUrlPrefix, objectPath.get(res.data, this.settings.lskySetting.imgUrlPath)].join('') + + resolve(url) + }, err => { + reject(err) + }) + }) + } + + // + drawSettingsTab(parentEL: HTMLDivElement) { + + } +} + +export default LskyUpload; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..48b3ed8 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,253 @@ +import { + App, + Editor, + MarkdownView, + Notice, + Plugin, + Menu, + MenuItem, + TFile, + FileSystemAdapter, + addIcon, +} from 'obsidian'; + +import { readFile } from 'fs'; +import { join, basename } from 'path'; + +// Utils +import Helper from "./utils/helper"; +import { isAssetTypeAnImage, bufferToArrayBuffer } from "./utils/utils"; +import uploader from "./uploader"; + +// Class +import UploadSettingTab from './settings-tab'; +import UploadModal from './modal'; + +// Types +import { IUploadPluginSettings, IImage, IUploader } from './type'; + +// Remember to rename these classes and interfaces! + +// 默认选项 +const DEFAULT_SETTINGS: IUploadPluginSettings = { + mode: 'lsky', + apiURL: '', + apiReqHeader: '', + apiReqBody: "{\"file\": \"$FILE\"}", + + imgUrlPath: '', + imgUrlPrefix: '', + + lskySetting: { + apiURL: "", + apiReqHeader: "", + apiReqBody: "{\"file\": \"$FILE\"}", + imgUrlPath: "", + imgUrlPrefix: "", + } +} + +class UploadPlugin extends Plugin { + settings: IUploadPluginSettings; + + // 自定义 + helper: Helper; + editor: Editor; + + // Uploader + uploader: IUploader; + + async onload() { + // 加载设置 + await this.loadSettings(); + + // 赋值 + this.helper = new Helper(this.app); + this.uploader = uploader(this.settings); + + // This creates an icon in the left ribbon. + const ribbonIconEl = this.addRibbonIcon('dice', 'Upload Plugin', (evt: MouseEvent) => { + // Called when the user clicks the icon. + new Notice('This is a notice!'); + }); + // Perform additional things with the ribbon + ribbonIconEl.addClass('my-plugin-ribbon-class'); + + // This adds a status bar item to the bottom of the app. Does not work on mobile apps. + const statusBarItemEl = this.addStatusBarItem(); + statusBarItemEl.setText('Status Bar Text'); + + // This adds a simple command that can be triggered anywhere + this.addCommand({ + id: 'open-sample-modal-simple', + name: 'Open sample modal (simple)', + callback: () => { + new UploadModal(this.app).open(); + } + }); + // This adds an editor command that can perform some operation on the current editor instance + this.addCommand({ + id: 'sample-editor-command', + name: 'Sample editor command', + editorCallback: (editor: Editor, view: MarkdownView) => { + console.log(editor.getSelection()); + editor.replaceSelection('Sample Editor Command'); + } + }); + // This adds a complex command that can check whether the current state of the app allows execution of the command + this.addCommand({ + id: 'open-sample-modal-complex', + name: 'Open sample modal (complex)', + checkCallback: (checking: boolean) => { + // Conditions to check + const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView); + if (markdownView) { + // If checking is true, we're simply "checking" if the command can be run. + // If checking is false, then we want to actually perform the operation. + if (!checking) { + new UploadModal(this.app).open(); + } + + // This command will only show up in Command Palette when the check function returns true + return true; + } + } + }); + + // 注册事件,添加右键菜单项 + this.registerMenuEvent(); + + // This adds a settings tab so the user can configure various aspects of the plugin + this.addSettingTab(new UploadSettingTab(this.app, this)); + + // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin) + // Using this function will automatically remove the event listener when this plugin is disabled. + this.registerDomEvent(document, 'click', (evt: MouseEvent) => { + console.log('click', evt); + }); + + // When registering intervals, this function will automatically clear the interval when the plugin is disabled. + this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000)); + } + + onunload() {} + + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + + async saveSettings() { + await this.saveData(this.settings); + } + + // 自定义方法 + // 右键菜单上传文件 + async menuUploadFile(file: TFile) { + new Notice('准备上传'); + + let content = this.helper.getValue(); + + const basePath = ( + this.app.vault.adapter as FileSystemAdapter + ).getBasePath(); + + let imageList: IImage[] = []; + const fileArray = this.helper.getAllFiles(); + + for (const match of fileArray) { + const imageName = match.name; + const encodedUri = match.path; + + const fileName = basename(decodeURI(encodedUri)); + + if (file && file.name === fileName) { + const abstractImageFile = join(basePath, file.path); + + if (isAssetTypeAnImage(abstractImageFile)) { + imageList.push({ + path: abstractImageFile, + name: imageName, + source: match.source, + }); + } + } + } + + if (imageList.length === 0) { + new Notice("没有找到文件"); + return; + } + + const fileList = imageList.map(item => item.path); + const files = []; + + for (let i = 0; i < fileList.length; i++) { + const file = fileList[i]; + const buffer: Buffer = await new Promise((resolve, reject) => { + readFile(file, (err, data) => { + if (err) { + reject(err); + } + resolve(data); + }); + }); + const arrayBuffer = bufferToArrayBuffer(buffer); + files.push(new File([arrayBuffer], file)); + } + + if (files.length === 0) { + new Notice("没有找到文件"); + return; + } + + if (!this.uploader) { + new Notice("上传配置加载失败,请检查设置"); + return; + } + + // 上传 + this.uploader.upload(files[0]) + .then(res => { + imageList.map(item => { + content = content.replaceAll( + item.source, + `![](${res})` + ); + }); + + this.helper.setValue(content); + + new Notice("上传成功"); + }) + .catch(err => { + new Notice(`上传失败: ${err}`); + }); + } + + // 注册右键菜单事件 + registerMenuEvent() { + this.registerEvent( + this.app.workspace.on( + "file-menu", + (menu: Menu, file: TFile, source: string, leaf) => { + if (source === "canvas-menu") return false; + if (!isAssetTypeAnImage(file.path)) return false; + + menu.addItem((item: MenuItem) => { + item + .setTitle("上传文件") + .setIcon("upload") + .onClick(() => { + if (!(file instanceof TFile)) { + return false; + } + this.menuUploadFile(file); + }); + }); + } + ) + ); + } +} + +export default UploadPlugin; diff --git a/src/modal.ts b/src/modal.ts new file mode 100644 index 0000000..e17c0c2 --- /dev/null +++ b/src/modal.ts @@ -0,0 +1,23 @@ +import { + App, + Modal, + Notice, +} from 'obsidian'; + +class UploadModal extends Modal { + constructor(app: App) { + super(app); + } + + onOpen() { + const {contentEl} = this; + contentEl.setText('Woah!'); + } + + onClose() { + const {contentEl} = this; + contentEl.empty(); + } +} + +export default UploadModal; diff --git a/src/settings-tab.ts b/src/settings-tab.ts new file mode 100644 index 0000000..46d83af --- /dev/null +++ b/src/settings-tab.ts @@ -0,0 +1,193 @@ +// Default +import { + App, + PluginSettingTab, + Setting, +} from 'obsidian'; + +// Class +import UploadPlugin from './main'; + +// Utils +import { modes } from './config'; + +// Settings Tab +class UploadSettingsTab extends PluginSettingTab { + plugin: UploadPlugin; + + // 定义三种 DIV 容器 + defaultDiv: HTMLDivElement; // 默认容器,存放公共配置 + modeDiv: HTMLDivElement; // 模式容器,存放不同模式下的配置 + otherDiv: HTMLDivElement; // 其它容器,存放额外配置 + + constructor(app: App, plugin: UploadPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + const { containerEl } = this; + + containerEl.empty(); + + // 设置大标题 + containerEl.createEl("h1", { text: "上传设置" }); + + // 默认配置 + this.defaultDiv = containerEl.createDiv(); + this.drawDefaultSettings(this.defaultDiv); + + // 模式配置 + this.modeDiv = containerEl.createDiv(); + this.drawModeSettings(this.modeDiv, this.plugin.settings.mode); + + // 其它配置 + this.otherDiv = containerEl.createDiv(); + this.drawOtherSettings(this.otherDiv); + } + + // 自定义方法 + // 默认配置 + private drawDefaultSettings(parentEl: HTMLDivElement) { + new Setting(parentEl) + .setName('模式') + .setDesc('选择一种模式') + .addDropdown(e => { + modes.forEach((m) => { + e.addOption(m.value, m.text); + }) + + e.setValue(this.plugin.settings.mode) + e.onChange(async value => { + this.plugin.settings.mode = value; + await this.plugin.saveSettings(); + + await this.drawModeSettings(this.modeDiv, value); + }) + }) + } + + // 模式配置 + // 切换模式,不同的模式对应不同的配置 + private async drawModeSettings(parentEl: HTMLDivElement, args: any) { + parentEl && parentEl.empty(); + + switch (args) { + case 'lsky': + this.drawLskySettings(parentEl); + break; + case 'github': + this.drawGithubSettings(parentEl); + break; + default: + break; + } + } + + // 其它配置 + private drawOtherSettings(parentEl: HTMLDivElement) { + + } + + // Lskypro 设置 + private drawLskySettings(parentEL: HTMLDivElement) { + // Api URL + new Setting(parentEL) + .setName("API 地址") + .setDesc("请求地址") + .addText(text => { + text + .setPlaceholder("") + .setValue(this.plugin.settings.lskySetting.apiURL) + .onChange(async (value) => { + try { + this.plugin.settings.lskySetting.apiURL = value; + await this.plugin.saveSettings(); + } + catch (e) { + console.log(e) + } + }) + }) + + // Api request header + new Setting(parentEL) + .setName("POST Header") + .setDesc("请求头,json 格式") + .addTextArea((text) => { + text.setPlaceholder("输入合法的请求头") + .setValue(this.plugin.settings.lskySetting.apiReqHeader) + .onChange(async (value) => { + try { + this.plugin.settings.lskySetting.apiReqHeader = value; + await this.plugin.saveSettings(); + } + catch (e) { + console.log(e) + } + }) + text.inputEl.rows = 5 + text.inputEl.cols = 40 + }) + + // Api request body + new Setting(parentEL) + .setName("POST Body") + .setDesc("请求体,json 格式") + .addTextArea((text) => { + text.setPlaceholder("输入合法的请求体") + .setValue(this.plugin.settings.lskySetting.apiReqBody) + .onChange(async (value) => { + try { + this.plugin.settings.lskySetting.apiReqBody = value; + await this.plugin.saveSettings(); + } + catch (e) { + console.log(e) + } + }) + text.inputEl.rows = 5 + text.inputEl.cols = 40 + }) + new Setting(parentEL) + .setName("图片 URL 路径") + .setDesc("返回json数据中的图片URL字段的路径,如:data/pathname") + .addText(text => { + text + .setPlaceholder("") + .setValue(this.plugin.settings.lskySetting.imgUrlPath) + .onChange(async (value) => { + try { + this.plugin.settings.lskySetting.imgUrlPath = value; + await this.plugin.saveSettings(); + } + catch (e) { + console.log(e) + } + }) + }) + + new Setting(parentEL) + .setName("图片 URL 前缀") + .setDesc("可选,当填入时,URL = 前缀 + 图片的路径值") + .addText(text => { + text + .setPlaceholder("") + .setValue(this.plugin.settings.lskySetting.imgUrlPrefix) + .onChange(async (value) => { + try { + this.plugin.settings.lskySetting.imgUrlPrefix = value; + await this.plugin.saveSettings(); + } + catch (e) { + console.log(e) + } + }) + }) + } + + // Github 设置 + private drawGithubSettings(parentEL: HTMLDivElement) {} +} + +export default UploadSettingsTab; diff --git a/src/type.ts b/src/type.ts new file mode 100644 index 0000000..cf57e13 --- /dev/null +++ b/src/type.ts @@ -0,0 +1,29 @@ +export interface ILskySettings { + apiURL: string; + apiReqHeader: string; + apiReqBody: string; + imgUrlPath: string; + imgUrlPrefix: string; +} + +export interface IImage { + path: string; + name: string; + source: string; +} + +export interface IUploadPluginSettings { + mode: string; + apiURL: string; + apiReqHeader: string; + apiReqBody: string; + + imgUrlPath: string; + imgUrlPrefix: string; + + lskySetting: ILskySettings; +} + +export interface IUploader { + upload(file: File, fullPath?: string): Promise; +} diff --git a/src/uploader.ts b/src/uploader.ts new file mode 100644 index 0000000..c5973dd --- /dev/null +++ b/src/uploader.ts @@ -0,0 +1,15 @@ +import LskyUpload from "./lskyUpload"; + +// Types +import { IUploadPluginSettings, IUploader } from './type'; + +function Uploader(settings: IUploadPluginSettings): IUploader { + switch (settings.mode) { + case 'lsky': + return new LskyUpload(settings); + default: + throw new Error('should not reach here!') + } +} + +export default Uploader; diff --git a/helper.ts b/src/utils/helper.ts similarity index 94% rename from helper.ts rename to src/utils/helper.ts index 998db5d..bec9534 100644 --- a/helper.ts +++ b/src/utils/helper.ts @@ -42,11 +42,16 @@ export default class Helper { } getValue() { const editor = this.getEditor(); - return editor.getValue(); + if (editor) { + return editor.getValue(); + } else { + return ""; + } } setValue(value: string) { - const editor = this.getEditor(); + const editor: any = this.getEditor(); + const { left, top } = editor.getScrollInfo(); const position = editor.getCursor(); @@ -57,7 +62,8 @@ export default class Helper { // get all file urls, include local and internet getAllFiles(): Image[] { - const editor = this.getEditor(); + const editor: any = this.getEditor(); + let value = editor.getValue(); return this.getImageLink(value); } diff --git a/utils.ts b/src/utils/utils.ts similarity index 100% rename from utils.ts rename to src/utils/utils.ts diff --git a/tsconfig.json b/tsconfig.json index caa8e20..8c92e54 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,8 @@ "ES6", "ES2021", "ES7" - ] + ], + "allowSyntheticDefaultImports": true }, "include": [ "**/*.ts"