Skip to content

Ribbon 与 StatusBar API

本文详细介绍如何为插件添加 Ribbon 侧边栏图标和 StatusBar 状态栏元素。

Ribbon 侧边栏图标

Ribbon 是 Obsidian 左侧的垂直按钮栏,用于快速触发插件功能。

基本用法

typescript
import { Plugin, addIcon } from 'obsidian';

export default class MyPlugin extends Plugin {
  async onload() {
    // 创建 Ribbon 图标
    const ribbonIcon = this.addRibbonIcon(
      'dice',        // 图标名称(lucide 图标集)
      'My Plugin',   // 悬停提示文字
      (evt: MouseEvent) => {
        // 点击回调
        new Notice('Ribbon clicked!');
      }
    );

    // 可以给元素添加样式
    ribbonIcon.addClass('my-plugin-ribbon-class');
  }
}

使用自定义 SVG 图标

typescript
import { addIcon } from 'obsidian';

// 注册自定义图标
addIcon('my-custom-icon', `<svg viewBox="0 0 100 100" ...>
  <!-- 你的 SVG 路径 -->
</svg>`);

// 使用自定义图标
this.addRibbonIcon('my-custom-icon', 'Custom Action', () => {
  // 处理点击
});

常用 Lucide 图标

图标名视觉效果适用场景
dice🎲随机/通用功能
bookmark🔖书签/收藏
calendar📅日历/日程
search🔍搜索/查找
settings⚙️设置/配置
file-plus📄+新建文件
folder-plus📁+新建文件夹
refresh-cw🔄同步/刷新
download⬇️下载/导入
upload⬆️上传/导出
eye👁️预览/查看
edit✏️编辑
trash🗑️删除
link🔗链接
image🖼️图片
code💻代码
terminal>_终端/命令
clock🕐时间/定时
star收藏/星标
heart❤️喜欢/关注

完整图标列表请参阅 Lucide Icons

条件显示与隐藏

typescript
export default class MyPlugin extends Plugin {
  private ribbonIcon: HTMLElement | null = null;

  async onload() {
    // 始终创建,通过 CSS 控制显示
    this.ribbonIcon = this.addRibbonIcon('clock', 'Timer', () => {
      this.toggleTimer();
    });

    // 根据设置决定初始可见性
    this.updateRibbonVisibility();
  }

  private updateRibbonVisibility() {
    if (this.ribbonIcon) {
      this.ribbonIcon.style.display =
        this.settings.showTimer ? '' : 'none';
    }
  }

  private toggleTimer() {
    this.settings.showTimer = !this.settings.showTimer;
    this.updateRibbonVisibility();
    this.saveSettings();
  }
}

Ribbon 图标动态更新

typescript
export default class MyPlugin extends Plugin {
  private ribbonIcon: HTMLElement | null = null;
  private timerRunning = false;

  async onload() {
    this.ribbonIcon = this.addRibbonIcon(
      'play',
      'Toggle Timer',
      () => this.toggleTimer()
    );
  }

  private toggleTimer() {
    this.timerRunning = !this.timerRunning;

    // 动态更换图标
    const iconEl = this.ribbonIcon?.querySelector('svg');
    if (iconEl) {
      // 方法 1:切换 CSS 类
      iconEl.classList.toggle('is-active', this.timerRunning);
    }

    // 方法 2:更换整个图标(需要重新创建)
    // 注意:addRibbonIcon 不支持直接更换图标,需要通过 CSS 变通
  }
}
css
/* styles.css - 图标状态样式 */
.my-plugin-ribbon-class.is-active {
  color: var(--text-accent);
}
.my-plugin-ribbon-class.is-active svg {
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

StatusBar 状态栏

StatusBar 位于 Obsidian 窗口底部右侧,用于显示持续性的状态信息。

基本用法

typescript
export default class MyPlugin extends Plugin {
  private statusBarItem: HTMLElement;

  async onload() {
    // 创建状态栏项
    this.statusBarItem = this.addStatusBarItem();

    // 设置文字
    this.statusBarItem.setText('Ready');

    // 添加点击事件
    this.statusBarItem.addEventListener('click', () => {
      new Notice('StatusBar clicked!');
    });

    // 添加 CSS 类
    this.statusBarItem.addClass('my-plugin-status');
  }
}

状态栏完整示例

typescript
export default class WordCounterPlugin extends Plugin {
  private statusBarItem: HTMLElement;

  async onload() {
    this.statusBarItem = this.addStatusBarItem();
    this.statusBarItem.addClass('word-count-status');

    // 监听编辑器变化
    this.registerEvent(
      this.app.workspace.on('active-leaf-change', () => {
        this.updateWordCount();
      })
    );

    this.registerEvent(
      this.app.workspace.on('editor-change', () => {
        this.updateWordCount();
      })
    );

    // 初始更新
    this.updateWordCount();
  }

  private updateWordCount() {
    const editor = this.app.workspace.activeEditor?.editor;
    if (!editor) {
      this.statusBarItem.setText('0 words');
      return;
    }

    const text = editor.getValue();
    const wordCount = text.split(/\s+/).filter(w => w.length > 0).length;
    const charCount = text.length;

    // 支持 HTML 内容(富文本显示)
    this.statusBarItem.setText(
      `${wordCount} words · ${charCount} chars`
    );
  }
}

StatusBar 富文本显示

typescript
// 使用 createEl 构建富文本状态栏
private setupRichStatusBar() {
  this.statusBarItem = this.addStatusBarItem();
  this.statusBarItem.addClass('rich-status');

  // 创建多个子元素
  const container = this.statusBarItem.createDiv('status-container');

  const wordIcon = container.createSpan('status-icon');
  wordIcon.innerHTML = '📝'; // 或使用 SVG

  const wordCount = container.createSpan('status-value');
  wordCount.setText('0');

  const separator = container.createSpan('status-separator');
  separator.setText('·');

  const charIcon = container.createSpan('status-icon');
  charIcon.innerHTML = '📄';

  const charCount = container.createSpan('status-value');
  charCount.setText('0');
}

状态指示器模式

typescript
// 不同状态显示不同样式
class SyncStatusBar {
  private statusBar: HTMLElement;
  private state: 'idle' | 'syncing' | 'error' | 'success' = 'idle';

  constructor(plugin: Plugin) {
    this.statusBar = plugin.addStatusBarItem();
    this.render();
  }

  setState(state: typeof this.state) {
    this.state = state;
    this.render();
  }

  private render() {
    // 清除之前的状态类
    this.statusBar.removeClass(
      'status-idle', 'status-syncing', 'status-error', 'status-success'
    );

    switch (this.state) {
      case 'idle':
        this.statusBar.addClass('status-idle');
        this.statusBar.setText('📴 Idle');
        break;
      case 'syncing':
        this.statusBar.addClass('status-syncing');
        this.statusBar.setText('🔄 Syncing...');
        break;
      case 'error':
        this.statusBar.addClass('status-error');
        this.statusBar.setText('❌ Sync failed');
        break;
      case 'success':
        this.statusBar.addClass('status-success');
        this.statusBar.setText('✅ Synced');
        // 3 秒后回到 idle
        setTimeout(() => this.setState('idle'), 3000);
        break;
    }
  }
}

多个状态栏项管理

typescript
export default class MyPlugin extends Plugin {
  private statusItems: Map<string, HTMLElement> = new Map();

  private addStatusItem(id: string): HTMLElement {
    const item = this.addStatusBarItem();
    this.statusItems.set(id, item);
    return item;
  }

  async onload() {
    // 创建多个状态栏项
    this.addStatusItem('sync').setText('📴 Ready');
    this.addStatusItem('cursor').setText('1:1');
    this.addStatusItem('file-size').setText('0 KB');

    // 监听更新
    this.registerEvent(
      this.app.workspace.on('editor-change', () => {
        this.updateCursorPosition();
        this.updateFileSize();
      })
    );
  }

  private updateCursorPosition() {
    const editor = this.app.workspace.activeEditor?.editor;
    if (editor) {
      const cursor = editor.getCursor();
      const item = this.statusItems.get('cursor');
      item?.setText(`${cursor.line + 1}:${cursor.ch + 1}`);
    }
  }

  private updateFileSize() {
    const file = this.app.workspace.getActiveFile();
    if (file) {
      const size = file.stat.size;
      const formatted = size > 1024
        ? `${(size / 1024).toFixed(1)} KB`
        : `${size} B`;
      const item = this.statusItems.get('file-size');
      item?.setText(formatted);
    }
  }
}

移动端适配

Ribbon 移动端差异

typescript
import { Platform } from 'obsidian';

async onload() {
  if (Platform.isMobile) {
    // 移动端 Ribbon 空间有限
    // 考虑只在桌面端添加 Ribbon 图标
  } else {
    this.addRibbonIcon('star', 'Favorite', callback);
  }
}

StatusBar 移动端适配

typescript
private updateStatusForPlatform() {
  if (Platform.isMobile) {
    // 移动端:精简显示,减少信息密度
    this.statusBarItem.setText(`${wordCount}w`);
    this.statusBarItem.style.fontSize = '11px';
    this.statusBarItem.style.padding = '0 4px';
  } else {
    // 桌面端:完整显示
    this.statusBarItem.setText(`${wordCount} words · ${charCount} chars`);
    this.statusBarItem.style.fontSize = 'var(--font-ui-small)';
  }
}

样式定制

Ribbon 图标样式

css
/* 自定义 Ribbon 按钮样式 */
.my-plugin-ribbon-class {
  opacity: 0.7;
  transition: opacity 0.2s, color 0.2s;
}

.my-plugin-ribbon-class:hover {
  opacity: 1;
  color: var(--text-accent);
}

.my-plugin-ribbon-class:active {
  transform: scale(0.95);
}

StatusBar 样式

css
/* 自定义状态栏样式 */
.my-plugin-status {
  cursor: pointer;
  padding: 0 8px;
  border-radius: 4px;
  transition: background-color 0.2s;
}

.my-plugin-status:hover {
  background-color: var(--background-modifier-hover);
}

常见问题

Q: 如何动态更新 StatusBar 文字?

A: 使用 statusBarItem.setText() 方法,它可以直接替换内容:

typescript
this.statusBarItem.setText(`Updated: ${new Date().toLocaleTimeString()}`);

Q: addRibbonIcon 能更换图标吗?

A: 不能直接更换。推荐通过 CSS 类控制不同状态的样式,或为不同状态创建不同的逻辑展示。

Q: StatusBar 能显示在左侧吗?

A: 原生 API 将 StatusBar 固定在最右侧。如需左侧显示,可以在 onload 时操作 DOM 调整顺序。

Q: 移动端 StatusBar 被截断怎么办?

A: 移动端屏幕较窄,建议:

  • 使用缩写代替完整文字
  • 减小内边距
  • 使用 font-size 缩放

相关资源