obsidian
插件:Copy document as HTML
复制的图片是base64
下载node.js
https://nodejs.org/zh-cn/download/
在终端输入 node -v
bat
C:\Users\MI>node -v
v24.15.0
ts
git clone https://github.com/obsidianmd/obsidian-sample-plugin.git my-note-plugin
cd my-note-plugin
npm install
项目根目录下的 manifest.json,修改 id、name 等基本信息
ts
{
"id": "my-note-plugin",
"name": "My Note Plugin",
"author": "Your Name",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"isDesktopOnly": true
}
在插件项目目录下运行:
xml
npm run dev
main.ts:
ts
import { App, Modal, Notice, Plugin, PluginSettingTab, Setting, requestUrl, TFile } from 'obsidian';
// --- 定义插件的设置项结构---
interface MyPluginSettings {
apiBaseUrl: string;
}
// --- 定义从后端API获取的数据结构---
interface NoteItem {
id: number;
title: string;
}
// --- 插件主类,必须继承自Plugin---
export default class MyNotePlugin extends Plugin {
settings: MyPluginSettings;
async onload() {
await this.loadSettings();
// 添加一个设置页面
this.addSettingTab(new MySettingTab(this.app, this));
// 添加一个命令,这是插件的入口
this.addCommand({
id: 'fetch-and-edit-note',
name: '从后端拉取笔记并编辑',
callback: () => this.fetchAndEditNote()
});
}
// 主要业务逻辑:拉取笔记列表,选择后在临时文件编辑,保存时自动回写
async fetchAndEditNote() {
// 1. 从后端获取所有笔记的ID和标题列表
let noteList: NoteItem[];
try {
const response = await requestUrl({
url: `${this.settings.apiBaseUrl}/note/list`,
method: 'GET',
});
noteList = response.json;
if (noteList.length === 0) {
new Notice("没有从后端获取到任何笔记");
return;
}
} catch (error) {
console.error(error);
new Notice("获取笔记列表失败,请检查API地址是否正确");
return;
}
// 2. 弹出一个模态框让用户选择要编辑的笔记
const selectedId = await this.showNoteSelectionModal(noteList);
if (selectedId === null) return;
// 3. 根据用户选择的ID,拉取具体的Markdown内容
let markdownContent: string;
try {
const response = await requestUrl({
url: `${this.settings.apiBaseUrl}/note/${selectedId}/study`,
method: 'GET',
});
markdownContent = response.json.markdown || "";
} catch (error) {
console.error(error);
new Notice("获取笔记内容失败");
return;
}
// 4. 在Obsidian Vault中创建一个临时文件,并将拉取的内容写入
const tempFileName = `temp_edit_note_${selectedId}.md`;
let tempFile: TFile;
try {
tempFile = await this.app.vault.create(tempFileName, markdownContent);
} catch (error) {
console.error(error);
new Notice("创建临时文件失败");
return;
}
// 5. 在编辑器中打开这个临时文件,让用户编辑
await this.app.workspace.openLinkText(tempFileName, '', false);
// 6. 监听文件修改事件,当临时文件被保存时,将内容回传给后端
const saveHandler = this.registerEvent(
this.app.vault.on('modify', async (file) => {
if (file.path !== tempFileName) return;
if (!(file instanceof TFile)) return;
const updatedContent = await this.app.vault.read(file);
try {
await requestUrl({
url: `${this.settings.apiBaseUrl}/note/${selectedId}/study`,
method: 'PUT',
contentType: 'application/json',
body: JSON.stringify({ markdown: updatedContent }),
});
new Notice(`笔记 "${noteList.find(n => n.id === selectedId)?.title}" 已成功回写至数据库`);
// 回写成功后,关闭并删除临时文件
await this.app.workspace.getLeaf().setViewState({ type: "empty" });
await this.app.vault.delete(file);
} catch (error) {
console.error(error);
new Notice("保存笔记到后端失败,请检查API连接");
}
})
);
}
// 显示选择笔记的模态框,返回用户选择的笔记ID
showNoteSelectionModal(notes: NoteItem[]): Promise<number | null> {
return new Promise((resolve) => {
const modal = new NoteSelectionModal(this.app, notes, (id) => resolve(id));
modal.open();
});
}
// 加载插件设置(从data.json)
async loadSettings() {
this.settings = Object.assign({ apiBaseUrl: 'http://localhost:8080/api' }, await this.loadData());
}
// 保存插件设置到data.json
async saveSettings() {
await this.saveData(this.settings);
}
}
// --- 选择笔记的模态框实现(使用FuzzySuggestModal)---
class NoteSelectionModal extends Modal {
notes: NoteItem[];
onSubmit: (id: number) => void;
constructor(app: App, notes: NoteItem[], onSubmit: (id: number) => void) {
super(app);
this.notes = notes;
this.onSubmit = onSubmit;
}
onOpen() {
const { contentEl } = this;
contentEl.createEl("h2", { text: "选择要编辑的笔记" });
const listEl = contentEl.createEl("ul");
this.notes.forEach(note => {
const li = listEl.createEl("li");
li.createEl("button", { text: `${note.id}. ${note.title}` }).onclick = () => {
this.close();
this.onSubmit(note.id);
};
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
}
// --- 插件设置页面,让用户配置后端API地址---
class MySettingTab extends PluginSettingTab {
plugin: MyNotePlugin;
constructor(app: App, plugin: MyNotePlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
new Setting(containerEl)
.setName('后端API基础地址')
.setDesc('你的Java Spring Boot API基础URL,例如 http://localhost:8080/api')
.addText(text => text
.setPlaceholder('http://localhost:8080/api')
.setValue(this.plugin.settings.apiBaseUrl)
.onChange(async (value) => {
this.plugin.settings.apiBaseUrl = value;
await this.plugin.saveSettings();
}));
}
}
在插件项目根目录下运行
xml
npm run build
文件目录:
xml
你的笔记仓库根目录/
├── .obsidian/
│ ├── plugins/ # 插件文件夹
│ │ ├── my-note-plugin/ # 你的插件专属文件夹
│ │ │ ├── main.js
│ │ │ ├── manifest.json
│ │ │ └── styles.css (如果有)
│ │ └── ...
│ └── ...