Skip to main content
obsidian-core-workflow-b Build advanced Obsidian plugin features: custom views (ItemView), modals,
editor commands with selection manipulation, status bar, context menus,
and Vault API file creation/modification. Use when adding UI components
to a plugin, building sidebar views, or creating modal dialogs.
Trigger with "obsidian modal", "obsidian custom view", "obsidian sidebar",
"obsidian context menu", "obsidian editor command", "obsidian UI".
Ir para a instalação Skills Marketplace Descubra e explore skills de IA criadas pela comunidade.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Copiar promptMostrar detalhes do prompt Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill obsidian-core-workflow-bO comando permanece em uma só linha. Role horizontalmente para revisá-lo antes de copiar.
Prefere uma cópia local? Baixe os arquivos disponíveis atualmente no SkillsMP.
Baixar Zip Baixando... Mais deste repositório Implement user sign-up and sign-in flows with Clerk.
Use when building authentication UI, customizing sign-in experience,
or implementing OAuth social login.
Trigger with phrases like "clerk sign-in", "clerk sign-up",
"clerk login flow", "clerk OAuth", "clerk social login".
Implement session management and middleware with Clerk.
Use when managing user sessions, configuring route protection,
or implementing token refresh and custom JWT templates.
Trigger with phrases like "clerk session", "clerk middleware",
"clerk route protection", "clerk token", "clerk JWT".
Configure enterprise SSO, role-based access control, and organization management.
Use when implementing SSO integration, configuring role-based permissions,
or setting up organization-level controls.
Trigger with phrases like "clerk SSO", "clerk RBAC",
"clerk enterprise", "clerk roles", "clerk permissions", "clerk organizations".
Explorador de arquivos
2 arquivos Ocupações relacionadas SOC
Baseado na classificação ocupacional SOC
name obsidian-core-workflow-b description Build advanced Obsidian plugin features: custom views (ItemView), modals,
editor commands with selection manipulation, status bar, context menus,
and Vault API file creation/modification. Use when adding UI components
to a plugin, building sidebar views, or creating modal dialogs.
Trigger with "obsidian modal", "obsidian custom view", "obsidian sidebar",
"obsidian context menu", "obsidian editor command", "obsidian UI".
allowed-tools Read, Write, Edit, Bash(npm:*), Grep version 1.13.0 license MIT author Jeremy Longshore <jeremy@intentsolutions.io> tags ["obsidian","ui-components","views","modals","editor"] compatibility Designed for Claude Code, also compatible with Codex and OpenClaw
Obsidian Core Workflow B: Advanced Plugin Features
Overview
Add production UI to an existing Obsidian plugin: custom sidebar views,
modal dialogs with forms, fuzzy-search suggestion popups, editor commands
that manipulate selections, status bar widgets, context menus, and
programmatic file creation via the Vault API. Every snippet is a complete,
copy-pasteable class.
Prerequisites
A working plugin built with obsidian-core-workflow-a (or equivalent)
npm install --save-dev obsidian already done
Familiarity with the Plugin lifecycle (onload / onunload)
Instructions
Step 1: Custom sidebar view (ItemView)
A custom view registers a new panel type that can live in the left or right sidebar.
import { ItemView , WorkspaceLeaf , TFile } from "obsidian" ;
export const STATS_VIEW_TYPE = "vault-stats-view" ;
export class StatsView extends ItemView {
constructor (leaf : WorkspaceLeaf ) {
super (leaf);
}
getViewType (): string {
return STATS_VIEW_TYPE ;
}
getDisplayText (): string {
return "Vault Stats" ;
}
getIcon (): string {
return "bar-chart-2" ;
}
( ) {
container = . . [ ];
container. ();
container. ( );
container. ( , { : });
listEl = container. ( );
files = . . . ();
totalWords = ;
( file files) {
content = . . . (file);
totalWords += content. ( ). ( ). ;
}
listEl. ( , { : });
listEl. ( , { : });
listEl. ( , {
: ,
});
btn = container. ( , { : });
btn. ( , . ());
}
( ) {
}
}
async
onOpen
const
this
containerEl
children
1
empty
addClass
"stats-view"
createEl
"h4"
text
"Vault Statistics"
const
createEl
"ul"
const
this
app
vault
getMarkdownFiles
let
0
for
const
of
const
await
this
app
vault
cachedRead
split
/\s+/
filter
Boolean
length
createEl
"li"
text
`Notes: ${files.length} `
createEl
"li"
text
`Total words: ${totalWords.toLocaleString()} `
createEl
"li"
text
`Avg words/note: ${files.length ? Math .round(totalWords / files.length) : 0 } `
const
createEl
"button"
text
"Refresh"
addEventListener
"click"
() =>
this
onOpen
async
onClose
Register and open it from your main plugin:
import { StatsView , STATS_VIEW_TYPE } from "./views/StatsView" ;
this .registerView (STATS_VIEW_TYPE , (leaf ) => new StatsView (leaf));
this .addCommand ({
id : "open-stats-view" ,
name : "Open vault stats" ,
callback : () => this .activateStatsView (),
});
this .addRibbonIcon ("bar-chart-2" , "Vault Stats" , () => this .activateStatsView ());
async activateStatsView ( ) {
const { workspace } = this .app ;
let leaf = workspace.getLeavesOfType (STATS_VIEW_TYPE )[0 ];
if (!leaf) {
const rightLeaf = workspace.getRightLeaf (false );
if (rightLeaf) {
await rightLeaf.setViewState ({ type : STATS_VIEW_TYPE , active : true });
leaf = rightLeaf;
}
}
if (leaf) workspace.revealLeaf (leaf);
}
this .app .workspace .detachLeavesOfType (STATS_VIEW_TYPE );
Step 2: Modal dialogs Confirmation modal -- returns a boolean via callback:
import { App , Modal , Setting } from "obsidian" ;
export class ConfirmModal extends Modal {
private resolved = false ;
constructor (
app : App ,
private message : string ,
private onResult : (confirmed: boolean ) => void
) {
super (app);
}
onOpen ( ) {
const { contentEl } = this ;
contentEl.createEl ("h3" , { text : "Confirm" });
contentEl.createEl ("p" , { text : this .message });
new Setting (contentEl)
.addButton ((btn ) =>
btn.setButtonText ("Cancel" ).onClick (() => this .close ())
)
.addButton ((btn ) =>
btn
.setButtonText ("Confirm" )
.setCta ()
.onClick (() => {
this .resolved = true ;
this .close ();
})
);
}
onClose ( ) {
this .onResult (this .resolved );
this .contentEl .empty ();
}
}
Text input modal -- collects a single string:
import { App , Modal , Setting } from "obsidian" ;
export class InputModal extends Modal {
private value = "" ;
constructor (
app : App ,
private title : string ,
private placeholder : string ,
private onSubmit : (value: string | null ) => void
) {
super (app);
}
onOpen ( ) {
const { contentEl } = this ;
contentEl.createEl ("h3" , { text : this .title });
new Setting (contentEl).addText ((text ) =>
text
.setPlaceholder (this .placeholder )
.onChange ((v ) => (this .value = v))
);
new Setting (contentEl)
.addButton ((btn ) =>
btn.setButtonText ("Cancel" ).onClick (() => {
this .onSubmit (null );
this .close ();
})
)
.addButton ((btn ) =>
btn
.setButtonText ("OK" )
.setCta ()
.onClick (() => {
this .onSubmit (this .value );
this .close ();
})
);
}
onClose ( ) {
this .contentEl .empty ();
}
}
Step 3: Fuzzy suggestion modal (SuggestModal) Opens a searchable list. Users type to filter, then pick an item.
import { App , FuzzySuggestModal , TFile } from "obsidian" ;
export class NotePicker extends FuzzySuggestModal <TFile > {
constructor (app : App , private onPick : (file: TFile) => void ) {
super (app);
}
getItems (): TFile [] {
return this .app .vault .getMarkdownFiles ();
}
getItemText (file : TFile ): string {
return file.path ;
}
onChooseItem (file : TFile ): void {
this .onPick (file);
}
}
this .addCommand ({
id : "pick-note" ,
name : "Pick a note" ,
callback : () => {
new NotePicker (this .app , (file ) => {
new Notice (`Selected: ${file.basename} ` );
}).open ();
},
});
Step 4: Editor commands with selection manipulation editorCallback gives you the CodeMirror Editor and the active MarkdownView.
this .addCommand ({
id : "wrap-callout" ,
name : "Wrap selection in callout" ,
editorCallback : (editor, view ) => {
const selection = editor.getSelection ();
if (!selection) {
new Notice ("Select text first" );
return ;
}
const callout = `> [!note]\n> ${selection.split("\n" ).join("\n> " )} ` ;
editor.replaceSelection (callout);
},
});
this .addCommand ({
id : "insert-timestamp" ,
name : "Insert timestamp" ,
editorCallback : (editor ) => {
const now = new Date ().toISOString ().slice (0 , 19 ).replace ("T" , " " );
editor.replaceSelection (now);
},
});
this .addCommand ({
id : "sort-lines" ,
name : "Sort selected lines" ,
editorCallback : (editor ) => {
const selection = editor.getSelection ();
if (!selection) return ;
const sorted = selection.split ("\n" ).sort ((a, b ) => a.localeCompare (b)).join ("\n" );
editor.replaceSelection (sorted);
},
});
Step 5: Status bar items Status bar items sit at the bottom of the Obsidian window.
const statusEl = this .addStatusBarItem ();
statusEl.setText ("Words: --" );
this .registerEvent (
this .app .workspace .on ("active-leaf-change" , async () => {
const view = this .app .workspace .getActiveViewOfType (MarkdownView );
if (view) {
const content = view.editor .getValue ();
const count = content.split (/\s+/ ).filter (Boolean ).length ;
statusEl.setText (`Words: ${count} ` );
} else {
statusEl.setText ("Words: --" );
}
})
);
Step 6: Context menus Add items to the file explorer right-click menu and the editor right-click menu.
this .registerEvent (
this .app .workspace .on ("file-menu" , (menu, file ) => {
if (file instanceof TFile && file.extension === "md" ) {
menu.addItem ((item ) => {
item
.setTitle ("Copy note title" )
.setIcon ("clipboard-copy" )
.onClick (async () => {
await navigator.clipboard .writeText (file.basename );
new Notice (`Copied: ${file.basename} ` );
});
});
}
})
);
this .registerEvent (
this .app .workspace .on ("editor-menu" , (menu, editor ) => {
menu.addItem ((item ) => {
item
.setTitle ("Insert today's date" )
.setIcon ("calendar" )
.onClick (() => {
const today = new Date ().toISOString ().slice (0 , 10 );
editor.replaceSelection (today);
});
});
})
);
Step 7: File creation and modification via Vault API Programmatically create, read, and modify notes.
async ensureDailyNote (): Promise <TFile > {
const today = new Date ().toISOString ().slice (0 , 10 );
const path = `Daily/${today} .md` ;
const existing = this .app .vault .getAbstractFileByPath (path);
if (existing instanceof TFile ) return existing;
const folder = this .app .vault .getAbstractFileByPath ("Daily" );
if (!folder) await this .app .vault .createFolder ("Daily" );
const content = `# ${today} \n\n## Tasks\n\n- [ ] \n\n## Notes\n\n` ;
return this .app .vault .create (path, content);
}
async appendToNote (file : TFile , text : string ): Promise <void > {
const current = await this .app .vault .read (file);
await this .app .vault .modify (file, current + "\n" + text);
}
async addTagToFolder (folder : string , tag : string ): Promise <number > {
const files = this .app .vault .getMarkdownFiles ()
.filter ((f ) => f.path .startsWith (folder + "/" ));
let count = 0 ;
for (const file of files) {
let content = await this .app .vault .read (file);
if (content.startsWith ("---" )) {
content = content.replace (
/^(---\n[\s\S]*?)(---)$/m ,
`$1tags:\n - ${tag} \n$2`
);
} else {
content = `---\ntags:\n - ${tag} \n---\n${content} ` ;
}
await this .app .vault .modify (file, content);
count++;
}
return count;
}
Output After applying these patterns your plugin gains:
A sidebar panel (ItemView) with live vault statistics
Confirmation and text-input modal dialogs
A fuzzy-search note picker
Editor commands that transform selected text
A live word-count status bar widget
Right-click context menu extensions
Vault API helpers for file creation and batch modification
Error Handling Error Cause Fix View not appearing Forgot registerView in onload Must register before opening getRightLeaf returns nullNo sidebar available Guard with if (leaf) Modal closes without callback Event order issue Set result before calling this.close() editorCallback greyed outNo active markdown editor Use callback instead for non-editor commands Context menu item missing Wrong event name file-menu for explorer, editor-menu for editorvault.create throwsFile already exists at path Check with getAbstractFileByPath first Stale cachedRead data Cache not yet updated Use vault.read when freshness matters
Examples Open a view in a new tab instead of sidebar:
const leaf = this .app .workspace .getLeaf ("tab" );
await leaf.setViewState ({ type : STATS_VIEW_TYPE , active : true });
Promise-based confirm modal:
function confirm (app : App , msg : string ): Promise <boolean > {
return new Promise ((resolve ) => {
new ConfirmModal (app, msg, resolve).open ();
});
}
if (await confirm (this .app , "Delete all empty notes?" )) {
}
Resources
Next Steps
Set up hot-reload development: see obsidian-local-dev-loop
Apply production safety patterns: see obsidian-sdk-patterns
Handle common errors: see obsidian-common-errors