辛宝的玄酒清谈!
2366 words
12 minutes
速通 Obsidian Docs - 侧重插件开发1
2023-10-22

速通 Obsidian Docs - 侧重插件开发 内部链接

  • [[技术折腾 xLog 1 可行性探索]]
  • [[技术折腾 xLog 2 深入理解 xlog 的鉴权]]
  • [[技术折腾 xLog 3 实现一个 obsidian 插件]]
  • [[技术折腾 xLog 4 用 unStorage 封装 xLogDriver]]
  • [[速通 - xLog 背后的 CrossBell SDK]]
  • [[从官方 XLOG Obsidian 插件中能学到什么]]
  • [[开发 Obsidian Sync To Xlog 插件之 处理 obsidian 的图片]]
  • [[速通-CrossBell 的开源作品]]
  • [[速通 Obsidian Docs - 侧重插件开发]]

关联:

官方内容很多:

  • Plugin 插件开发,本次核心
  • Theme 主题开发。本次忽略
  • Refer 罗列 api
  • 其他

可以翻到下面看快速代码片段

插件开发#

我想通过介绍一个纯 js 的简单插件来讲解其结构和基本用法。这个插件非常实用,可以帮助我们更好地开发和调试。为了方便大家使用,我建议先安装一个名为 “hot-reload” 的插件,你可以在  https://github.com/pjeby/hot-reload  上找到它。

对于使用 vue 的用户,我还推荐一个模板,它结合了 vue3、vite4 和 obsidian 插件,可以让你更轻松地开始开发。你可以在  https://github.com/Otto-J/Obsidian-Vue-Starter  上找到这个模板。

在讲解插件的具体用法之前,我想先介绍一下插件的生命周期。插件的生命周期包括 onload 和 onunload 两个阶段。

移动端插件忽略。

使用 vue 开发的页面是挂在到 ItemView 上的。这意味着我们可以在 ItemView 中使用 vue 的各种功能来开发自定义页面。

  • 开始
    • 通过介绍一个纯 js 的简单插件,介绍结构和基本用法
    • 一定尽早安装 https://github.com/pjeby/hot-reload 方便开发和调试
    • vue 用户用这个模板,vue3+vite4+obsidian plugin
    • 生命周期 onload - onunload
    • 移动端插件,提供了如何开启模拟,知道就行
    • 使用 vue 开发的页面是挂在到 ItemView 上的
  • UI
    • UI 结构介绍,对齐不同区域的叫法
      • 放出一张图,介绍了不同的位置,sidebar 叫 ribbon actions 有点奇怪
      • 这是图片![[Pasted image 20230909121152.png]]
    • 【命令面板】
      • 类似 vsCode 使用 cmd + p 可以调出命令面板 command palette,插件开发在 onload 里调用 this.addCommand 方法注册,不复杂,比如处理当前激活编辑器的内容
      • 命令可以加前置判断,使用 checkCallback 方法,先校验是否运行,都加上把,省心
      • 可以访问编辑器,有 editorCallback 方法,参数里有 edtor, view 两个参数,当然也有 editorCheckCallback 选项
      • 可以注册 hotkeys,增加一种触发方式。细节忽略
    • 【右键菜单】
      • 导入obsidian.Menu,实例化 menu.addItem 可以补充,这是放到 sidebar 里的
      • 也可以放到 文件列表 file-menu 和 editor-menu 编辑器右上角![[Pasted image 20230909125832.png]]
    • 【html 容器】可以在插件设置里放 html 容器,在 PluginSettingTab 里
      • 可以用传统的 js createEl ,类似于 h 渲染函数。
      • 插件根目录的 styles.css,用 vue 吧省事
    • 【icons】从这里面找,https://lucide.dev/ 支持 flutter 挺好
      • 自定义的图标需要准备 svg 原内容,不用管 vue icons 一套走
    • 【modals】弹窗让用户输入信息
      • 从 Obsidian.Modal 导入 interface,需要实现 onOpen 和 onClose 方法
      • 点击提交时候有对应的回调
      • 还有一种 SuggestModal 辅助填写,和 FuzzySuggestModal 先忽略
    • 【sidebar】官方叫法是 ribbon actions
      • 用法就是 demo 一定会提到的 this.addRibbonIcon
    • 【settings】也就是公开的设置,插件填写配置的地方
      • 插件的设置是持久化的,所以流程都是先载入 this.loadData 合并得到配置结果
      • 官方实例的代码和文档一样
    • 【status bar】右下角的状态栏
      • this.addStatusBarItem 添加
      • 可以添加多个
    • 【views】资源管理、编辑器、预览都是 view
      • 获取 view ID getViewType
    • 【workspace】没看懂,树状结构
      • 可以并排打开过个文档,分割 split 的方式
      • 看不懂,文档写的烂
      • 关键词是 Leaf 啥啥啥
      • 直接看下面的快捷操作的代码片段把
  • Editor
    • 【Editor】Class 获取、编辑模式的 MD
      • 看下面的代码 p
    • 【Markdown 后处理】预览模式下的 md 是 html,可以调整 html
      • 例子 1,把 :happy 替换为 emoji 图标
      • 例子 2,把特殊语法的内容解析、添加,哦,就是数据转化,比如 csv 转成 table
    • 【编辑器拓展】可以先不看,介绍 CodeMirror6 的内容
      • 底层依赖的是 codeMirror6 也就是写拓展,这里不看了
      • 【装饰】给上面编辑器拓展的内容补充样式更没关
        • 先不看
      • 【State fields】编辑器拓展的,看不懂
      • 【State management】编辑器拓展的状态管理
        • 如果要撤销,需要保留状态
      • 【View Plugins】编辑器拓展插件,可以访问 viewport 忽略
      • 【viewport】高性能的编辑器少不了虚拟滚动,避免卡顿,滚动文档时候 viewport 会更新重新计算
      • 【编辑器通信】 忽略
  • Releasing 发布
    • CI 操作- 文档提供了一个 Github action ,用来辅助构建插件
    • 提交到官方市场之前,提供 Readme.md 许可证、mainfest.json 官方示例都有
    • 按照文档进行操作就行,官方维护了一个 json,提 pr 就行
    • 注意 node 和 electron 只允许桌面使用
    • 如果是 beta 版本,可以考虑 brat 插件
  • Event
    • 我们可能想要定时器,比如 setTimeInterval 用来试试更新一些东西,这里有一个专门的 this.registerInterval 方法 this.registerInterval( window.setInterval(() => this.updateStatusBar(), 1000) );
    • 时间格式化,内置了 obsidian.moment
  • Valut
    • 笔记的集合,封装了这些 fs 操作
    • 文档只读不操作用 cachedRead()
    • 文档读取并修改可以 read()
    • 修改文件使用 vault.modify() 这个是覆盖操作
    • valut.process 回调地址里有 data 表示当前内容,只支持同步操作
    • 异步修改考虑 vault.cachedRead() - 异步操作 - vault.process() 更新
    • 删除文件 trash() 是垃圾箱,彻底删除是 delete()
    • app.vault.getAbstractFileByPath("folderOrFile") 绝对路径的结果可能是文件、文件夹,通过 instanceof TFile/ TFolder 判断

常用片段#

有两种方法可以读取文件的内容:read()  和  cachedRead()。

  • 如果只想向用户显示内容,则 用于避免多次从磁盘读取文件。cachedRead()
  • 如果要读取内容,请更改它,然后将其写回磁盘,然后使用 以避免可能用过时的副本覆盖文件。read()
// 获取激活文本的内容
app.workspace.getActiveFileView().data

// 也可以
app.workspace.activeEditor

// 用户激活的光标位置
app.workspace.getActiveFileView().editor.getCursor()
// {line:2,ch:2}

// 指定位置追加文本 比如当前日期啥的,或者 template
app.workspace.getActiveFileView().editor.replaceRange('222',{line: 2, ch: 2})

// 正在激活的编辑器
const editor = app.workspace.getActiveFileView().editor

// 用户选中了一部分内容
app.workspace.getActiveFileView().editor.getSelection()

// 替换内容,比如上传后变成 cdn地址sha
editor.replaceSelection('xxx')

// 获取所有md文档
app.vault.getMarkdownFiles()

// 获取所有文件,之后过滤
app.vault.getFile()

// 获取文本内容
var file = app.vault.getMarkdownFiles()[0]
const content = await app.vault.cachedRead(file)

// parse frontmatter by content
const frontMatter = getFrontMatterInfo(content);


// 修改内容,这是同步操作
app.vault.process(file, (data) => { return data.replace(":)", "🙂"); })


this.app.workspace.on('editor-paste', (evt, editor, view) => {
if (evt.clipboardData) {}
}))

// 可以通过获得当前文件 file
this.app.workspace.on("file-menu", (menu, file) => {
if (file instanceof TFile) {}
});

// 给定一个字符串路径,判断文件是否存在,更安全
const isExist = await this.app.vault.adapter.exists('xx.md')

// 给定字符串,查找最佳匹配文件
app.metadataCache.getFirstLinkpathDest('文件夹/下载.png','')

// 得到 TFile 读取内容
app.metadataCache.getFileCache(file)
// 其中 embeds 表示关联的图片,但不包含外链标准语法,要结合使用
// frontmatter 是其中的 fm
// frontmatterPosition 是 fm 的起止位置,可以删除后替换
// links 表示当前文章的外链


// 读取图片二进制内容,后面把 blob 展示和上传
const conArrayBuffer = await plugin.app.vault.readBinary(TFile);
// 转成二进制,通过 post 上传
const blob = new Blob([conArrayBuffer], {
    type: "image/" + obInnerFile.extension,
});



操作 front-matter 比较常见和麻烦,提供一个常见方案

export const useObsidianFrontmatter = (file: TFile, app: App) => {
  // 使用更具语义化的函数名
  const doesFileExist = () => !!app.metadataCache.getFileCache(file);

  const currentFrontMatter = () =>
    app.metadataCache.getFileCache(file)?.frontmatter ?? {};
  const addOrUpdateFrontMatter = async (obj: Record<string, string>) => {
    const fileCache = app.metadataCache.getFileCache(file);
    // 如果文件不存在,直接返回
    if (!fileCache) {
      new Notice("文件不存在");
      return;
    }

    const currentFrontMatter = fileCache?.frontmatter ?? {};
    const newFrontMatter = `---\n${stringifyYaml({
      ...currentFrontMatter,
      ...obj,
    })}\n---\n`;

    // const { frontmatterPosition } = fileCache;
    // readcontent or con = vault.cachedRead(file)
    const fileContents = await app.vault.read(file);

    const frontmatterPosition = fileCache.frontmatterPosition ?? {
      start: {
        line: 0,
        col: 0,
        offset: 0,
      },
      end: {
        line: 0,
        col: 0,
        offset: 0,
      },
    };

    // 这里逻辑比较绕,目的是重写文件内容,后面如果有 api 可能就一行代码解决了,类似 metadataCache.update
    const {
      start: { offset: deleteFrom },
      end: { offset: deleteTo },
    } = frontmatterPosition;

    const newFileContents =
      fileContents.slice(0, deleteFrom) +
      newFrontMatter +
      fileContents.slice(deleteTo);

    await app.vault.modify(file, newFileContents);
  };

  return {
    doesFileExist,
    addOrUpdateFrontMatter,
    currentFrontMatter,
  };
};

ts api 快速熟悉#

官方文档写的特别差,周边也没看到有好的介绍。

快速罗列

  • abstract-input-suggest 在 input 输入框里给一些提示的,比如历史的 tags 之类的
  • abstract-text-component 看着是文本输入框相关的抽象类
  • app 应用级别的属性,可以拿到文件、vault、热键、工作区之类的
  • base-component 可以设置 disabled
  • class BlockCache extends CacheItem
  • block-sub-path-result
  • button-component 按钮组件,不用看,统一封装
  • cached-metadata—interface
  • cache-item—interface
  • closeable-component—class 提供关闭方法,不用看,也都封装
  • ColorComponent—class,颜色选择器,可以获得颜色 hex,可以被封装
  • Commond—interface 注册命令相关的方法
  • component—class 核心入口组件
  • data-adapter—interface,处理文件和温酒,推荐 Vault 的 api
  • data-write-options—interface
  • debouncer—interface
  • dropdown-component—class 可以封装
  • Editor—class,抹平 codemirror 版本差异的接口,应该可以提供很多辅助功能
  • editor-change—interface
  • editor-position—interface
  • editor-range—interface
  • editor-range-or-caret—interface
  • editor-scroll-info
  • editorxxx 略过
  • events—class 事件相关
  • extra-button-component—class 被封装
  • file-manager—class,从 UI 角度管理文件创建、删除、重命名
  • file-stats—interface
  • file-system-adapter implements DataAdapter—class
速通 Obsidian Docs - 侧重插件开发1
https://ijust.cc/posts/read-obsidian-plugin-docs/
Author
辛宝 Otto
Published at
2023-10-22