commit da398b658b4c2b7f2eb9268cc9890babc682f9cd Author: Graham Date: Thu Mar 20 15:04:13 2025 -0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9b203a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +main.js diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..473de1b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,18 @@ +Copyright 2025 Graham Barber + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f40ddfc --- /dev/null +++ b/README.md @@ -0,0 +1,137 @@ +# Obsidian Sample Plugin (Deno) + +A nearly-exact port of the original Obsidian sample plugin, but using Deno +instead of Node.js and npm. The plugin files have been copied exactly, but the +helper functions have been moved into Obsideno, a Deno-based CLI helper for +writing Obsidian plugins. Its functions include: + +- Building the plugin code using Vite instead of ESBuild directly +- Bumping versions with a command, using patch/minor/major Semver language +- Installing and uninstalling your plugin from your test vault, using symlinks + for quick development + +Both this repo and Obsideno are pretty barebones, but that's what felt right for +the moment. I just wanted to be able to make new plugins quickly. + +**The original README.md contents:** + +This is a sample plugin for Obsidian (https://obsidian.md). + +This project uses TypeScript to provide type checking and documentation. 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. + +- 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? + +Quick starting guide for new plugin devs: + +- 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. +- 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 + +- Update your `manifest.json` with your new version number, such as `1.0.1`, and + the minimum Obsidian version required for your latest release. +- 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`. 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 + +- Check the + [plugin guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines). +- 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 + +- Clone this repo. +- Make sure your NodeJS is at least v16 (`node --version`). +- `npm i` or `yarn` to install dependencies. +- `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 diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..844bc4d --- /dev/null +++ b/deno.json @@ -0,0 +1,19 @@ +{ + "tasks": { + "dev": "obsideno build --watch", + "build": "obsideno build --production", + "install": "obsideno install", + "uninstall": "obsideno uninstall", + "bump": "obsideno bump" + }, + "compilerOptions": { + "noImplicitAny": true, + "strictNullChecks": true, + "lib": [ + "DOM", + "ES5", + "ES6", + "ES7" + ] + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..f7dfcfd --- /dev/null +++ b/deno.lock @@ -0,0 +1,58 @@ +{ + "version": "4", + "specifiers": { + "npm:obsidian@*": "1.8.7_@codemirror+state@6.5.2_@codemirror+view@6.36.4" + }, + "npm": { + "@codemirror/state@6.5.2": { + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "dependencies": [ + "@marijn/find-cluster-break" + ] + }, + "@codemirror/view@6.36.4": { + "integrity": "sha512-ZQ0V5ovw/miKEXTvjgzRyjnrk9TwriUB1k4R5p7uNnHR9Hus+D1SXHGdJshijEzPFjU25xea/7nhIeSqYFKdbA==", + "dependencies": [ + "@codemirror/state", + "style-mod", + "w3c-keyname" + ] + }, + "@marijn/find-cluster-break@1.0.2": { + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==" + }, + "@types/codemirror@5.60.8": { + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", + "dependencies": [ + "@types/tern" + ] + }, + "@types/estree@1.0.6": { + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + }, + "@types/tern@0.23.9": { + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dependencies": [ + "@types/estree" + ] + }, + "moment@2.29.4": { + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + }, + "obsidian@1.8.7_@codemirror+state@6.5.2_@codemirror+view@6.36.4": { + "integrity": "sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA==", + "dependencies": [ + "@codemirror/state", + "@codemirror/view", + "@types/codemirror", + "moment" + ] + }, + "style-mod@4.1.2": { + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" + }, + "w3c-keyname@2.2.8": { + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + } + } +} diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..c54ab50 --- /dev/null +++ b/main.ts @@ -0,0 +1,147 @@ +import { + App, + MarkdownView, + Modal, + Notice, + Plugin, + PluginSettingTab, + Setting, +} from "npm: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; + + override async onload() { + await this.loadSettings(); + + // This creates an icon in the left ribbon. + const ribbonIconEl = this.addRibbonIcon("dice", "Sample Plugin", (_) => { + // 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, _) => { + 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( + setInterval(() => console.log("setInterval"), 5 * 60 * 1000), + ); + } + + override 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); + } + + override onOpen() { + const { contentEl } = this; + contentEl.setText("Woah!"); + } + + override 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(); + }) + ); + } +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..d8feb6a --- /dev/null +++ b/manifest.json @@ -0,0 +1,11 @@ +{ + "id": "sample-plugin", + "name": "Sample Plugin", + "version": "1.0.0", + "minAppVersion": "0.15.0", + "description": "Demonstrates some of the capabilities of the Obsidian API.", + "author": "Obsidian", + "authorUrl": "https://obsidian.md", + "fundingUrl": "https://obsidian.md/pricing", + "isDesktopOnly": false +} diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..af9a39e --- /dev/null +++ b/versions.json @@ -0,0 +1,3 @@ +{ + "1.0.0": "0.15.0" +}