Obsidian UI 技术原理
在插件开发的过程中发现 Obsidian 有比较复杂的 UI 设计,不同的功能被归属到不同的类中,同时为了方便管理还引入了 workspace、leaf 等概念。为了能够更加高效的进行 Obsidian 插件开发和个性化定制,有必要对这一技术细节进行梳理总结。
UI 概览
Obsidian UI 中主要分为:
- Ribbon,上面主要是一些功能/插件的按钮
- Right Sidebar/Left Sidebar,左右侧边栏
- Tab view,主要的显示区域
- Editor,是 view 的成员,用来与 CodeMirror 交互操作 markdown 文本
- Status bar,是右下角的状态栏
workspace
与 VS Code 类似,Obsidian 支持在任何时候配置哪些内容是否可见,例如在不需要时可以配置隐藏文件资源管理器,例如可以并排显示多个文档。整个 Obsidian 应用程序窗口中可见内容的配置称为工作区(workspace)。这就意味着,workspace 中会包含上文提到的所有元素,如 view、editor、sidebar、statusbar 等。
workspace 数据结构
workspace 的底层实现是一个树形结构,树的节点是 WorkspaceItem
,它包括两种类型:WorkspaceParent
、WorkspaceLeaf
。
WorkspaceParent 与 WorkspaceLeaf 区别
WorkspaceParent
可以包括子节点(包括WorkspaceParent
类型),有两种类型:WorkspaceSplits
和WorkspaceTabs
WorkspaceLeaf
不包括任何子节点
WorkspaceSplit 与 WorkspaceTabs 区别
WorkspaceSplits
沿着垂直或水平方向一个接一个地排列它的子项WorkspaceTabs
一次只显示一个子项,并隐藏其他的子项
workspace 示意图
Class 定义
上面提到的 workspace 相关的类型在 Obsidian API 中都有详细的定义,可以参考:obsidian-api/obsidian.d.ts
/**
* @public
*/
export class WorkspaceLeaf extends WorkspaceItem {
/**
* @public
*/
view: View;
/**
* By default, `openFile` will also make the leaf active.
* Pass in `{ active: false }` to override.
*
* @public
*/
openFile(file: TFile, openState?: OpenViewState): Promise<void>;
/**
* @public
*/
open(view: View): Promise<View>;
/**
* @public
*/
getViewState(): ViewState;
/**
* @public
*/
setViewState(viewState: ViewState, eState?: any): Promise<void>;
/**
* @public
*/
getEphemeralState(): any;
/**
* @public
*/
setEphemeralState(state: any): void;
/**
* @public
*/
togglePinned(): void;
/**
* @public
*/
setPinned(pinned: boolean): void;
/**
* @public
*/
setGroupMember(other: WorkspaceLeaf): void;
/**
* @public
*/
setGroup(group: string): void;
/**
* @public
*/
detach(): void;
/**
* @public
*/
getIcon(): IconName;
/**
* @public
*/
getDisplayText(): string;
/**
* @public
*/
onResize(): void;
/**
* @public
*/
on(name: 'pinned-change', callback: (pinned: boolean) => any, ctx?: any): EventRef;
/**
* @public
*/
on(name: 'group-change', callback: (group: string) => any, ctx?: any): EventRef;
}
view
view 决定了 Obisidian 如何显示内容,文件资源管理器,Graph 视图和 Markdown 视图都是一种视图,在插件开发的过程中如果需要自定义显示的内容还可以创建自己的 ItemView
,ItemView
继承自 View
,关于 View class 的具体定义可以参考:obsidian-api View
自定义 View
如下是一个扩展示例:
import { ItemView, WorkspaceLeaf } from "obsidian";
export const VIEW_TYPE_EXAMPLE = "example-view";
export class ExampleView extends ItemView {
constructor(leaf: WorkspaceLeaf) {
super(leaf);
}
getViewType() {
return VIEW_TYPE_EXAMPLE;
}
getDisplayText() {
return "Example view";
}
async onOpen() {
const container = this.containerEl.children[1];
container.empty();
container.createEl("h4", { text: "Example view" });
}
async onClose() {
// Nothing to clean up.
}
}
view 和 workspace 的关系
根据 WorkspaceLeaf class 定义可以确认 view 是 WorkspaceLeaf
的成员,同样的 View 也引用了对应的 WorkspaceLeaf
成员。
实用 Obsidian UI 编程
1. 当前 workspace 检查
this.app.workspace.iterateAllLeaves((leaf) => {
// 遍历 workspace 树形结构,可以获取没有节点对应的 view
// view 可以对显示内容进行控制和管理
console.log(leaf.getViewState().type);
});
2. view 生命周期
import { Plugin } from "obsidian";
import { ExampleView, VIEW_TYPE_EXAMPLE } from "./view";
export default class ExamplePlugin extends Plugin {
async onload() {
this.registerView(
VIEW_TYPE_EXAMPLE,
(leaf) => new ExampleView(leaf)
);
this.addRibbonIcon("dice", "Activate view", () => {
this.activateView();
});
}
async onunload() {
this.app.workspace.detachLeavesOfType(VIEW_TYPE_EXAMPLE);
}
async activateView() {
this.app.workspace.detachLeavesOfType(VIEW_TYPE_EXAMPLE);
await this.app.workspace.getRightLeaf(false).setViewState({
type: VIEW_TYPE_EXAMPLE,
active: true,
});
this.app.workspace.revealLeaf(
this.app.workspace.getLeavesOfType(VIEW_TYPE_EXAMPLE)[0]
);
}
}
开发实践
利用 QuickAdd 提供的执行 js 脚本的能力实现操作 UI 显示当前笔记对应的 graph 视图,效果如下: