- Target: Obsidian Community Plugin (TypeScript → bundled JavaScript).
- Entry point:
src/main.tscompiled tomain.jsand loaded by Obsidian. - Required release artifacts:
main.js,manifest.json, and optionalstyles.css.
- Node.js: use current LTS (Node 18+ recommended).
- Package manager: npm (required -
package.jsondefines npm scripts and dependencies). - Bundler: esbuild (required -
esbuild.config.mjsand build scripts depend on it). - Types:
obsidiantype definitions.
npm installnpm run dev:esbuildnpm run build:esbuild- ESLint is preconfigured.
- Run
npm run lint(if available) to lint the project.
- Source lives in
src/. Keepmain.tssmall and focused on plugin lifecycle (loading, unloading, registering commands). - Current structure:
src/ main.ts # Plugin entry point, lifecycle management types.ts # TypeScript interfaces and types constants.ts # Default settings, static data l10n.ts # Localization (t() function) util.tsx # Utility functions and Preact helpers manager/commands/ # Feature managers (one per UI location) commandManager.ts # Abstract base class leftRibbonManager.ts statusBarManager.ts pageHeaderManager.ts menuManager.ts explorerManager.ts textToolbarManager.ts # Text selection toolbar (new) index.ts ui/ settingTab.ts # Registers settings tab with Obsidian settingTabModal.ts icons.ts addCommandModal.ts chooseIconModal.ts chooseCustomNameModal.ts confirmDeleteModal.ts components/ # Preact components settingTabComponent.tsx # Tab container, tab list commandViewerComponent.tsx # Reusable command list with add/remove/reorder commandComponent.tsx # Single command row hidingViewer.tsx # Eye-toggle accordions for hiding native items settingComponent.tsx # ToggleComponent, SliderComponent, EyeToggleComponent TextToolbarSettings.tsx # Text toolbar tab content (new) AdvancedToolbarSettings.tsx MacroViewer.tsx MacroBuilder.tsx Accordion.tsx About.tsx styles/ styles.scss advanced-toolbar.scss locale/ # Translation JSON files (en.json is canonical) - Do not commit build artifacts: Never commit
node_modules/,main.js, or other generated files.
Each UI location (ribbon, status bar, page header, editor menu, etc.) has a manager class that extends CommandManagerBase. Managers:
- Own the
CommandIconPair[]array for their location - Implement
addCommand,removeCommand,reorder - Handle DOM creation and updates for their location
- Are initialized in
main.tsonload()and stored onplugin.manager
Settings are persisted via plugin.loadData() / plugin.saveData(). The CommanderSettings interface in types.ts is the source of truth. Always add a nullish guard in onload() for any new optional array field so that users upgrading from older versions don't get errors:
this.settings.hide.textToolbar ??= [];All user-visible strings should go through t() from src/l10n.ts. The canonical locale is locale/en.json. If you add a new UI string, add it to en.json (and ideally other locale files). Strings not in the locale fall back to en.json; if not found there either, t() returns undefined, which renders as blank text.
The settings UI uses Preact (not React). Components live in src/ui/components/. Reuse existing components:
ToggleComponent/SliderComponent/EyeToggleComponentfromsettingComponent.tsxCommandViewerfromcommandViewerComponent.tsx— takes aCommandManagerBase, renders the command list with add/remove/reorderAccordionfromAccordion.tsx
To add a new settings tab:
- Create a Preact component in
src/ui/components/ - Add an entry to the
tabsarray insettingTabComponent.tsx - The tab name must be a plain string (not
undefined) — if usingt(), ensure the key exists inen.json
- Types: Add fields to
CommanderSettingsinsrc/types.ts - Defaults: Add corresponding defaults in
DEFAULT_SETTINGSinsrc/constants.ts - Manager: Create
src/manager/commands/<feature>Manager.tsextendingCommandManagerBase; export fromindex.ts - main.ts: Add nullish guard for any new array fields; instantiate manager; add to
plugin.manager - Settings UI: Create component in
src/ui/components/; add tab tosettingTabComponent.tsx - Locale: Add any new UI strings to
locale/en.json - Styles: Add CSS classes to
src/styles/styles.scssusingcmdr-prefix
The Text Toolbar (src/manager/commands/textToolbarManager.ts) shows a floating action bar above selected text in a markdown source editor. Key implementation notes:
- Enabled/disabled via
plugin.settings.textToolbarEnabled - Always instantiated; event handlers check the setting at runtime
- Toolbar element is appended to
document.bodywithposition: fixed - Uses
mousedown+e.preventDefault()on buttons to keep editor focus and preserve selection - Checks that selection is inside
.cm-editorbefore showing (prevents showing in UI panels) - Desktop-only: mobile guard via
Platform.isMobilein the manager - Default commands (Bold, Italic, Strikethrough, Copy, Cut) can be individually hidden via
settings.hide.textToolbar - User-added commands are
CommandIconPair[]stored insettings.textToolbar
id,name,version(SemVer),minAppVersion,description,isDesktopOnlyare required- Never change
idafter release - Keep
minAppVersionaccurate when using newer APIs
- Manual install for testing: copy
main.js,manifest.json,styles.css(if any) to:<Vault>/.obsidian/plugins/<plugin-id>/ - Reload Obsidian and enable the plugin in Settings → Community plugins
- This plugin's source folder IS the installed plugin folder (Dev Vault), so building in place is sufficient — just reload Obsidian after
npm run build:esbuild
Follow Obsidian's Developer Policies and Plugin Guidelines:
- Default to local/offline operation. Only make network requests when essential.
- No hidden telemetry.
- Never execute remote code or auto-update plugin code outside of normal releases.
- Register and clean up all DOM, app, and interval listeners using
register*helpers.
- Keep startup light. Defer heavy work until
onLayoutReady. - Debounce expensive operations (e.g. selection-change handlers).
- Batch disk access; avoid excessive vault scans.
- TypeScript; existing codebase uses
"strict": falsebut prefer strict-compatible code. - CSS class prefix:
cmdr-for all plugin-owned classes. - Keep
main.tsminimal — delegate all feature logic to managers and UI components. this.register*helpers for everything that needs cleanup.- Prefer
async/await; handle errors gracefully.
- Obsidian sample plugin: https://github.com/obsidianmd/obsidian-sample-plugin
- API documentation: https://docs.obsidian.md
- Developer policies: https://docs.obsidian.md/Developer+policies
- Plugin guidelines: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines