Skip to content

插件开发

核心概念

Plugin 基类

所有插件都继承自 Plugin 基类:

typescript
import { Plugin } from 'obsidian';

export default class MyPlugin extends Plugin {
  async onload() {
    // 插件加载时执行
  }

  onunload() {
    // 插件卸载时执行
  }
}

生命周期

typescript
async onload() {
  // 1. 加载设置
  await this.loadSettings();

  // 2. 注册命令
  this.addCommand({ ... });

  // 3. 注册视图
  this.registerView({ ... });

  // 4. 添加 UI 元素
  this.addRibbonIcon({ ... });

  // 5. 注册事件监听
  this.registerEvent({ ... });
}

onunload() {
  // 清理资源
}

注册命令

基本命令

typescript
this.addCommand({
  id: 'insert-date',
  name: 'Insert current date',
  callback: () => {
    const date = new Date().toLocaleDateString();
    // 在光标位置插入文本
    const editor = this.app.workspace.activeEditor?.editor;
    if (editor) {
      editor.replaceSelection(date);
    }
  }
});

带检查器的命令

typescript
this.addCommand({
  id: 'format-note',
  name: 'Format current note',
  checkCallback: (checking: boolean) => {
    const file = this.app.workspace.getActiveFile();
    if (file) {
      if (!checking) {
        this.formatFile(file);
      }
      return true;
    }
    return false;
  }
});

UI 元素

功能区图标

typescript
import { Notice } from 'obsidian';

this.addRibbonIcon('calendar', 'Open calendar', (evt) => {
  new Notice('Calendar clicked!');
});

状态栏

typescript
this.addStatusBarItem().setText('My Plugin Active');

视图

typescript
import { ItemView, WorkspaceLeaf } from 'obsidian';

const VIEW_TYPE = 'my-plugin-view';

class MyView extends ItemView {
  constructor(leaf: WorkspaceLeaf) {
    super(leaf);
  }

  getViewType() {
    return VIEW_TYPE;
  }

  getDisplayText() {
    return 'My View';
  }

  async onOpen() {
    const container = this.containerEl.children[1];
    container.empty();
    container.createEl('h1', { text: 'Hello from my view' });
  }

  async onClose() {
    // 清理
  }
}

// 注册视图
this.registerView(VIEW_TYPE, (leaf) => new MyView(leaf));

// 添加视图按钮
this.addRibbonIcon('info', 'Open my view', () => {
  this.activateView();
});

async activateView() {
  const { workspace } = this.app;
  let leaf: WorkspaceLeaf | null = null;
  const leaves = workspace.getLeavesOfType(VIEW_TYPE);

  if (leaves.length > 0) {
    leaf = leaves[0];
  } else {
    leaf = workspace.getRightLeaf(false);
    await leaf?.setViewState({ type: VIEW_TYPE, active: true });
  }

  if (leaf) {
    workspace.revealLeaf(leaf);
  }
}

模态框

基本模态框

typescript
import { App, Modal, Notice, Plugin, Setting } from 'obsidian';

class SampleModal extends Modal {
  constructor(app: App) {
    super(app);
  }

  onOpen() {
    const { contentEl } = this;
    contentEl.createEl('h1', { text: 'Modal Title' });

    new Setting(contentEl)
      .setName('Name')
      .addText(text => text.setPlaceholder('Enter name'));

    new Setting(contentEl)
      .addButton(btn => btn
        .setButtonText('Submit')
        .setCta()
        .onClick(() => {
          this.close();
        }));
  }

  onClose() {
    const { contentEl } = this;
    contentEl.empty();
  }
}

// 打开模态框
this.addCommand({
  id: 'open-modal',
  name: 'Open sample modal',
  callback: () => {
    new SampleModal(this.app).open();
  }
});

带建议的输入框

typescript
import { App, SuggestModal, TFile } from 'obsidian';

class FileSuggestModal extends SuggestModal<TFile> {
  constructor(app: App, onChoose: (file: TFile) => void) {
    super(app);
    this.onChoose = onChoose;
  }

  getSuggestions(query: string): TFile[] {
    const files = this.app.vault.getMarkdownFiles();
    return files.filter(file =>
      file.path.toLowerCase().includes(query.toLowerCase())
    );
  }

  renderSuggestion(file: TFile, el: HTMLElement) {
    el.createEl('div', { text: file.basename });
    el.createEl('small', { text: file.parent?.path });
  }

  onChooseSuggestion(file: TFile) {
    this.onChoose(file);
  }

  onChoose: (file: TFile) => void;
}

事件处理

注册事件

typescript
// 文件事件
this.registerEvent(
  this.app.vault.on('create', (file) => {
    console.log('File created:', file.path);
  })
);

this.registerEvent(
  this.app.vault.on('modify', (file) => {
    console.log('File modified:', file.path);
  })
);

// 工作区事件
this.registerEvent(
  this.app.workspace.on('file-open', (file) => {
    console.log('File opened:', file?.path);
  })
);

事件类型

typescript
// Vault 事件
vault.on('create', (file) => { });
vault.on('modify', (file) => { });
vault.on('delete', (file) => { });
vault.on('rename', (file, oldPath) => { });

// Workspace 事件
workspace.on('file-open', (file) => { });
workspace.on('active-leaf-change', (leaf) => { });
workspace.on('layout-change', () => { });

文件操作

读取文件

typescript
// 读取文本文件
const content = await this.app.vault.read(file);

// 读取二进制文件
const data = await this.app.vault.readBinary(file);

// 读取配置文件
const config = await this.app.vault.readJson('.obsidian/config.json');

写入文件

typescript
// 写入文本文件
await this.app.vault.modify(file, 'New content');

// 创建新文件
await this.app.vault.create('path/to/new.md', 'Initial content');

// 删除文件
await this.app.vault.trash(file, true);

文件夹操作

typescript
// 创建文件夹
await this.app.vault.createFolder('new-folder');

// 列出文件夹内容
const files = this.app.vault.getAbstractFileByPath('folder');

// 获取所有 Markdown 文件
const mdFiles = this.app.vault.getMarkdownFiles();

编辑器操作

获取编辑器

typescript
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (view) {
  const editor = view.editor;
  // 操作编辑器
}

编辑器方法

typescript
// 获取选中文本
const selection = editor.getSelection();

// 替换选中文本
editor.replaceSelection('New text');

// 获取光标位置
const cursor = editor.getCursor();

// 设置光标位置
editor.setCursor({ line: 0, ch: 0 });

// 插入文本
editor.replaceRange('Inserted text', { line: 0, ch: 0 });

// 获取行数
const lineCount = editor.lineCount();

// 获取行内容
const line = editor.getLine(0);

设置系统

设置接口

typescript
interface MyPluginSettings {
  enableFeature: boolean;
  maxItems: number;
  templatePath: string;
}

const DEFAULT_SETTINGS: MyPluginSettings = {
  enableFeature: true,
  maxItems: 10,
  templatePath: ''
};

加载和保存

typescript
async loadSettings() {
  this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}

async saveSettings() {
  await this.saveData(this.settings);
}

设置面板

typescript
class MySettingTab extends PluginSettingTab {
  plugin: MyPlugin;

  constructor(app: App, plugin: MyPlugin) {
    super(app, plugin);
    this.plugin = plugin;
  }

  display(): void {
    const { containerEl } = this;
    containerEl.empty();

    new Setting(containerEl)
      .setName('Enable feature')
      .setDesc('Toggle the main feature')
      .addToggle(toggle => toggle
        .setValue(this.plugin.settings.enableFeature)
        .onChange(async (value) => {
          this.plugin.settings.enableFeature = value;
          await this.plugin.saveSettings();
        }));

    new Setting(containerEl)
      .setName('Max items')
      .addSlider(slider => slider
        .setLimits(1, 100, 1)
        .setValue(this.plugin.settings.maxItems)
        .setDynamicTooltip()
        .onChange(async (value) => {
          this.plugin.settings.maxItems = value;
          await this.plugin.saveSettings();
        }));
  }
}

通知

typescript
import { Notice } from 'obsidian';

// 基本通知
new Notice('Hello, World!');

// 设置持续时间(毫秒)
new Notice('This will show for 5 seconds', 5000);

// 错误通知
new Notice('Error occurred!', 0); // 0 表示需要手动关闭

下一步

最后更新:2026年2月16日编辑此页反馈问题