diff --git a/README.md b/README.md index 9e4fe9c..515e1ad 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,21 @@ -## Obsidian Sample Plugin +# Obsidian Customizable Sidebar Plugin [![GitHub tag (Latest by date)](https://img.shields.io/github/v/tag/phibr0/obsidian-customizable-sidebar)](https://github.com/phibr0/obsidian-customizable-sidebar/releases) ![GitHub all releases](https://img.shields.io/github/downloads/phibr0/obsidian-customizable-sidebar/total) -This is a sample plugin for Obsidian (https://obsidian.md). +This Plugin allows you to add any Command, including those of Plugins, to the Sidebar and assign Custom Icons to them. -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. +## Custom Icons -**Note:** The Obsidian API is still in early alpha and is subject to change at any time! +If the Command doesn't yet have any Icon, you can select from a Range of Icons, including Obsidian's internal Icons and all Feather Icons. -This sample plugin demonstrates some of the basic functionality the plugin API can do. -- Changes the default font color to red using `styles.css`. -- 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? +## How to install -Quick starting guide for new plugin devs: +1. Go to **Community Plugins** in your [Obsidian](https://www.obsidian.md) Settings and **disable** Safe Mode +2. Click on **Browse** and search for „Obsidian Customizable Sidebar“ +3. Click install +4. Toggle the Plugin on in the **Community Plugins** Tab -- 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. +## Support me -### Releasing new releases +If you find this Plugin helpful, consider supporting me: -- 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. -- Publish the release. - -### Adding your plugin to the community plugin list - -- 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. -- `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/`. - -### API Documentation - -See https://github.com/obsidianmd/obsidian-api + diff --git a/main.ts b/main.ts deleted file mode 100644 index eeb3dda..0000000 --- a/main.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { App, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; - -interface MyPluginSettings { - mySetting: string; -} - -const DEFAULT_SETTINGS: MyPluginSettings = { - mySetting: 'default' -} - -export default class MyPlugin extends Plugin { - settings: MyPluginSettings; - - async onload() { - console.log('loading plugin'); - - await this.loadSettings(); - - this.addRibbonIcon('dice', 'Sample Plugin', () => { - new Notice('This is a notice!'); - }); - - this.addStatusBarItem().setText('Status Bar Text'); - - this.addCommand({ - id: 'open-sample-modal', - name: 'Open Sample Modal', - // callback: () => { - // console.log('Simple Callback'); - // }, - checkCallback: (checking: boolean) => { - let leaf = this.app.workspace.activeLeaf; - if (leaf) { - if (!checking) { - new SampleModal(this.app).open(); - } - return true; - } - return false; - } - }); - - this.addSettingTab(new SampleSettingTab(this.app, this)); - - this.registerCodeMirror((cm: CodeMirror.Editor) => { - console.log('codemirror', cm); - }); - - this.registerDomEvent(document, 'click', (evt: MouseEvent) => { - console.log('click', evt); - }); - - this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000)); - } - - onunload() { - console.log('unloading plugin'); - } - - 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() { - let {contentEl} = this; - contentEl.setText('Woah!'); - } - - onClose() { - let {contentEl} = this; - contentEl.empty(); - } -} - -class SampleSettingTab extends PluginSettingTab { - plugin: MyPlugin; - - constructor(app: App, plugin: MyPlugin) { - super(app, plugin); - this.plugin = plugin; - } - - display(): void { - let {containerEl} = this; - - containerEl.empty(); - - containerEl.createEl('h2', {text: 'Settings for my awesome plugin.'}); - - new Setting(containerEl) - .setName('Setting #1') - .setDesc('It\'s a secret') - .addText(text => text - .setPlaceholder('Enter your secret') - .setValue('') - .onChange(async (value) => { - console.log('Secret: ' + value); - this.plugin.settings.mySetting = value; - await this.plugin.saveSettings(); - })); - } -} diff --git a/manifest.json b/manifest.json index 4ca4889..4f8865b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { - "id": "obsidian-sample-plugin", - "name": "Sample Plugin", - "version": "1.0.1", - "minAppVersion": "0.9.12", - "description": "This is a sample plugin for Obsidian. This plugin demonstrates some of the capabilities of the Obsidian API.", - "author": "Obsidian", + "id": "customizable-sidebar", + "name": "Customizable Sidebar", + "version": "0.0.1", + "minAppVersion": "0.12.11", + "description": "This Plugin allows to add any Command to Obsidian's Sidebar Ribbon.", + "author": "phibr0", "authorUrl": "https://obsidian.md/about", "isDesktopOnly": false } diff --git a/package.json b/package.json index 29e2406..6543fb0 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,14 @@ "@rollup/plugin-commonjs": "^18.0.0", "@rollup/plugin-node-resolve": "^11.2.1", "@rollup/plugin-typescript": "^8.2.1", + "@types/feather-icons": "^4.7.0", "@types/node": "^14.14.37", "obsidian": "^0.12.0", "rollup": "^2.32.1", "tslib": "^2.2.0", "typescript": "^4.2.4" + }, + "dependencies": { + "feather-icons": "^4.28.0" } } diff --git a/rollup.config.js b/rollup.config.js index dd4d041..8e28e8d 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -12,7 +12,7 @@ if you want to view the source visit the plugins github repository `; export default { - input: 'main.ts', + input: 'src/main.ts', output: { dir: '.', sourcemap: 'inline', diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..6b83012 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,37 @@ +import { App, Command, FuzzyMatch, FuzzySuggestModal, Modal, Notice, Plugin, PluginSettingTab, setIcon, Setting } from 'obsidian'; +import { addFeatherIcons } from './ui/icons'; +import CustomSidebarSettingsTab, { CustomSidebarSettings, DEFAULT_SETTINGS } from './ui/settingsTab'; + +export default class CustomSidebarPlugin extends Plugin { + settings: CustomSidebarSettings; + iconList: string[] = ["any-key", "audio-file", "blocks", "bold-glyph", "bracket-glyph", "broken-link", "bullet-list", "bullet-list-glyph", "calendar-with-checkmark", "check-in-circle", "check-small", "checkbox-glyph", "checkmark", "clock", "cloud", "code-glyph", "create-new", "cross", "cross-in-box", "crossed-star", "csv", "deleteColumn", "deleteRow", "dice", "document", "documents", "dot-network", "double-down-arrow-glyph", "double-up-arrow-glyph", "down-arrow-with-tail", "down-chevron-glyph", "enter", "exit-fullscreen", "expand-vertically", "filled-pin", "folder", "formula", "forward-arrow", "fullscreen", "gear", "go-to-file", "hashtag", "heading-glyph", "help", "highlight-glyph", "horizontal-split", "image-file", "image-glyph", "indent-glyph", "info", "insertColumn", "insertRow", "install", "italic-glyph", "keyboard-glyph", "languages", "left-arrow", "left-arrow-with-tail", "left-chevron-glyph", "lines-of-text", "link", "link-glyph", "logo-crystal", "magnifying-glass", "microphone", "microphone-filled", "minus-with-circle", "moveColumnLeft", "moveColumnRight", "moveRowDown", "moveRowUp", "note-glyph", "number-list-glyph", "open-vault", "pane-layout", "paper-plane", "paused", "pdf-file", "pencil", "percent-sign-glyph", "pin", "plus-with-circle", "popup-open", "presentation", "price-tag-glyph", "quote-glyph", "redo-glyph", "reset", "right-arrow", "right-arrow-with-tail", "right-chevron-glyph", "right-triangle", "run-command", "search", "sheets-in-box", "sortAsc", "sortDesc", "spreadsheet", "stacked-levels", "star", "star-list", "strikethrough-glyph", "switch", "sync", "sync-small", "tag-glyph", "three-horizontal-bars", "trash", "undo-glyph", "unindent-glyph", "up-and-down-arrows", "up-arrow-with-tail", "up-chevron-glyph", "uppercase-lowercase-a", "vault", "vertical-split", "vertical-three-dots", "wrench-screwdriver-glyph"]; + + async onload() { + console.log('loading plugin'); + + await this.loadSettings(); + + this.addSettingTab(new CustomSidebarSettingsTab(this.app, this)); + + addFeatherIcons(this.iconList); + + this.settings.sidebarCommands.forEach(c => { + this.addRibbonIcon(c.icon, c.name, () => { + //@ts-ignore + this.app.commands.executeCommandById(this.command.id); + }); + }) + } + + onunload() { + console.log('unloading plugin'); + } + + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + + async saveSettings() { + await this.saveData(this.settings); + } +} \ No newline at end of file diff --git a/src/ui/commandSuggester.ts b/src/ui/commandSuggester.ts new file mode 100644 index 0000000..4ff06d1 --- /dev/null +++ b/src/ui/commandSuggester.ts @@ -0,0 +1,36 @@ +import { FuzzySuggestModal, Command } from "obsidian"; +import CustomSidebarPlugin from "src/main"; +import IconPicker from "./iconPicker"; + +export default class CommandSuggester extends FuzzySuggestModal { + + constructor(private plugin: CustomSidebarPlugin) { + super(plugin.app); + } + + getItems(): Command[] { + //@ts-ignore + return this.app.commands.listCommands(); + } + + getItemText(item: Command): string { + return item.name; + } + + async onChooseItem(item: Command, evt: MouseEvent | KeyboardEvent): Promise { + if (item.icon) { + this.plugin.addRibbonIcon(item.icon ?? "", item.name, () => { + //@ts-ignore + this.app.commands.executeCommandById(item.id); + }) + this.plugin.settings.sidebarCommands.push(item); + await this.plugin.saveSettings(); + setTimeout(() => { + dispatchEvent(new Event("CS-addedCommand")); + }, 100); + } else { + new IconPicker(this.plugin, item).open() + } + } + +} \ No newline at end of file diff --git a/src/ui/iconPicker.ts b/src/ui/iconPicker.ts new file mode 100644 index 0000000..ad4f218 --- /dev/null +++ b/src/ui/iconPicker.ts @@ -0,0 +1,52 @@ +import { FuzzySuggestModal, Command, FuzzyMatch, setIcon } from "obsidian"; +import CustomSidebarPlugin from "src/main"; + +export default class IconPicker extends FuzzySuggestModal{ + plugin: CustomSidebarPlugin; + command: Command; + + constructor(plugin: CustomSidebarPlugin, command: Command) { + super(plugin.app); + this.plugin = plugin; + this.command = command; + this.setPlaceholder("Pick an Icon"); + } + + private cap(string: string): string { + const words = string.split(" "); + + return words.map((word) => { + return word[0].toUpperCase() + word.substring(1); + }).join(" "); + } + + getItems(): string[] { + return this.plugin.iconList; + } + + getItemText(item: string): string { + return this.cap(item.replace("feather-", "").replace(/-/ig, " ")); + } + + renderSuggestion(item: FuzzyMatch, el: HTMLElement): void { + el.addClass("CS-icon-container"); + const div = createDiv({ cls: "CS-icon" }); + el.appendChild(div); + setIcon(div, item.item); + super.renderSuggestion(item, el); + } + + async onChooseItem(item: string): Promise { + this.plugin.addRibbonIcon(item, this.command.name, () => { + //@ts-ignore + this.app.commands.executeCommandById(this.command.id); + }) + this.command.icon = item; + this.plugin.settings.sidebarCommands.push(this.command); + await this.plugin.saveSettings(); + setTimeout(() => { + dispatchEvent(new Event("CS-addedCommand")); + }, 100); + } + +} diff --git a/src/ui/icons.ts b/src/ui/icons.ts new file mode 100644 index 0000000..e424586 --- /dev/null +++ b/src/ui/icons.ts @@ -0,0 +1,11 @@ +import * as feather from "feather-icons"; +import { addIcon } from "obsidian"; + +export function addFeatherIcons(iconList: string[]) { + Object.values(feather.icons).forEach((i) => { + const svg = i.toSvg({viewBox: "0 0 24 24", width: "100", height: "100"}); + //Remove the svg tag: svg.match(/(?<=>).*(?=<\/svg>)/).first() + addIcon("feather-" + i.name, svg); + iconList.push("feather-" + i.name); + }); +} \ No newline at end of file diff --git a/src/ui/settingsTab.ts b/src/ui/settingsTab.ts new file mode 100644 index 0000000..d5f3573 --- /dev/null +++ b/src/ui/settingsTab.ts @@ -0,0 +1,70 @@ +import { PluginSettingTab, App, Setting, setIcon, Command, Notice } from "obsidian"; +import CustomSidebarPlugin from "src/main"; +import CommandSuggester from "./commandSuggester"; + +export interface CustomSidebarSettings { + sidebarCommands: Command[]; +} + +export const DEFAULT_SETTINGS: CustomSidebarSettings = { + sidebarCommands: [], +} + +export default class CustomSidebarSettingsTab extends PluginSettingTab { + plugin: CustomSidebarPlugin; + + constructor(app: App, plugin: CustomSidebarPlugin) { + super(app, plugin); + this.plugin = plugin; + addEventListener("CS-addedCommand", () => { + this.display(); + }); + } + + display(): void { + let { containerEl } = this; + + containerEl.empty(); + + containerEl.createEl('h2', { text: 'Customizable Sidebar Settings' }); + + new Setting(containerEl) + .setName("Add Command to Sidebar") + .setDesc("Add a new Command to the left Sidebar Ribbon") + .addButton((bt) => { + bt.setButtonText("Add Command") + .onClick(() => { + new CommandSuggester(this.plugin).open(); + }); + }); + + this.plugin.settings.sidebarCommands.forEach(c => { + const iconDiv = createDiv({ cls: "CS-settings-icon" }); + setIcon(iconDiv, c.icon, 20); + const setting = new Setting(containerEl) + .setName(c.name) + .addButton(bt => { + bt.setButtonText("Remove Command") + .onClick(async () => { + this.plugin.settings.sidebarCommands.remove(c); + await this.plugin.saveSettings(); + this.display(); + new Notice("You will need to restart Obsidian for the Command to dissapear.") + }) + }); + setting.nameEl.prepend(iconDiv); + setting.nameEl.addClass("CS-flex"); + }) + + new Setting(containerEl) + .setName('Donate') + .setDesc('If you like this Plugin, consider donating to support continued development:') + .setClass("AT-extra") + .addButton((bt) => { + bt.buttonEl.outerHTML = ``; + }); + } +} + + + diff --git a/styles.css b/styles.css index cfd0fd7..3391602 100644 --- a/styles.css +++ b/styles.css @@ -1,4 +1,27 @@ -/* Sets all the text color to red! */ -body { - color: red; +/*Icon Picker*/ +.CS-icon { + transform: translateY(3px); + margin-right: 8px; } + +.CS-icon-container{ + display: flex; +} + +.CS-settings-icon { + height: 20px; + margin-right: 0.5rem; + transform: translateY(2px); +} + +.CS-flex { + display: flex; +} + +a[href="https://www.buymeacoffee.com/phibr0"] > img { + height: 2.2em; +} + +a[href="https://www.buymeacoffee.com/phibr0"]{ + transform: translate(0, 5%); +} \ No newline at end of file diff --git a/versions.json b/versions.json index ba14785..9e26dfe 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1 @@ -{ - "1.0.1": "0.9.12", - "1.0.0": "0.9.7" -} +{} \ No newline at end of file