mirror of
https://github.com/appleec/e-obsidian-upload-plugin.git
synced 2026-07-22 11:50:27 +00:00
perf: 优化代码
This commit is contained in:
parent
eb9d617e21
commit
23fcd59035
16 changed files with 621 additions and 471 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
-
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
460
main.ts
460
main.ts
|
|
@ -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<string> {
|
||||
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,
|
||||
``
|
||||
);
|
||||
});
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
24
src/config.ts
Normal file
24
src/config.ts
Normal file
|
|
@ -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;
|
||||
45
src/lskyUpload.ts
Normal file
45
src/lskyUpload.ts
Normal file
|
|
@ -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<string> {
|
||||
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;
|
||||
253
src/main.ts
Normal file
253
src/main.ts
Normal file
|
|
@ -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,
|
||||
``
|
||||
);
|
||||
});
|
||||
|
||||
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;
|
||||
23
src/modal.ts
Normal file
23
src/modal.ts
Normal file
|
|
@ -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;
|
||||
193
src/settings-tab.ts
Normal file
193
src/settings-tab.ts
Normal file
|
|
@ -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;
|
||||
29
src/type.ts
Normal file
29
src/type.ts
Normal file
|
|
@ -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<string>;
|
||||
}
|
||||
15
src/uploader.ts
Normal file
15
src/uploader.ts
Normal file
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -17,7 +17,8 @@
|
|||
"ES6",
|
||||
"ES2021",
|
||||
"ES7"
|
||||
]
|
||||
],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
|
|
|
|||
Loading…
Reference in a new issue