obsidian-dev-skills 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/obsidian-dev-plugins/SKILL.md +35 -0
- package/obsidian-dev-plugins/references/agent-dos-donts.md +57 -0
- package/obsidian-dev-plugins/references/code-patterns.md +852 -0
- package/obsidian-dev-plugins/references/coding-conventions.md +21 -0
- package/obsidian-dev-plugins/references/commands-settings.md +24 -0
- package/obsidian-dev-plugins/references/common-tasks.md +429 -0
- package/obsidian-dev-themes/SKILL.md +34 -0
- package/obsidian-dev-themes/references/theme-best-practices.md +50 -0
- package/obsidian-dev-themes/references/theme-coding-conventions.md +45 -0
- package/package.json +11 -3
- package/scripts/init.mjs +134 -0
- package/scripts/setup-local.ps1 +52 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Source: Based on Obsidian Sample Plugin and TypeScript best practices
|
|
3
|
+
Last synced: See sync-status.json for authoritative sync dates
|
|
4
|
+
Update frequency: Check Obsidian Sample Plugin repo for updates
|
|
5
|
+
Applicability: Plugin
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Coding conventions
|
|
9
|
+
|
|
10
|
+
**Note**: This file is specific to plugin development (TypeScript). For theme development, see CSS/SCSS best practices in other files.
|
|
11
|
+
|
|
12
|
+
- TypeScript with `"strict": true` preferred.
|
|
13
|
+
- **Avoid `any` type**: Use proper types, `unknown`, or type assertions instead. `any` defeats TypeScript's type safety benefits.
|
|
14
|
+
- **Keep `main.ts` minimal**: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules.
|
|
15
|
+
- **Split large files**: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules.
|
|
16
|
+
- **Use clear module boundaries**: Each file should have a single, well-defined responsibility.
|
|
17
|
+
- Bundle everything into `main.js` (no unbundled runtime deps).
|
|
18
|
+
- Avoid Node/Electron APIs if you want mobile compatibility; set `isDesktopOnly` accordingly.
|
|
19
|
+
- Prefer `async/await` over promise chains; handle errors gracefully.
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Source: Based on Obsidian Sample Plugin and community plugin guidelines
|
|
3
|
+
Last synced: See sync-status.json for authoritative sync dates
|
|
4
|
+
Update frequency: Check Obsidian Sample Plugin repo for updates
|
|
5
|
+
Applicability: Plugin
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Commands & settings
|
|
9
|
+
|
|
10
|
+
**Note**: This file is specific to plugin development. Themes do not have commands or settings.
|
|
11
|
+
|
|
12
|
+
- Any user-facing commands should be added via `this.addCommand(...)`.
|
|
13
|
+
- If the plugin has configuration, provide a settings tab and sensible defaults.
|
|
14
|
+
- Persist settings using `this.loadData()` / `this.saveData()`.
|
|
15
|
+
- Use stable command IDs; avoid renaming once released.
|
|
16
|
+
|
|
17
|
+
## Version Considerations
|
|
18
|
+
|
|
19
|
+
When using newer API features (e.g., `SettingGroup` since API 1.11.0), consider backward compatibility:
|
|
20
|
+
- **For new plugins**: You can set `minAppVersion: "1.11.0"` in `manifest.json` and use the feature directly
|
|
21
|
+
- **For existing plugins**: Use version checking with `requireApiVersion()` to support both newer and older Obsidian versions
|
|
22
|
+
- See [code-patterns.md](code-patterns.md) for backward compatibility patterns, including a complete example for `SettingGroup`
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Source: Based on Obsidian Sample Plugin
|
|
3
|
+
Last synced: See sync-status.json for authoritative sync dates
|
|
4
|
+
Update frequency: Check Obsidian Sample Plugin repo for updates
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# Common tasks
|
|
8
|
+
|
|
9
|
+
**Note**: The examples below are for plugin development (TypeScript).
|
|
10
|
+
|
|
11
|
+
**When to use this vs [code-patterns.md](code-patterns.md)**:
|
|
12
|
+
- **common-tasks.md**: Quick snippets and basic patterns for common operations
|
|
13
|
+
- **code-patterns.md**: Complete, production-ready examples with full context and error handling
|
|
14
|
+
|
|
15
|
+
> **Note**: If user asks "what does the Obsidian API say about X?" or similar, check `.ref/obsidian-api/obsidian.d.ts` first. See [ref-instructions.md](ref-instructions.md) for when to check `.ref` setup.
|
|
16
|
+
|
|
17
|
+
## Organize code across multiple files
|
|
18
|
+
|
|
19
|
+
**main.ts** (minimal, lifecycle only):
|
|
20
|
+
```ts
|
|
21
|
+
import { Plugin } from "obsidian";
|
|
22
|
+
import { MySettings, DEFAULT_SETTINGS } from "./settings";
|
|
23
|
+
import { registerCommands } from "./commands";
|
|
24
|
+
|
|
25
|
+
export default class MyPlugin extends Plugin {
|
|
26
|
+
settings: MySettings;
|
|
27
|
+
|
|
28
|
+
async onload() {
|
|
29
|
+
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
|
30
|
+
registerCommands(this);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**settings.ts**:
|
|
36
|
+
```ts
|
|
37
|
+
export interface MySettings {
|
|
38
|
+
enabled: boolean;
|
|
39
|
+
apiKey: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const DEFAULT_SETTINGS: MySettings = {
|
|
43
|
+
enabled: true,
|
|
44
|
+
apiKey: "",
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**commands/index.ts**:
|
|
49
|
+
```ts
|
|
50
|
+
import { Plugin } from "obsidian";
|
|
51
|
+
import { doSomething } from "./my-command";
|
|
52
|
+
|
|
53
|
+
export function registerCommands(plugin: Plugin) {
|
|
54
|
+
plugin.addCommand({
|
|
55
|
+
id: "do-something",
|
|
56
|
+
name: "Do something",
|
|
57
|
+
callback: () => doSomething(plugin),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Add a command
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
this.addCommand({
|
|
66
|
+
id: "your-command-id",
|
|
67
|
+
name: "Do the thing",
|
|
68
|
+
callback: () => this.doTheThing(),
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Persist settings
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
interface MySettings { enabled: boolean }
|
|
76
|
+
const DEFAULT_SETTINGS: MySettings = { enabled: true };
|
|
77
|
+
|
|
78
|
+
async onload() {
|
|
79
|
+
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
|
80
|
+
await this.saveData(this.settings);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Register listeners safely
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
this.registerEvent(this.app.workspace.on("file-open", f => { /* ... */ }));
|
|
88
|
+
this.registerDomEvent(window, "resize", () => { /* ... */ });
|
|
89
|
+
this.registerInterval(window.setInterval(() => { /* ... */ }, 1000));
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Settings Tab Implementation
|
|
93
|
+
|
|
94
|
+
**Source**: Based on `.ref/obsidian-sample-plugin/main.ts` and `.ref/obsidian-api/obsidian.d.ts` (API is authoritative for SettingGroup - available since 1.11.0)
|
|
95
|
+
|
|
96
|
+
Basic settings tab:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { App, PluginSettingTab, Setting } from "obsidian";
|
|
100
|
+
|
|
101
|
+
class MySettingTab extends PluginSettingTab {
|
|
102
|
+
plugin: MyPlugin;
|
|
103
|
+
|
|
104
|
+
constructor(app: App, plugin: MyPlugin) {
|
|
105
|
+
super(app, plugin);
|
|
106
|
+
this.plugin = plugin;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
display(): void {
|
|
110
|
+
const { containerEl } = this;
|
|
111
|
+
containerEl.empty();
|
|
112
|
+
|
|
113
|
+
new Setting(containerEl)
|
|
114
|
+
.setName("Setting name")
|
|
115
|
+
.setDesc("Setting description")
|
|
116
|
+
.addText((text) =>
|
|
117
|
+
text
|
|
118
|
+
.setPlaceholder("Enter value")
|
|
119
|
+
.setValue(this.plugin.settings.mySetting)
|
|
120
|
+
.onChange(async (value) => {
|
|
121
|
+
this.plugin.settings.mySetting = value;
|
|
122
|
+
await this.plugin.saveSettings();
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// In main plugin class:
|
|
129
|
+
this.addSettingTab(new MySettingTab(this.app, this));
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Note**: For settings groups (available since API 1.11.0), use `SettingGroup` from the API. Plugin docs may not yet document this feature - always check `.ref/obsidian-api/obsidian.d.ts` for the latest API.
|
|
133
|
+
|
|
134
|
+
**SettingGroup Methods** (available since 1.11.0):
|
|
135
|
+
- `setHeading(heading: string)` - Set the group heading
|
|
136
|
+
- `addSetting(cb: (setting: Setting) => void)` - Add a setting to the group
|
|
137
|
+
- `addSearch(cb: (component: SearchComponent) => any)` - Add a search input at the beginning of the group (useful for filtering)
|
|
138
|
+
- `addExtraButton(cb: (component: ExtraButtonComponent) => any)` - Add an extra button to the group
|
|
139
|
+
|
|
140
|
+
**Backward Compatibility**: To support users on both Obsidian 1.11.0+ and older versions, use a compatibility utility. See [code-patterns.md](code-patterns.md) for the complete implementation with `createSettingsGroup()` utility. Alternatively, you can force `minAppVersion: "1.11.0"` in `manifest.json` if you don't need to support older versions.
|
|
141
|
+
|
|
142
|
+
## Secret Storage
|
|
143
|
+
|
|
144
|
+
**Source**: Based on [SecretStorage and SecretComponent guide](https://docs.obsidian.md/plugins/guides/secret-storage) (available since Obsidian 1.11.4)
|
|
145
|
+
|
|
146
|
+
**Important**: Always use `SecretStorage` and `SecretComponent` for storing sensitive data like API keys, tokens, or passwords. Never store secrets directly in your plugin's `data.json` file.
|
|
147
|
+
|
|
148
|
+
### Using SecretComponent in Settings
|
|
149
|
+
|
|
150
|
+
Store only the secret *name* (ID) in your settings, not the actual secret value:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { App, PluginSettingTab, SecretComponent, Setting } from "obsidian";
|
|
154
|
+
|
|
155
|
+
export interface MyPluginSettings {
|
|
156
|
+
apiKeySecretId: string; // Store the secret name, not the value
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export class MySettingTab extends PluginSettingTab {
|
|
160
|
+
plugin: MyPlugin;
|
|
161
|
+
|
|
162
|
+
constructor(app: App, plugin: MyPlugin) {
|
|
163
|
+
super(app, plugin);
|
|
164
|
+
this.plugin = plugin;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
display(): void {
|
|
168
|
+
const { containerEl } = this;
|
|
169
|
+
containerEl.empty();
|
|
170
|
+
|
|
171
|
+
new Setting(containerEl)
|
|
172
|
+
.setName("API key")
|
|
173
|
+
.setDesc("Select a secret from SecretStorage")
|
|
174
|
+
.addComponent((el) =>
|
|
175
|
+
new SecretComponent(this.app, el)
|
|
176
|
+
.setValue(this.plugin.settings.apiKeySecretId)
|
|
177
|
+
.onChange(async (value) => {
|
|
178
|
+
this.plugin.settings.apiKeySecretId = value;
|
|
179
|
+
await this.plugin.saveSettings();
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Note**: `SecretComponent` requires the `App` instance in its constructor, so it must be used with `Setting#addComponent()` rather than methods like `addText()`.
|
|
187
|
+
|
|
188
|
+
### Retrieving Secrets
|
|
189
|
+
|
|
190
|
+
When you need the actual secret value, retrieve it from `SecretStorage`:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
// Get a secret by its ID (name)
|
|
194
|
+
const secret = this.app.secretStorage.getSecret(this.settings.apiKeySecretId);
|
|
195
|
+
|
|
196
|
+
if (secret) {
|
|
197
|
+
// Use the secret value
|
|
198
|
+
console.log("API key retrieved");
|
|
199
|
+
} else {
|
|
200
|
+
// Secret not found - handle gracefully
|
|
201
|
+
console.warn("API key secret not found");
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Managing Secrets Programmatically
|
|
206
|
+
|
|
207
|
+
You can also manage secrets programmatically (though typically users manage them through the UI):
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
// Set a secret
|
|
211
|
+
this.app.secretStorage.setSecret("my-api-key", "actual-secret-value");
|
|
212
|
+
|
|
213
|
+
// List all secrets
|
|
214
|
+
const allSecrets = this.app.secretStorage.listSecrets();
|
|
215
|
+
// Returns: ["my-api-key", "another-secret", ...]
|
|
216
|
+
|
|
217
|
+
// Get a secret
|
|
218
|
+
const value = this.app.secretStorage.getSecret("my-api-key");
|
|
219
|
+
// Returns: "actual-secret-value" or null if not found
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Important**: Secret IDs must be lowercase alphanumeric with optional dashes (e.g., `my-plugin-api-key`). Invalid IDs will throw an error.
|
|
223
|
+
|
|
224
|
+
See [security-privacy.md](security-privacy.md) for security best practices and [code-patterns.md](code-patterns.md) for comprehensive examples with error handling.
|
|
225
|
+
|
|
226
|
+
## Modal Patterns
|
|
227
|
+
|
|
228
|
+
**Source**: Based on `.ref/obsidian-sample-plugin/main.ts` and `.ref/obsidian-plugin-docs/docs/guides/modals.md`
|
|
229
|
+
|
|
230
|
+
Simple modal:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { App, Modal } from "obsidian";
|
|
234
|
+
|
|
235
|
+
class MyModal extends Modal {
|
|
236
|
+
constructor(app: App) {
|
|
237
|
+
super(app);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
onOpen() {
|
|
241
|
+
const { contentEl } = this;
|
|
242
|
+
contentEl.setText("Modal content");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
onClose() {
|
|
246
|
+
const { contentEl } = this;
|
|
247
|
+
contentEl.empty();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Open modal:
|
|
252
|
+
new MyModal(this.app).open();
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Modal with user input:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { App, Modal, Setting } from "obsidian";
|
|
259
|
+
|
|
260
|
+
class InputModal extends Modal {
|
|
261
|
+
result: string;
|
|
262
|
+
onSubmit: (result: string) => void;
|
|
263
|
+
|
|
264
|
+
constructor(app: App, onSubmit: (result: string) => void) {
|
|
265
|
+
super(app);
|
|
266
|
+
this.onSubmit = onSubmit;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
onOpen() {
|
|
270
|
+
const { contentEl } = this;
|
|
271
|
+
contentEl.createEl("h1", { text: "Enter value" });
|
|
272
|
+
|
|
273
|
+
new Setting(contentEl)
|
|
274
|
+
.setName("Name")
|
|
275
|
+
.addText((text) =>
|
|
276
|
+
text.onChange((value) => {
|
|
277
|
+
this.result = value;
|
|
278
|
+
})
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
new Setting(contentEl)
|
|
282
|
+
.addButton((btn) =>
|
|
283
|
+
btn
|
|
284
|
+
.setButtonText("Submit")
|
|
285
|
+
.setCta()
|
|
286
|
+
.onClick(() => {
|
|
287
|
+
this.close();
|
|
288
|
+
this.onSubmit(this.result);
|
|
289
|
+
})
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
onClose() {
|
|
294
|
+
const { contentEl } = this;
|
|
295
|
+
contentEl.empty();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Custom Views
|
|
301
|
+
|
|
302
|
+
**Source**: Based on `.ref/obsidian-plugin-docs/docs/guides/custom-views.md`
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
import { ItemView, WorkspaceLeaf } from "obsidian";
|
|
306
|
+
|
|
307
|
+
export const VIEW_TYPE_MY_VIEW = "my-view";
|
|
308
|
+
|
|
309
|
+
export class MyView extends ItemView {
|
|
310
|
+
constructor(leaf: WorkspaceLeaf) {
|
|
311
|
+
super(leaf);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
getViewType() {
|
|
315
|
+
return VIEW_TYPE_MY_VIEW;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
getDisplayText() {
|
|
319
|
+
return "My View";
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async onOpen() {
|
|
323
|
+
const container = this.containerEl.children[1];
|
|
324
|
+
container.empty();
|
|
325
|
+
container.createEl("h4", { text: "My View Content" });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async onClose() {
|
|
329
|
+
// Clean up resources
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// In main plugin class:
|
|
334
|
+
async onload() {
|
|
335
|
+
this.registerView(VIEW_TYPE_MY_VIEW, (leaf) => new MyView(leaf));
|
|
336
|
+
|
|
337
|
+
// Activate view:
|
|
338
|
+
await this.activateView();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async activateView() {
|
|
342
|
+
const { workspace } = this.app;
|
|
343
|
+
let leaf = workspace.getLeavesOfType(VIEW_TYPE_MY_VIEW)[0];
|
|
344
|
+
|
|
345
|
+
if (!leaf) {
|
|
346
|
+
leaf = workspace.getRightLeaf(false);
|
|
347
|
+
await leaf.setViewState({ type: VIEW_TYPE_MY_VIEW, active: true });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
workspace.revealLeaf(leaf);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async onunload() {
|
|
354
|
+
this.app.workspace.detachLeavesOfType(VIEW_TYPE_MY_VIEW);
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Warning**: Never store references to views. Use `getLeavesOfType()` to access view instances.
|
|
359
|
+
|
|
360
|
+
## Status Bar Items
|
|
361
|
+
|
|
362
|
+
**Source**: Based on `.ref/obsidian-sample-plugin/main.ts` and `.ref/obsidian-plugin-docs/docs/guides/status-bar.md`
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
// Add status bar item (not supported on mobile)
|
|
366
|
+
const statusBarItemEl = this.addStatusBarItem();
|
|
367
|
+
statusBarItemEl.setText("Status text");
|
|
368
|
+
|
|
369
|
+
// Or create custom elements:
|
|
370
|
+
const statusBarItemEl = this.addStatusBarItem();
|
|
371
|
+
statusBarItemEl.createEl("span", { text: "Status: " });
|
|
372
|
+
statusBarItemEl.createEl("span", { text: "Active" });
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Ribbon Icons
|
|
376
|
+
|
|
377
|
+
**Source**: Based on `.ref/obsidian-sample-plugin/main.ts` and `.ref/obsidian-plugin-docs/docs/guides/ribbon-actions.md`
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
const ribbonIconEl = this.addRibbonIcon("dice", "My Plugin", (evt: MouseEvent) => {
|
|
381
|
+
new Notice("Ribbon clicked!");
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Add CSS class for styling:
|
|
385
|
+
ribbonIconEl.addClass("my-plugin-ribbon-class");
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Editor Commands
|
|
389
|
+
|
|
390
|
+
**Source**: Based on `.ref/obsidian-sample-plugin/main.ts`
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
this.addCommand({
|
|
394
|
+
id: "editor-command",
|
|
395
|
+
name: "Editor command",
|
|
396
|
+
editorCallback: (editor: Editor, view: MarkdownView) => {
|
|
397
|
+
const selection = editor.getSelection();
|
|
398
|
+
editor.replaceSelection("Replaced text");
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Complex Commands with Conditions
|
|
404
|
+
|
|
405
|
+
**Source**: Based on `.ref/obsidian-sample-plugin/main.ts`
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
this.addCommand({
|
|
409
|
+
id: "conditional-command",
|
|
410
|
+
name: "Conditional command",
|
|
411
|
+
checkCallback: (checking: boolean) => {
|
|
412
|
+
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
|
413
|
+
if (markdownView) {
|
|
414
|
+
if (!checking) {
|
|
415
|
+
// Execute command
|
|
416
|
+
this.doAction();
|
|
417
|
+
}
|
|
418
|
+
return true; // Command is available
|
|
419
|
+
}
|
|
420
|
+
return false; // Command is not available
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
The `checkCallback` receives a `checking` boolean:
|
|
426
|
+
- When `true`: Only check if command can run (don't execute)
|
|
427
|
+
- When `false`: Actually execute the command
|
|
428
|
+
|
|
429
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: obsidian-dev-themes
|
|
3
|
+
description: CSS/SCSS development patterns for Obsidian themes. Load when working with theme.css, SCSS variables, or CSS selectors.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Obsidian Theme Development Skill
|
|
7
|
+
|
|
8
|
+
This skill provides patterns and rules for developing Obsidian themes, focusing on CSS/SCSS development, styling conventions, and theme-specific coding practices.
|
|
9
|
+
|
|
10
|
+
## Purpose
|
|
11
|
+
|
|
12
|
+
To ensure consistent theme development, proper CSS organization, and adherence to Obsidian's theming patterns and CSS variable usage.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
Load this skill when:
|
|
17
|
+
- Writing or modifying CSS/SCSS for `theme.css`
|
|
18
|
+
- Working with Obsidian's CSS variables and theming system
|
|
19
|
+
- Implementing responsive design or dark/light mode support
|
|
20
|
+
- Debugging CSS layout or styling issues
|
|
21
|
+
- Following CSS/SCSS coding conventions
|
|
22
|
+
|
|
23
|
+
## Core Rules
|
|
24
|
+
|
|
25
|
+
- **Use Obsidian CSS Variables**: Always prefer Obsidian's built-in CSS variables over hardcoded values
|
|
26
|
+
- **Follow BEM Methodology**: Use Block-Element-Modifier naming for CSS classes
|
|
27
|
+
- **Mobile-First**: Consider mobile layouts and responsive design
|
|
28
|
+
- **Dark/Light Mode Support**: Test themes in both light and dark modes
|
|
29
|
+
- **Performance**: Minimize CSS complexity and avoid expensive selectors
|
|
30
|
+
|
|
31
|
+
## Bundled Resources
|
|
32
|
+
|
|
33
|
+
- `references/theme-best-practices.md`: Essential CSS patterns and Obsidian variable usage
|
|
34
|
+
- `references/theme-coding-conventions.md`: CSS/SCSS style guidelines and naming conventions
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Theme Development Best Practices
|
|
2
|
+
|
|
3
|
+
## CSS Organization
|
|
4
|
+
- Group related styles together (Editor, UI, Sidebar, etc.)
|
|
5
|
+
- Use comments to separate major sections
|
|
6
|
+
- Keep specificity low for easy customization
|
|
7
|
+
|
|
8
|
+
## Obsidian CSS Variables
|
|
9
|
+
Always prefer Obsidian's built-in CSS variables over hardcoded values:
|
|
10
|
+
|
|
11
|
+
```css
|
|
12
|
+
/* Good */
|
|
13
|
+
.theme-dark {
|
|
14
|
+
--my-accent: var(--interactive-accent);
|
|
15
|
+
background-color: var(--background-primary);
|
|
16
|
+
color: var(--text-normal);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Avoid */
|
|
20
|
+
.theme-dark {
|
|
21
|
+
background-color: #2d2d30;
|
|
22
|
+
color: #cccccc;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Responsive Design
|
|
27
|
+
```css
|
|
28
|
+
/* Mobile-first approach */
|
|
29
|
+
.my-component {
|
|
30
|
+
width: 100%;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@media (min-width: 768px) {
|
|
34
|
+
.my-component {
|
|
35
|
+
width: 50%;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Dark/Light Mode Support
|
|
41
|
+
```css
|
|
42
|
+
/* Support both themes */
|
|
43
|
+
.theme-dark .my-element {
|
|
44
|
+
background: var(--background-primary);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.theme-light .my-element {
|
|
48
|
+
background: var(--background-secondary);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Theme Coding Conventions
|
|
2
|
+
|
|
3
|
+
## CSS/SCSS Guidelines
|
|
4
|
+
|
|
5
|
+
### Naming Conventions
|
|
6
|
+
- Use lowercase with hyphens for class names: `.my-component`
|
|
7
|
+
- Follow BEM methodology: `.block__element--modifier`
|
|
8
|
+
- Prefix custom classes: `.obsidian-my-theme-`
|
|
9
|
+
|
|
10
|
+
### Structure
|
|
11
|
+
```scss
|
|
12
|
+
// Variables at top
|
|
13
|
+
:root {
|
|
14
|
+
--my-theme-primary: #007acc;
|
|
15
|
+
--my-theme-secondary: #cccccc;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Component styles
|
|
19
|
+
.obsidian-my-theme {
|
|
20
|
+
&__header {
|
|
21
|
+
background: var(--my-theme-primary);
|
|
22
|
+
|
|
23
|
+
&--dark {
|
|
24
|
+
background: darken(var(--my-theme-primary), 20%);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&__content {
|
|
29
|
+
color: var(--text-normal);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Best Practices
|
|
35
|
+
- Use SCSS nesting sparingly (max 3 levels)
|
|
36
|
+
- Always use Obsidian CSS variables when available
|
|
37
|
+
- Comment complex selectors and their purpose
|
|
38
|
+
- Group related styles together
|
|
39
|
+
- Test in both light and dark themes
|
|
40
|
+
|
|
41
|
+
### Performance
|
|
42
|
+
- Avoid universal selectors (`*`)
|
|
43
|
+
- Minimize CSS specificity
|
|
44
|
+
- Use efficient selectors (prefer classes over complex selectors)
|
|
45
|
+
- Keep CSS bundle size reasonable
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obsidian-dev-skills",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Agent skills for Obsidian plugin and theme development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"obsidian",
|
|
@@ -19,10 +19,18 @@
|
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"author": "David V. Kimball",
|
|
21
21
|
"files": [
|
|
22
|
-
"obsidian-dev/",
|
|
22
|
+
"obsidian-dev-plugins/",
|
|
23
|
+
"obsidian-dev-themes/",
|
|
23
24
|
"obsidian-ops/",
|
|
24
|
-
"obsidian-ref/"
|
|
25
|
+
"obsidian-ref/",
|
|
26
|
+
"scripts/"
|
|
25
27
|
],
|
|
28
|
+
"bin": {
|
|
29
|
+
"obsidian-dev-skills": "./scripts/init.mjs"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"postinstall": "node ./scripts/init.mjs"
|
|
33
|
+
},
|
|
26
34
|
"engines": {
|
|
27
35
|
"node": ">=16.0.0"
|
|
28
36
|
}
|