@notehub.md/cli 0.1.8 → 0.1.10
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/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +1 -3
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +13 -1
- package/dist/commands/create.js.map +1 -1
- package/package.json +2 -2
- package/templates/docs/en/01-getting-started.md +207 -0
- package/templates/docs/en/02-architecture.md +228 -0
- package/templates/docs/en/03-api-reference.md +747 -0
- package/templates/docs/en/04-widgets.md +322 -0
- package/templates/docs/en/05-settings.md +303 -0
- package/templates/docs/en/06-context-menu.md +283 -0
- package/templates/docs/en/07-examples.md +547 -0
- package/templates/docs/en/README.md +125 -0
- package/templates/docs/ru/01-getting-started.md +207 -0
- package/templates/docs/ru/02-architecture.md +228 -0
- package/templates/docs/ru/03-api-reference.md +747 -0
- package/templates/docs/ru/04-widgets.md +293 -0
- package/templates/docs/ru/05-settings.md +303 -0
- package/templates/docs/ru/06-context-menu.md +283 -0
- package/templates/docs/ru/07-examples.md +547 -0
- package/templates/docs/ru/README.md +125 -0
- package/templates/docs.html +6 -4
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
# Plugin Examples
|
|
2
|
+
|
|
3
|
+
Complete, working plugin examples you can use as templates.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Example 1: Hello World (Minimal)
|
|
8
|
+
|
|
9
|
+
The simplest possible plugin.
|
|
10
|
+
|
|
11
|
+
### `manifest.json`
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"id": "hello-world",
|
|
16
|
+
"name": "Hello World",
|
|
17
|
+
"version": "1.0.0"
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### `src/index.ts`
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { NotehubPlugin, PluginContext } from '@notehub/api';
|
|
25
|
+
|
|
26
|
+
export default class HelloWorld extends NotehubPlugin {
|
|
27
|
+
async onload(ctx: PluginContext): Promise<void> {
|
|
28
|
+
await ctx.invokeApi('logger:info', 'HelloWorld', '👋 Hello from my first plugin!');
|
|
29
|
+
|
|
30
|
+
// Register a simple API
|
|
31
|
+
ctx.registerApi('hello:greet', (name: string) => {
|
|
32
|
+
return `Hello, ${name}!`;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async onunload(): Promise<void> {
|
|
37
|
+
console.log('Goodbye!');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Example 2: Word Counter Widget
|
|
45
|
+
|
|
46
|
+
A widget that counts words in the current paragraph.
|
|
47
|
+
|
|
48
|
+
### `manifest.json`
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"id": "word-counter",
|
|
53
|
+
"name": "Word Counter Widget",
|
|
54
|
+
"version": "1.0.0"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `src/index.ts`
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { NotehubPlugin, PluginContext } from '@notehub/api';
|
|
62
|
+
import React from 'react';
|
|
63
|
+
|
|
64
|
+
// Widget component
|
|
65
|
+
const WordCounter: React.FC<{ match: RegExpExecArray }> = ({ match }) => {
|
|
66
|
+
const text = match[1];
|
|
67
|
+
const words = text.trim().split(/\s+/).filter(w => w.length > 0).length;
|
|
68
|
+
const chars = text.length;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<span style={{
|
|
72
|
+
display: 'inline-flex',
|
|
73
|
+
gap: '12px',
|
|
74
|
+
padding: '4px 12px',
|
|
75
|
+
background: 'var(--nh-bg-surface)',
|
|
76
|
+
borderRadius: '4px',
|
|
77
|
+
fontSize: '12px',
|
|
78
|
+
color: 'var(--nh-text-muted)',
|
|
79
|
+
border: '1px solid var(--nh-border-subtle)',
|
|
80
|
+
}}>
|
|
81
|
+
<span>📝 {words} words</span>
|
|
82
|
+
<span>📏 {chars} chars</span>
|
|
83
|
+
</span>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default class WordCounterPlugin extends NotehubPlugin {
|
|
88
|
+
async onload(ctx: PluginContext): Promise<void> {
|
|
89
|
+
// Match: {{count: any text here}}
|
|
90
|
+
await ctx.invokeApi(
|
|
91
|
+
'editor:register-widget',
|
|
92
|
+
'word-counter',
|
|
93
|
+
/\{\{count:\s*(.+?)\}\}/g,
|
|
94
|
+
WordCounter
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
await ctx.invokeApi('logger:info', 'WordCounter', 'Widget registered');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async onunload(): Promise<void> {}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Usage:**
|
|
105
|
+
```markdown
|
|
106
|
+
{{count: This is a sample text with exactly ten words total}}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Example 3: Settings Plugin
|
|
112
|
+
|
|
113
|
+
A plugin with configurable settings.
|
|
114
|
+
|
|
115
|
+
### `manifest.json`
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"id": "settings-demo",
|
|
120
|
+
"name": "Settings Demo",
|
|
121
|
+
"version": "1.0.0"
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `src/index.ts`
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { NotehubPlugin, PluginContext } from '@notehub/api';
|
|
129
|
+
|
|
130
|
+
export default class SettingsDemo extends NotehubPlugin {
|
|
131
|
+
async onload(ctx: PluginContext): Promise<void> {
|
|
132
|
+
// Register settings tab
|
|
133
|
+
await ctx.invokeApi('settings:register-tab', {
|
|
134
|
+
id: 'settings-demo',
|
|
135
|
+
label: 'Demo Settings',
|
|
136
|
+
icon: 'sliders',
|
|
137
|
+
order: 100
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Register group
|
|
141
|
+
await ctx.invokeApi('settings:register-group', {
|
|
142
|
+
id: 'demo-general',
|
|
143
|
+
tabId: 'settings-demo',
|
|
144
|
+
label: 'General Options',
|
|
145
|
+
order: 0
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Register items
|
|
149
|
+
await ctx.invokeApi('settings:register-items', [
|
|
150
|
+
{
|
|
151
|
+
key: 'settings-demo.enabled',
|
|
152
|
+
type: 'toggle',
|
|
153
|
+
label: 'Enable feature',
|
|
154
|
+
description: 'Turn the demo feature on or off',
|
|
155
|
+
groupId: 'demo-general',
|
|
156
|
+
order: 0,
|
|
157
|
+
defaultValue: true
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
key: 'settings-demo.name',
|
|
161
|
+
type: 'text',
|
|
162
|
+
label: 'Your name',
|
|
163
|
+
placeholder: 'Enter your name...',
|
|
164
|
+
groupId: 'demo-general',
|
|
165
|
+
order: 1,
|
|
166
|
+
defaultValue: ''
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
key: 'settings-demo.count',
|
|
170
|
+
type: 'number',
|
|
171
|
+
label: 'Item count',
|
|
172
|
+
min: 1,
|
|
173
|
+
max: 100,
|
|
174
|
+
step: 1,
|
|
175
|
+
groupId: 'demo-general',
|
|
176
|
+
order: 2,
|
|
177
|
+
defaultValue: 10
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
key: 'settings-demo.mode',
|
|
181
|
+
type: 'select',
|
|
182
|
+
label: 'Display mode',
|
|
183
|
+
options: [
|
|
184
|
+
{ label: 'Compact', value: 'compact' },
|
|
185
|
+
{ label: 'Normal', value: 'normal' },
|
|
186
|
+
{ label: 'Expanded', value: 'expanded' }
|
|
187
|
+
],
|
|
188
|
+
groupId: 'demo-general',
|
|
189
|
+
order: 3,
|
|
190
|
+
defaultValue: 'normal'
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
key: 'settings-demo.color',
|
|
194
|
+
type: 'color',
|
|
195
|
+
label: 'Highlight color',
|
|
196
|
+
groupId: 'demo-general',
|
|
197
|
+
order: 4,
|
|
198
|
+
defaultValue: '#3b82f6'
|
|
199
|
+
}
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
// Read and use settings
|
|
203
|
+
const enabled = await ctx.invokeApi<boolean>('config:get', 'settings-demo.enabled', true);
|
|
204
|
+
const name = await ctx.invokeApi<string>('config:get', 'settings-demo.name', 'User');
|
|
205
|
+
|
|
206
|
+
await ctx.invokeApi('logger:info', 'SettingsDemo',
|
|
207
|
+
`Loaded! enabled=${enabled}, name="${name}"`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async onunload(): Promise<void> {}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Example 4: Context Menu Extension
|
|
217
|
+
|
|
218
|
+
Add custom actions to file explorer context menu.
|
|
219
|
+
|
|
220
|
+
### `manifest.json`
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"id": "quick-actions",
|
|
225
|
+
"name": "Quick Actions",
|
|
226
|
+
"version": "1.0.0"
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### `src/index.ts`
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { NotehubPlugin, PluginContext } from '@notehub/api';
|
|
234
|
+
|
|
235
|
+
interface FilePayload {
|
|
236
|
+
path: string;
|
|
237
|
+
isDirectory: boolean;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export default class QuickActionsPlugin extends NotehubPlugin {
|
|
241
|
+
private ctx!: PluginContext;
|
|
242
|
+
|
|
243
|
+
async onload(ctx: PluginContext): Promise<void> {
|
|
244
|
+
this.ctx = ctx;
|
|
245
|
+
|
|
246
|
+
await ctx.invokeApi(
|
|
247
|
+
'context-menu:register',
|
|
248
|
+
'explorer-item',
|
|
249
|
+
(payload: FilePayload) => this.buildMenu(payload)
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
await ctx.invokeApi('logger:info', 'QuickActions', 'Context menu registered');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private buildMenu(payload: FilePayload) {
|
|
256
|
+
const items = [];
|
|
257
|
+
|
|
258
|
+
// Only for markdown files
|
|
259
|
+
if (payload.path.endsWith('.md')) {
|
|
260
|
+
items.push({
|
|
261
|
+
type: 'action' as const,
|
|
262
|
+
id: 'qa-word-count',
|
|
263
|
+
label: 'Word Count',
|
|
264
|
+
icon: 'hash',
|
|
265
|
+
onClick: () => this.countWords(payload.path)
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
items.push({
|
|
269
|
+
type: 'action' as const,
|
|
270
|
+
id: 'qa-add-date',
|
|
271
|
+
label: 'Add Today\'s Date',
|
|
272
|
+
icon: 'calendar',
|
|
273
|
+
onClick: () => this.addDate(payload.path)
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// For all files
|
|
278
|
+
items.push({ type: 'separator' as const });
|
|
279
|
+
|
|
280
|
+
items.push({
|
|
281
|
+
type: 'action' as const,
|
|
282
|
+
id: 'qa-copy-path',
|
|
283
|
+
label: 'Copy Path',
|
|
284
|
+
icon: 'copy',
|
|
285
|
+
onClick: () => navigator.clipboard.writeText(payload.path)
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Duplicate action
|
|
289
|
+
if (!payload.isDirectory) {
|
|
290
|
+
items.push({
|
|
291
|
+
type: 'action' as const,
|
|
292
|
+
id: 'qa-duplicate',
|
|
293
|
+
label: 'Duplicate File',
|
|
294
|
+
icon: 'files',
|
|
295
|
+
onClick: () => this.duplicateFile(payload.path)
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return items;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private async countWords(path: string) {
|
|
303
|
+
const content = await this.ctx.invokeApi<string>('fs:read-text-file', path);
|
|
304
|
+
const words = content.split(/\s+/).filter(w => w.length > 0).length;
|
|
305
|
+
await this.ctx.invokeApi('dialog:alert', 'Word Count', `${words} words in this file`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private async addDate(path: string) {
|
|
309
|
+
const content = await this.ctx.invokeApi<string>('fs:read-text-file', path);
|
|
310
|
+
const date = new Date().toISOString().split('T')[0];
|
|
311
|
+
const newContent = `---\ndate: ${date}\n---\n\n${content}`;
|
|
312
|
+
await this.ctx.invokeApi('fs:write-text-file', path, newContent);
|
|
313
|
+
await this.ctx.invokeApi('dialog:alert', 'Success', 'Date added to file');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private async duplicateFile(path: string) {
|
|
317
|
+
const content = await this.ctx.invokeApi<string>('fs:read-text-file', path);
|
|
318
|
+
const newPath = path.replace(/\.md$/, ' (copy).md');
|
|
319
|
+
await this.ctx.invokeApi('fs:write-text-file', newPath, content);
|
|
320
|
+
await this.ctx.invokeApi('dialog:alert', 'Success', `Created: ${newPath}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async onunload(): Promise<void> {}
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Example 5: Full-Featured Plugin
|
|
330
|
+
|
|
331
|
+
A complete plugin combining widgets, settings, and context menus.
|
|
332
|
+
|
|
333
|
+
### `manifest.json`
|
|
334
|
+
|
|
335
|
+
```json
|
|
336
|
+
{
|
|
337
|
+
"id": "task-tracker",
|
|
338
|
+
"name": "Task Tracker",
|
|
339
|
+
"version": "1.0.0"
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### `src/index.ts`
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import { NotehubPlugin, PluginContext } from '@notehub/api';
|
|
347
|
+
import React, { useState } from 'react';
|
|
348
|
+
|
|
349
|
+
// === Widget: Interactive Task Checkbox ===
|
|
350
|
+
const TaskWidget: React.FC<{ match: RegExpExecArray }> = ({ match }) => {
|
|
351
|
+
const status = match[1]; // 'x' or ' '
|
|
352
|
+
const text = match[2];
|
|
353
|
+
const [checked, setChecked] = useState(status === 'x');
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
<span
|
|
357
|
+
onClick={() => setChecked(!checked)}
|
|
358
|
+
style={{
|
|
359
|
+
display: 'inline-flex',
|
|
360
|
+
alignItems: 'center',
|
|
361
|
+
gap: '8px',
|
|
362
|
+
padding: '4px 8px',
|
|
363
|
+
background: checked ? 'var(--nh-accent-primary)20' : 'var(--nh-bg-surface)',
|
|
364
|
+
borderRadius: '4px',
|
|
365
|
+
cursor: 'pointer',
|
|
366
|
+
transition: 'all 0.2s',
|
|
367
|
+
}}
|
|
368
|
+
>
|
|
369
|
+
<span style={{
|
|
370
|
+
width: '18px',
|
|
371
|
+
height: '18px',
|
|
372
|
+
borderRadius: '4px',
|
|
373
|
+
border: `2px solid ${checked ? 'var(--nh-accent-primary)' : 'var(--nh-border-subtle)'}`,
|
|
374
|
+
background: checked ? 'var(--nh-accent-primary)' : 'transparent',
|
|
375
|
+
display: 'flex',
|
|
376
|
+
alignItems: 'center',
|
|
377
|
+
justifyContent: 'center',
|
|
378
|
+
color: '#fff',
|
|
379
|
+
fontSize: '12px',
|
|
380
|
+
}}>
|
|
381
|
+
{checked && '✓'}
|
|
382
|
+
</span>
|
|
383
|
+
<span style={{
|
|
384
|
+
textDecoration: checked ? 'line-through' : 'none',
|
|
385
|
+
color: checked ? 'var(--nh-text-muted)' : 'var(--nh-text-primary)',
|
|
386
|
+
}}>
|
|
387
|
+
{text}
|
|
388
|
+
</span>
|
|
389
|
+
</span>
|
|
390
|
+
);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// === Plugin Class ===
|
|
394
|
+
export default class TaskTracker extends NotehubPlugin {
|
|
395
|
+
private ctx!: PluginContext;
|
|
396
|
+
|
|
397
|
+
async onload(ctx: PluginContext): Promise<void> {
|
|
398
|
+
this.ctx = ctx;
|
|
399
|
+
|
|
400
|
+
// 1. Register widget
|
|
401
|
+
await ctx.invokeApi(
|
|
402
|
+
'editor:register-widget',
|
|
403
|
+
'task-tracker:checkbox',
|
|
404
|
+
/\[([x ])\]\s+(.+?)(?=\n|$)/g,
|
|
405
|
+
TaskWidget
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
// 2. Register settings
|
|
409
|
+
await ctx.invokeApi('settings:register-tab', {
|
|
410
|
+
id: 'task-tracker',
|
|
411
|
+
label: 'Task Tracker',
|
|
412
|
+
icon: 'check-square',
|
|
413
|
+
order: 50
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
await ctx.invokeApi('settings:register-group', {
|
|
417
|
+
id: 'task-tracker-options',
|
|
418
|
+
tabId: 'task-tracker',
|
|
419
|
+
label: 'Options',
|
|
420
|
+
order: 0
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
await ctx.invokeApi('settings:register-items', [
|
|
424
|
+
{
|
|
425
|
+
key: 'task-tracker.style',
|
|
426
|
+
type: 'select',
|
|
427
|
+
label: 'Checkbox style',
|
|
428
|
+
options: [
|
|
429
|
+
{ label: 'Round', value: 'round' },
|
|
430
|
+
{ label: 'Square', value: 'square' }
|
|
431
|
+
],
|
|
432
|
+
groupId: 'task-tracker-options',
|
|
433
|
+
order: 0,
|
|
434
|
+
defaultValue: 'square'
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
key: 'task-tracker.strikethrough',
|
|
438
|
+
type: 'toggle',
|
|
439
|
+
label: 'Strikethrough completed',
|
|
440
|
+
groupId: 'task-tracker-options',
|
|
441
|
+
order: 1,
|
|
442
|
+
defaultValue: true
|
|
443
|
+
}
|
|
444
|
+
]);
|
|
445
|
+
|
|
446
|
+
// 3. Register context menu
|
|
447
|
+
await ctx.invokeApi(
|
|
448
|
+
'context-menu:register',
|
|
449
|
+
'explorer-item',
|
|
450
|
+
(payload: { path: string }) => {
|
|
451
|
+
if (!payload.path.endsWith('.md')) return [];
|
|
452
|
+
|
|
453
|
+
return [
|
|
454
|
+
{
|
|
455
|
+
type: 'action' as const,
|
|
456
|
+
id: 'tt-count-tasks',
|
|
457
|
+
label: 'Count Tasks',
|
|
458
|
+
icon: 'list-checks',
|
|
459
|
+
onClick: () => this.countTasks(payload.path)
|
|
460
|
+
}
|
|
461
|
+
];
|
|
462
|
+
}
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
await ctx.invokeApi('logger:info', 'TaskTracker', 'Plugin loaded successfully!');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private async countTasks(path: string) {
|
|
469
|
+
const content = await this.ctx.invokeApi<string>('fs:read-text-file', path);
|
|
470
|
+
const total = (content.match(/\[[x ]\]/g) || []).length;
|
|
471
|
+
const done = (content.match(/\[x\]/g) || []).length;
|
|
472
|
+
|
|
473
|
+
await this.ctx.invokeApi(
|
|
474
|
+
'dialog:alert',
|
|
475
|
+
'Task Summary',
|
|
476
|
+
`${done}/${total} tasks completed (${Math.round(done/total*100)}%)`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async onunload(): Promise<void> {
|
|
481
|
+
await this.ctx.invokeApi('logger:info', 'TaskTracker', 'Plugin unloaded');
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**Usage in documents:**
|
|
487
|
+
```markdown
|
|
488
|
+
## Today's Tasks
|
|
489
|
+
|
|
490
|
+
[x] Review pull request
|
|
491
|
+
[ ] Write documentation
|
|
492
|
+
[ ] Deploy to production
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Build Configuration
|
|
498
|
+
|
|
499
|
+
### `package.json`
|
|
500
|
+
|
|
501
|
+
```json
|
|
502
|
+
{
|
|
503
|
+
"name": "my-plugin",
|
|
504
|
+
"version": "1.0.0",
|
|
505
|
+
"type": "module",
|
|
506
|
+
"scripts": {
|
|
507
|
+
"build": "esbuild src/index.ts --bundle --format=esm --outfile=main.js --external:@notehub/api --external:react --external:react-dom",
|
|
508
|
+
"watch": "npm run build -- --watch"
|
|
509
|
+
},
|
|
510
|
+
"devDependencies": {
|
|
511
|
+
"@notehub/api": "file:../path/to/notehub/packages/api",
|
|
512
|
+
"@types/react": "^18.2.0",
|
|
513
|
+
"esbuild": "^0.20.0",
|
|
514
|
+
"typescript": "^5.3.0"
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### `tsconfig.json`
|
|
520
|
+
|
|
521
|
+
```json
|
|
522
|
+
{
|
|
523
|
+
"compilerOptions": {
|
|
524
|
+
"target": "ES2020",
|
|
525
|
+
"module": "ESNext",
|
|
526
|
+
"moduleResolution": "bundler",
|
|
527
|
+
"lib": ["ES2020", "DOM"],
|
|
528
|
+
"jsx": "react-jsx",
|
|
529
|
+
"strict": true,
|
|
530
|
+
"esModuleInterop": true,
|
|
531
|
+
"skipLibCheck": true,
|
|
532
|
+
"declaration": false,
|
|
533
|
+
"outDir": "./dist"
|
|
534
|
+
},
|
|
535
|
+
"include": ["src/**/*"]
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## Tips for Development
|
|
542
|
+
|
|
543
|
+
1. **Use `npm run watch`** for automatic rebuilding
|
|
544
|
+
2. **Notehub auto-reloads** plugins when files change
|
|
545
|
+
3. **Open DevTools** (Ctrl+Shift+I) for console output
|
|
546
|
+
4. **Use `logger:info`** for structured logging
|
|
547
|
+
5. **Test incrementally** - add features one at a time
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<h1 align="center">🔌 Notehub Plugin Developer Guide</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<em>Create powerful plugins for Notehub.md - the extensible note-taking app</em>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="#-quick-start">Quick Start</a> •
|
|
9
|
+
<a href="#-documentation">Documentation</a> •
|
|
10
|
+
<a href="#-examples">Examples</a> •
|
|
11
|
+
<a href="#-api-reference">API Reference</a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🚀 Quick Start
|
|
17
|
+
|
|
18
|
+
### Option 1: Use the Plugin Generator (Recommended)
|
|
19
|
+
|
|
20
|
+
For **internal plugins** (part of the monorepo):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm gen:plugin
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This interactive CLI will:
|
|
27
|
+
1. Ask for a plugin name (kebab-case, e.g., `my-feature`)
|
|
28
|
+
2. Let you choose a category (`system`, `ui`, `features`)
|
|
29
|
+
3. Generate the full plugin structure
|
|
30
|
+
|
|
31
|
+
**Output:**
|
|
32
|
+
```
|
|
33
|
+
🔌 Notehub.md Plugin Generator
|
|
34
|
+
|
|
35
|
+
✔ Plugin name (kebab-case): word-counter
|
|
36
|
+
✔ Select plugin category: features - User-facing features
|
|
37
|
+
|
|
38
|
+
📦 Creating plugin: nh.features.word-counter
|
|
39
|
+
Path: packages/plugins/features/word-counter
|
|
40
|
+
|
|
41
|
+
✅ Created: package.json
|
|
42
|
+
✅ Created: tsconfig.json
|
|
43
|
+
✅ Created: manifest.json
|
|
44
|
+
✅ Created: src/index.ts
|
|
45
|
+
|
|
46
|
+
✨ Plugin created successfully!
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Option 2: Manual Setup (External Plugins)
|
|
52
|
+
|
|
53
|
+
For plugins that will be loaded at runtime from a vault:
|
|
54
|
+
|
|
55
|
+
#### 1. Create a plugin folder
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
mkdir my-plugin && cd my-plugin
|
|
59
|
+
npm init -y
|
|
60
|
+
npm install @notehub/api typescript esbuild --save-dev
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 📚 Documentation
|
|
66
|
+
|
|
67
|
+
| Chapter | Description |
|
|
68
|
+
|---------|-------------|
|
|
69
|
+
| [Getting Started](01-getting-started.md) | Prerequisites, setup, first plugin |
|
|
70
|
+
| [Architecture](02-architecture.md) | Plugin lifecycle, EventBus, ApiBus |
|
|
71
|
+
| [API Reference](03-api-reference.md) | All 50+ API methods with examples |
|
|
72
|
+
| [Widgets](04-widgets.md) | Custom React components in notes |
|
|
73
|
+
| [Settings](05-settings.md) | Add configuration UI |
|
|
74
|
+
| [Context Menu](06-context-menu.md) | Right-click menu integration |
|
|
75
|
+
| [Examples](07-examples.md) | Complete working plugins |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 💡 What Can Plugins Do?
|
|
80
|
+
|
|
81
|
+
| Feature | API |
|
|
82
|
+
|---------|-----|
|
|
83
|
+
| 📁 Read/write files | `fs:read-text-file`, `fs:write-text-file` |
|
|
84
|
+
| ⚙️ Save settings | `config:get`, `config:set` |
|
|
85
|
+
| 🎨 Register themes | `theme:register`, `theme:set` |
|
|
86
|
+
| 🧩 Create widgets | `editor:register-widget` |
|
|
87
|
+
| 📋 Context menus | `context-menu:register` |
|
|
88
|
+
| 💬 Show dialogs | `dialog:alert`, `dialog:confirm` |
|
|
89
|
+
| 📡 Subscribe to events | `ctx.subscribe()` |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 🎯 Examples
|
|
94
|
+
|
|
95
|
+
### Hello World
|
|
96
|
+
```typescript
|
|
97
|
+
ctx.registerApi('hello:greet', (name: string) => `Hello, ${name}!`);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Progress Bar Widget
|
|
101
|
+
```typescript
|
|
102
|
+
await ctx.invokeApi('editor:register-widget', 'progress', /\[progress:(\d+)\]/g,
|
|
103
|
+
({ match }) => <ProgressBar value={parseInt(match[1])} />);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### File Watcher
|
|
107
|
+
```typescript
|
|
108
|
+
ctx.subscribe<{ path: string }>('explorer:file-selected', (payload) => {
|
|
109
|
+
console.log('Selected:', payload.path);
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 🔗 Links
|
|
116
|
+
|
|
117
|
+
- [Main README](../../README.md)
|
|
118
|
+
- [API Package](../../packages/api)
|
|
119
|
+
- [Example Plugins](../../packages/plugins)
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
<p align="center">
|
|
124
|
+
<strong>Happy coding! 🎉</strong>
|
|
125
|
+
</p>
|