开发步骤

  1. obsidian-sample-plugin克隆一个仓库
  2. 修改其中的 manifest.json 文件,其中 plugin id 是最重要的。
  3. 修改主文件 main.ts,使用 npm install 安装依赖库
  4. 使用命令 npm run dev 编译出 main.js
  5. 在 Obsidian 的 vault 目录 .obsidian/plugins/ 创建一个插件名称的文件夹,拷贝 manifest.json 和 main.js 到该目录,有的插件可能还有 style.css 等文件。
  6. 在 settings 页面加载插件

在开发过程中,可以通过 Ctrl-Shift-i 来打开调试页面。

创建您的第一个插件

开发插件

hot-reload: 热重载

首先,需要在插件目录中添加 .hotreload 文件,然后,下载插件并开启。

注意❗

  • 如果没有hot-reload,需要重新打开插件,代码才能生效。
  • 插件id为输出文件夹的名称。

常用命令

如何开启开发者模式 /控制台

  1. Win:Ctrl + Shitf + i.
  2. Mac:cmd+ option + i.

发布插件

使用 GitHub Actions 发布您的插件

name: Release Obsidian plugin
 
on:
  push:
    tags:
      - "*"
 
env:
  PLUGIN_NAME: your-plugin-id # Change this to match the id of your plugin.
 
jobs:
  build:
    runs-on: ubuntu-latest
 
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js
        uses: actions/setup-node@v1
        with:
          node-version: "14.x"
 
      - name: Build
        id: build
        run: |
          npm install
          npm run build
          mkdir ${{ env.PLUGIN_NAME }}
          cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }}
          zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
          ls
          echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)"
 
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          VERSION: ${{ github.ref }}
        with:
          tag_name: ${{ github.ref }}
          release_name: ${{ github.ref }}
          draft: false
          prerelease: false
 
      - name: Upload zip file
        id: upload-zip
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./${{ env.PLUGIN_NAME }}.zip
          asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip
          asset_content_type: application/zip
 
      - name: Upload main.js
        id: upload-main
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./main.js
          asset_name: main.js
          asset_content_type: text/javascript
 
      - name: Upload manifest.json
        id: upload-manifest
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./manifest.json
          asset_name: manifest.json
          asset_content_type: application/json
 
      - name: Upload styles.css
        id: upload-css
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./styles.css
          asset_name: styles.css
          asset_content_type: text/css

提交插件

将插件添加到配置文件中,提交pull request 进行审核。

{
  "id": "recent-files-obsidian",
  "name": "Recent Files",
  "author": "Tony Grosinger",
  "description": "Display a list of recently opened files",
  "repo": "tgrosinger/recent-files-obsidian",
  "branch": "main"
}

UI 界面

Views (创建视图)

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.
  }
}

DOM API (创建元素)

 

  • createEl()
  • createDiv()
  • createSpan()
const book = containerEl.createEl('div', { cls: 'book' });
book.createEl('div', { text: 'How to Take Smart Notes', cls: 'book__title' });
book.createEl('small', { text: 'Sönke Ahrens', cls: 'book__author' });
.book {
  border: 1px solid var(--background-modifier-border);
  padding: 10px;
}
 
.book__title {
  font-weight: 600;
}
 
.book__author {
  color: var(--text-muted);
}

根据条件设置样式:

element.toggleClass('danger', status === 'error');

下面是系统中的CSS 变量:

使用 React 组件

安装React并配置

  1. Add React to your plugin dependencies:
npm install react react-dom
  1. Add type definitions for React:
npm install --save-dev @types/react @types/react-dom
  1. In tsconfig.json, enable JSX support on the compilerOptions object:
{
  "compilerOptions": {
	"jsx": "react-jsx"
  }
}
    ```
 
### 创建组件
 
创建一个叫 `ReactView.tsx` 的组件:
 
```tsx
export const ReactView = () => {
  return <h4>Hello, React!</h4>;
};

使用React组件

下面将 ReactView 组件挂载到 this.containerEl.children[1] 元素上:

import { StrictMode } from 'react';
import { ItemView, WorkspaceLeaf } from 'obsidian';
import { Root, createRoot } from 'react-dom/client';
import { ReactView } from './ReactView';
 
const VIEW_TYPE_EXAMPLE = 'example-view';
 
class ExampleView extends ItemView {
	root: Root | null = null;
 
	constructor(leaf: WorkspaceLeaf) {
		super(leaf);
	}
 
	getViewType() {
		return VIEW_TYPE_EXAMPLE;
	}
 
	getDisplayText() {
		return 'Example view';
	}
 
	async onOpen() {
		this.root = createRoot(this.containerEl.children[1]);
		this.root.render(
			<StrictMode>
				<ReactView />,
			</StrictMode>,
		);
	}
 
	async onClose() {
		this.root?.unmount();
	}
}

创建全局状态

  1. Use createContext() to create a new app context.
import { createContext } from 'react';
import { App } from 'obsidian';
 
export const AppContext = createContext<App | undefined>(undefined);
  1. Wrap the ReactView with a context provider and pass the app as the value.
this.root = createRoot(this.containerEl.children[1]);
this.root.render(
  <AppContext.Provider value={this.app}>
	<ReactView />
  </AppContext.Provider>
);
  1. Create a custom hook to make it easier to use the context in your components.
import { useContext } from 'react';
import { AppContext } from './context';
 
export const useApp = (): App | undefined => {
  return useContext(AppContext);
};
  1. Use the hook in any React component within ReactView to access the app.
import { useApp } from './hooks';
 
export const ReactView = () => {
  const { vault } = useApp();
 
  return <h4>{vault.getName()}</h4>;
};

Settings

扩展阅读