feat: init

This commit is contained in:
exoticknight 2025-01-27 22:47:57 +08:00
parent f7c5c8e128
commit be3a89b3fb
13 changed files with 2821 additions and 234 deletions

114
README.md
View file

@ -1,94 +1,44 @@
# Obsidian Sample Plugin # Quick Nav
This is a sample plugin for Obsidian (https://obsidian.md). English | [中文](./README.zh-cn.md)
This project uses TypeScript to provide type checking and documentation. Swift cursor navigation plugin to enhance editing.
The repo depends on the latest plugin API (obsidian.d.ts) in TypeScript Definition format, which contains TSDoc comments describing what it does.
This sample plugin demonstrates some of the basic functionality the plugin API can do. ## Usage
- Adds a ribbon icon, which shows a Notice when clicked.
- Adds a command "Open Sample Modal" which opens a Modal.
- Adds a plugin setting tab to the settings page.
- Registers a global click event and output 'click' to the console.
- Registers a global interval which logs 'setInterval' to the console.
## First time developing plugins? ### Cursor Move
Quick starting guide for new plugin devs: - **Move Cursor Up**: `Alt + I` (Windows/Linux), `Ctrl + I` (MacOS)
- **Move Cursor Down**: `Alt + K` (Windows/Linux), `Ctrl + K` (MacOS)
- **Move Cursor Left**: `Alt + J` (Windows/Linux), `Ctrl + J` (MacOS)
- **Move Cursor Right**: `Alt + L` (Windows/Linux), `Ctrl + L` (MacOS)
- **Move Cursor Page Up**: `Alt + U` (Windows/Linux), `Ctrl + U` (MacOS)
- **Move Cursor Page Down**: `Alt + O` (Windows/Linux), `Ctrl + O` (MacOS)
- **Move Cursor Left End**: `Alt + H` (Windows/Linux), `Ctrl + H` (MacOS)
- **Move Cursor Right End**: `Alt + ;` (Windows/Linux), `Ctrl + ;` (MacOS)
- Check if [someone already developed a plugin for what you want](https://obsidian.md/plugins)! There might be an existing plugin similar enough that you can partner up with. ### Cursor Select
- Make a copy of this repo as a template with the "Use this template" button (login to GitHub if you don't see it).
- Clone your repo to a local development folder. For convenience, you can place this folder in your `.obsidian/plugins/your-plugin-name` folder.
- Install NodeJS, then run `npm i` in the command line under your repo folder.
- Run `npm run dev` to compile your plugin from `main.ts` to `main.js`.
- Make changes to `main.ts` (or create new `.ts` files). Those changes should be automatically compiled into `main.js`.
- Reload Obsidian to load the new version of your plugin.
- Enable plugin in settings window.
- For updates to the Obsidian API run `npm update` in the command line under your repo folder.
## Releasing new releases - **Select Cursor Up**: `Alt + Shift + I` (Windows/Linux), `Ctrl + Shift + I` (MacOS)
- **Select Cursor Down**: `Alt + Shift + K` (Windows/Linux), `Ctrl + Shift + K` (MacOS)
- **Select Cursor Left**: `Alt + Shift + J` (Windows/Linux), `Ctrl + Shift + J` (MacOS)
- **Select Cursor Right**: `Alt + Shift + L` (Windows/Linux), `Ctrl + Shift + L` (MacOS)
- **Select Cursor Left End**: `Alt + Shift + H` (Windows/Linux), `Ctrl + Shift + H` (MacOS)
- **Select Cursor Right End**: `Alt + Shift + ;` (Windows/Linux), `Ctrl + Shift + ;` (MacOS)
- Update your `manifest.json` with your new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release. ## Installation
- Update your `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible.
- Create new GitHub release using your new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. See here for an example: https://github.com/obsidianmd/obsidian-sample-plugin/releases
- Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. Note: The manifest.json file must be in two places, first the root path of your repository and also in the release.
- Publish the release.
> You can simplify the version bump process by running `npm version patch`, `npm version minor` or `npm version major` after updating `minAppVersion` manually in `manifest.json`. ### Method 1: Install via Obsidian Settings
> The command will bump version in `manifest.json` and `package.json`, and add the entry for the new version to `versions.json`
## Adding your plugin to the community plugin list 1. Open Obsidian and go to `Settings`.
2. Navigate to the `Community plugins` tab.
3. Click on `Browse` and search for `Quick Nav`.
4. Click `Install` and then `Enable`.
- Check the [plugin guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines). ### Method 2: Manual Installation
- Publish an initial version.
- Make sure you have a `README.md` file in the root of your repo.
- Make a pull request at https://github.com/obsidianmd/obsidian-releases to add your plugin.
## How to use 1. Download the latest release from the [GitHub repository](https://github.com/exoticknight/quick-nav/releases).
2. Extract the contents of the zip file.
- Clone this repo. 3. Copy the extracted folder to your Obsidian vault's plugins directory: `/path/to/your/vault/.obsidian/plugins/`.
- Make sure your NodeJS is at least v16 (`node --version`). 4. Open Obsidian and go to `Settings`.
- `npm i` or `yarn` to install dependencies. 5. Navigate to the `Community plugins` tab and enable `Quick Nav`.
- `npm run dev` to start compilation in watch mode.
## Manually installing the plugin
- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`.
## Improve code quality with eslint (optional)
- [ESLint](https://eslint.org/) is a tool that analyzes your code to quickly find problems. You can run ESLint against your plugin to find common bugs and ways to improve your code.
- To use eslint with this project, make sure to install eslint from terminal:
- `npm install -g eslint`
- To use eslint to analyze this project use this command:
- `eslint main.ts`
- eslint will then create a report with suggestions for code improvement by file and line number.
- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder:
- `eslint .\src\`
## Funding URL
You can include funding URLs where people who use your plugin can financially support it.
The simple way is to set the `fundingUrl` field to your link in your `manifest.json` file:
```json
{
"fundingUrl": "https://buymeacoffee.com"
}
```
If you have multiple URLs, you can also do:
```json
{
"fundingUrl": {
"Buy Me a Coffee": "https://buymeacoffee.com",
"GitHub Sponsor": "https://github.com/sponsors",
"Patreon": "https://www.patreon.com/"
}
}
```
## API Documentation
See https://github.com/obsidianmd/obsidian-api

44
README.zh-cn.md Normal file
View file

@ -0,0 +1,44 @@
# Quick Nav
[English](./README.md) | 中文
快速光标导航插件以增强编辑功能。
## 使用方法
### 光标移动
- **向上移动光标**: `Alt + I` (Windows/Linux), `Ctrl + I` (MacOS)
- **向下移动光标**: `Alt + K` (Windows/Linux), `Ctrl + K` (MacOS)
- **向左移动光标**: `Alt + J` (Windows/Linux), `Ctrl + J` (MacOS)
- **向右移动光标**: `Alt + L` (Windows/Linux), `Ctrl + L` (MacOS)
- **向上翻页**: `Alt + U` (Windows/Linux), `Ctrl + U` (MacOS)
- **向下翻页**: `Alt + O` (Windows/Linux), `Ctrl + O` (MacOS)
- **移动到行首**: `Alt + H` (Windows/Linux), `Ctrl + H` (MacOS)
- **移动到行尾**: `Alt + ;` (Windows/Linux), `Ctrl + ;` (MacOS)
### 光标选择
- **向上选择光标**: `Alt + Shift + I` (Windows/Linux), `Ctrl + Shift + I` (MacOS)
- **向下选择光标**: `Alt + Shift + K` (Windows/Linux), `Ctrl + Shift + K` (MacOS)
- **向左选择光标**: `Alt + Shift + J` (Windows/Linux), `Ctrl + Shift + J` (MacOS)
- **向右选择光标**: `Alt + Shift + L` (Windows/Linux), `Ctrl + Shift + L` (MacOS)
- **选择到行首**: `Alt + Shift + H` (Windows/Linux), `Ctrl + Shift + H` (MacOS)
- **选择到行尾**: `Alt + Shift + ;` (Windows/Linux), `Ctrl + Shift + ;` (MacOS)
## 安装方法
### 方法一: 通过 Obsidian 设置安装
1. 打开 Obsidian 并进入 `设置`
2. 导航到 `第三方插件` 标签。
3. 点击 `浏览` 并搜索 `Quick Nav`
4. 点击 `安装` 然后 `启用`
### 方法二: 手动安装
1. 从 [GitHub 仓库](https://github.com/exoticknight/quick-nav/releases) 下载最新版本。
2. 解压 zip 文件内容。
3. 将解压后的文件夹复制到你的 Obsidian vault 的插件目录:`/path/to/your/vault/.obsidian/plugins/`。
4. 打开 Obsidian 并进入 `设置`
5. 导航到 `第三方插件` 标签并启用 `Quick Nav`

View file

@ -15,7 +15,7 @@ const context = await esbuild.context({
banner: { banner: {
js: banner, js: banner,
}, },
entryPoints: ["main.ts"], entryPoints: ["src/main.ts"],
bundle: true, bundle: true,
external: [ external: [
"obsidian", "obsidian",

134
main.ts
View file

@ -1,134 +0,0 @@
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
// Remember to rename these classes and interfaces!
interface MyPluginSettings {
mySetting: string;
}
const DEFAULT_SETTINGS: MyPluginSettings = {
mySetting: 'default'
}
export default class MyPlugin extends Plugin {
settings: MyPluginSettings;
async onload() {
await this.loadSettings();
// This creates an icon in the left ribbon.
const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => {
// Called when the user clicks the icon.
new Notice('This is a notice!');
});
// Perform additional things with the ribbon
ribbonIconEl.addClass('my-plugin-ribbon-class');
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
const statusBarItemEl = this.addStatusBarItem();
statusBarItemEl.setText('Status Bar Text');
// This adds a simple command that can be triggered anywhere
this.addCommand({
id: 'open-sample-modal-simple',
name: 'Open sample modal (simple)',
callback: () => {
new SampleModal(this.app).open();
}
});
// This adds an editor command that can perform some operation on the current editor instance
this.addCommand({
id: 'sample-editor-command',
name: 'Sample editor command',
editorCallback: (editor: Editor, view: MarkdownView) => {
console.log(editor.getSelection());
editor.replaceSelection('Sample Editor Command');
}
});
// This adds a complex command that can check whether the current state of the app allows execution of the command
this.addCommand({
id: 'open-sample-modal-complex',
name: 'Open sample modal (complex)',
checkCallback: (checking: boolean) => {
// Conditions to check
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (markdownView) {
// If checking is true, we're simply "checking" if the command can be run.
// If checking is false, then we want to actually perform the operation.
if (!checking) {
new SampleModal(this.app).open();
}
// This command will only show up in Command Palette when the check function returns true
return true;
}
}
});
// This adds a settings tab so the user can configure various aspects of the plugin
this.addSettingTab(new SampleSettingTab(this.app, this));
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
// Using this function will automatically remove the event listener when this plugin is disabled.
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
console.log('click', evt);
});
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
}
onunload() {
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
}
class SampleModal extends Modal {
constructor(app: App) {
super(app);
}
onOpen() {
const {contentEl} = this;
contentEl.setText('Woah!');
}
onClose() {
const {contentEl} = this;
contentEl.empty();
}
}
class SampleSettingTab 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('Setting #1')
.setDesc('It\'s a secret')
.addText(text => text
.setPlaceholder('Enter your secret')
.setValue(this.plugin.settings.mySetting)
.onChange(async (value) => {
this.plugin.settings.mySetting = value;
await this.plugin.saveSettings();
}));
}
}

View file

@ -1,11 +1,11 @@
{ {
"id": "sample-plugin", "id": "quick-nav",
"name": "Sample Plugin", "name": "Quick Nav",
"version": "1.0.0", "version": "1.0.0",
"minAppVersion": "0.15.0", "minAppVersion": "0.15.0",
"description": "Demonstrates some of the capabilities of the Obsidian API.", "description": "Swift cursor navigation plugin to enhance editing",
"author": "Obsidian", "author": "ExoticKnight",
"authorUrl": "https://obsidian.md", "authorUrl": "https://github.com/exoticknight",
"fundingUrl": "https://obsidian.md/pricing", "fundingUrl": "https://github.com/exoticknight",
"isDesktopOnly": false "isDesktopOnly": true
} }

2448
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,11 @@
{ {
"name": "obsidian-sample-plugin", "name": "quick-nav",
"version": "1.0.0", "version": "1.0.0",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)", "description": "Swift cursor navigation plugin to enhance editing",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"dev": "node esbuild.config.mjs", "dev": "node esbuild.config.mjs",
"lint": "eslint ./src/",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json" "version": "node version-bump.mjs && git add manifest.json versions.json"
}, },
@ -12,6 +13,7 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@codemirror/commands": "^6.8.0",
"@types/node": "^16.11.6", "@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0", "@typescript-eslint/parser": "5.29.0",

View file

@ -0,0 +1,114 @@
import type { Modifier } from "obsidian";
import type { Command } from "@codemirror/view";
import {
cursorCharLeft,
cursorCharRight,
cursorLineDown,
cursorLineUp,
cursorPageDown,
cursorPageUp,
cursorLineBoundaryLeft,
cursorLineBoundaryRight,
} from "@codemirror/commands";
import { Platform } from "src/constraints";
export const CursorMove: {
id: string;
name: string;
modifiers: {
[Platform.Windows]: Modifier[];
[Platform.MacOS]: Modifier[];
[Platform.Linux]: Modifier[];
};
key: string;
commandFn: Command;
}[] = [
{
id: "quick-nav-move-cursor-up",
name: "Move Cursor Up",
modifiers: {
[Platform.Windows]: ["Alt"],
[Platform.MacOS]: ["Ctrl"],
[Platform.Linux]: ["Alt"],
},
key: "I",
commandFn: cursorLineUp,
},
{
id: "quick-nav-move-cursor-down",
name: "Move Cursor Down",
modifiers: {
[Platform.Windows]: ["Alt"],
[Platform.MacOS]: ["Ctrl"],
[Platform.Linux]: ["Alt"],
},
key: "K",
commandFn: cursorLineDown,
},
{
id: "quick-nav-move-cursor-left",
name: "Move Cursor Left",
modifiers: {
[Platform.Windows]: ["Alt"],
[Platform.MacOS]: ["Ctrl"],
[Platform.Linux]: ["Alt"],
},
key: "J",
commandFn: cursorCharLeft,
},
{
id: "quick-nav-move-cursor-right",
name: "Move Cursor Right",
modifiers: {
[Platform.Windows]: ["Alt"],
[Platform.MacOS]: ["Ctrl"],
[Platform.Linux]: ["Alt"],
},
key: "L",
commandFn: cursorCharRight,
},
{
id: "quick-nav-move-cursor-page-up",
name: "Move Cursor Page Up",
modifiers: {
[Platform.Windows]: ["Alt"],
[Platform.MacOS]: ["Ctrl"],
[Platform.Linux]: ["Alt"],
},
key: "U",
commandFn: cursorPageUp,
},
{
id: "quick-nav-move-cursor-page-down",
name: "Move Cursor Page Down",
modifiers: {
[Platform.Windows]: ["Alt"],
[Platform.MacOS]: ["Ctrl"],
[Platform.Linux]: ["Alt"],
},
key: "O",
commandFn: cursorPageDown,
},
{
id: "quick-nav-move-cursor-left-end",
name: "Move Cursor Left End",
modifiers: {
[Platform.Windows]: ["Alt"],
[Platform.MacOS]: ["Ctrl"],
[Platform.Linux]: ["Alt"],
},
key: "H",
commandFn: cursorLineBoundaryLeft,
},
{
id: "quick-nav-move-cursor-right-end",
name: "Move Cursor Right End",
modifiers: {
[Platform.Windows]: ["Alt"],
[Platform.MacOS]: ["Ctrl"],
[Platform.Linux]: ["Alt"],
},
key: ";",
commandFn: cursorLineBoundaryRight,
},
];

View file

@ -0,0 +1,90 @@
import type { Modifier } from "obsidian";
import type { Command } from "@codemirror/view";
import {
selectCharLeft,
selectCharRight,
selectLineDown,
selectLineUp,
selectLineBoundaryLeft,
selectLineBoundaryRight,
} from "@codemirror/commands";
import { Platform } from "src/constraints";
export const CursorSelect: {
id: string;
name: string;
modifiers: {
[Platform.Windows]: Modifier[];
[Platform.MacOS]: Modifier[];
[Platform.Linux]: Modifier[];
};
key: string;
commandFn: Command;
}[] = [
{
id: "quick-nav-select-cursor-up",
name: "Select Cursor Up",
modifiers: {
[Platform.Windows]: ["Alt", "Shift"],
[Platform.MacOS]: ["Ctrl", "Shift"],
[Platform.Linux]: ["Alt", "Shift"],
},
key: "I",
commandFn: selectLineUp,
},
{
id: "quick-nav-select-cursor-down",
name: "Select Cursor Down",
modifiers: {
[Platform.Windows]: ["Alt", "Shift"],
[Platform.MacOS]: ["Ctrl", "Shift"],
[Platform.Linux]: ["Alt", "Shift"],
},
key: "K",
commandFn: selectLineDown,
},
{
id: "quick-nav-select-cursor-left",
name: "Select Cursor Left",
modifiers: {
[Platform.Windows]: ["Alt", "Shift"],
[Platform.MacOS]: ["Ctrl", "Shift"],
[Platform.Linux]: ["Alt", "Shift"],
},
key: "J",
commandFn: selectCharLeft,
},
{
id: "quick-nav-select-cursor-right",
name: "Select Cursor Right",
modifiers: {
[Platform.Windows]: ["Alt", "Shift"],
[Platform.MacOS]: ["Ctrl", "Shift"],
[Platform.Linux]: ["Alt", "Shift"],
},
key: "L",
commandFn: selectCharRight,
},
{
id: "quick-nav-select-cursor-left-end",
name: "Select Cursor Left End",
modifiers: {
[Platform.Windows]: ["Alt", "Shift"],
[Platform.MacOS]: ["Ctrl", "Shift"],
[Platform.Linux]: ["Alt", "Shift"],
},
key: "H",
commandFn: selectLineBoundaryLeft,
},
{
id: "quick-nav-select-cursor-right-end",
name: "Select Cursor Right End",
modifiers: {
[Platform.Windows]: ["Alt", "Shift"],
[Platform.MacOS]: ["Ctrl", "Shift"],
[Platform.Linux]: ["Alt", "Shift"],
},
key: ";",
commandFn: selectLineBoundaryRight,
},
];

View file

@ -0,0 +1,2 @@
export * from "./cursorMove";
export * from "./cursorSelect";

5
src/constraints.ts Normal file
View file

@ -0,0 +1,5 @@
export enum Platform {
Windows = "win",
MacOS = "mac",
Linux = "linux",
}

74
src/main.ts Normal file
View file

@ -0,0 +1,74 @@
import {
MarkdownView,
Modifier,
Plugin,
Platform as OBPlatform,
} from "obsidian";
import { Command, EditorView } from "@codemirror/view";
import { CursorMove, CursorSelect } from "./codeMirrorCommand";
import { Platform } from "./constraints";
export default class MyPlugin extends Plugin {
async onload() {
let platform: Platform = Platform.Windows;
if (OBPlatform.isMacOS) {
platform = Platform.MacOS;
}
if (OBPlatform.isLinux) {
platform = Platform.Linux;
}
CursorMove.forEach((cursorMove) => {
this.registerKeybindToCMCommand(
cursorMove.id,
cursorMove.name,
cursorMove.modifiers[platform],
cursorMove.key,
cursorMove.commandFn
);
});
CursorSelect.forEach((cursorSelect) => {
this.registerKeybindToCMCommand(
cursorSelect.id,
cursorSelect.name,
cursorSelect.modifiers[platform],
cursorSelect.key,
cursorSelect.commandFn
);
});
}
registerKeybindToCMCommand(
id: string,
name: string,
modifiers: Modifier[],
key: string,
commandFn: Command
) {
this.addCommand({
id,
name,
callback: () => this.getEditorViewToExecCommand(commandFn),
hotkeys: [
{
modifiers,
key,
},
],
});
}
getEditorViewToExecCommand(command: Command): boolean {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (view) {
// @ts-expect-error, not typed
const editorView = view.editor.cm as EditorView;
return command(editorView);
}
return false;
}
onunload() {}
}

View file

@ -1,8 +0,0 @@
/*
This CSS file will be included with your plugin, and
available in the app when your plugin is enabled.
If your plugin does not need CSS, delete this file.
*/