@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.
@@ -0,0 +1,547 @@
1
+ # Примеры плагинов
2
+
3
+ Полные, рабочие примеры плагинов, которые можно использовать как шаблоны.
4
+
5
+ ---
6
+
7
+ ## Пример 1: Hello World (Минимальный)
8
+
9
+ Простейший возможный плагин.
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', '👋 Привет из моего первого плагина!');
29
+
30
+ // Регистрация простого API
31
+ ctx.registerApi('hello:greet', (name: string) => {
32
+ return `Привет, ${name}!`;
33
+ });
34
+ }
35
+
36
+ async onunload(): Promise<void> {
37
+ console.log('До свидания!');
38
+ }
39
+ }
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Пример 2: Виджет счетчика слов
45
+
46
+ Виджет, считающий слова в текущем параграфе.
47
+
48
+ ### `manifest.json`
49
+
50
+ ```json
51
+ {
52
+ "id": "word-counter",
53
+ "name": "Счетчик слов",
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
+ // Компонент виджета
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} слов</span>
82
+ <span>📏 {chars} символов</span>
83
+ </span>
84
+ );
85
+ };
86
+
87
+ export default class WordCounterPlugin extends NotehubPlugin {
88
+ async onload(ctx: PluginContext): Promise<void> {
89
+ // Совпадение: {{count: любой текст здесь}}
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', 'Виджет зарегистрирован');
98
+ }
99
+
100
+ async onunload(): Promise<void> {}
101
+ }
102
+ ```
103
+
104
+ **Использование:**
105
+ ```markdown
106
+ {{count: Это пример текста ровно с десятью словами всего}}
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Пример 3: Плагин с настройками
112
+
113
+ Плагин с настраиваемыми параметрами.
114
+
115
+ ### `manifest.json`
116
+
117
+ ```json
118
+ {
119
+ "id": "settings-demo",
120
+ "name": "Демо настроек",
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
+ // Регистрация вкладки
133
+ await ctx.invokeApi('settings:register-tab', {
134
+ id: 'settings-demo',
135
+ label: 'Демо настроек',
136
+ icon: 'sliders',
137
+ order: 100
138
+ });
139
+
140
+ // Регистрация группы
141
+ await ctx.invokeApi('settings:register-group', {
142
+ id: 'demo-general',
143
+ tabId: 'settings-demo',
144
+ label: 'Основные опции',
145
+ order: 0
146
+ });
147
+
148
+ // Регистрация элементов
149
+ await ctx.invokeApi('settings:register-items', [
150
+ {
151
+ key: 'settings-demo.enabled',
152
+ type: 'toggle',
153
+ label: 'Включить функцию',
154
+ description: 'Включить или выключить демо-функцию',
155
+ groupId: 'demo-general',
156
+ order: 0,
157
+ defaultValue: true
158
+ },
159
+ {
160
+ key: 'settings-demo.name',
161
+ type: 'text',
162
+ label: 'Ваше имя',
163
+ placeholder: 'Введите ваше имя...',
164
+ groupId: 'demo-general',
165
+ order: 1,
166
+ defaultValue: ''
167
+ },
168
+ {
169
+ key: 'settings-demo.count',
170
+ type: 'number',
171
+ label: 'Количество элементов',
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: 'Режим отображения',
183
+ options: [
184
+ { label: 'Компактный', value: 'compact' },
185
+ { label: 'Обычный', value: 'normal' },
186
+ { label: 'Расширенный', 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: 'Цвет подсветки',
196
+ groupId: 'demo-general',
197
+ order: 4,
198
+ defaultValue: '#3b82f6'
199
+ }
200
+ ]);
201
+
202
+ // Чтение и использование настроек
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', 'Пользователь');
205
+
206
+ await ctx.invokeApi('logger:info', 'SettingsDemo',
207
+ `Загружено! enabled=${enabled}, name="${name}"`);
208
+ }
209
+
210
+ async onunload(): Promise<void> {}
211
+ }
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Пример 4: Расширение контекстного меню
217
+
218
+ Добавление кастомных действий в контекстное меню проводника.
219
+
220
+ ### `manifest.json`
221
+
222
+ ```json
223
+ {
224
+ "id": "quick-actions",
225
+ "name": "Быстрые действия",
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', 'Контекстное меню зарегистрировано');
253
+ }
254
+
255
+ private buildMenu(payload: FilePayload) {
256
+ const items = [];
257
+
258
+ // Только для markdown-файлов
259
+ if (payload.path.endsWith('.md')) {
260
+ items.push({
261
+ type: 'action' as const,
262
+ id: 'qa-word-count',
263
+ label: 'Подсчитать слова',
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: 'Добавить сегодняшнюю дату',
272
+ icon: 'calendar',
273
+ onClick: () => this.addDate(payload.path)
274
+ });
275
+ }
276
+
277
+ // Для всех файлов
278
+ items.push({ type: 'separator' as const });
279
+
280
+ items.push({
281
+ type: 'action' as const,
282
+ id: 'qa-copy-path',
283
+ label: 'Скопировать путь',
284
+ icon: 'copy',
285
+ onClick: () => navigator.clipboard.writeText(payload.path)
286
+ });
287
+
288
+ // Дублирование
289
+ if (!payload.isDirectory) {
290
+ items.push({
291
+ type: 'action' as const,
292
+ id: 'qa-duplicate',
293
+ label: 'Дублировать файл',
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', 'Количество слов', `${words} слов в этом файле`);
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', 'Успех', 'Дата добавлена в файл');
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$/, ' (копия).md');
319
+ await this.ctx.invokeApi('fs:write-text-file', newPath, content);
320
+ await this.ctx.invokeApi('dialog:alert', 'Успех', `Создано: ${newPath}`);
321
+ }
322
+
323
+ async onunload(): Promise<void> {}
324
+ }
325
+ ```
326
+
327
+ ---
328
+
329
+ ## Пример 5: Полнофункциональный плагин
330
+
331
+ Комплексный плагин, объединяющий виджеты, настройки и контекстные меню.
332
+
333
+ ### `manifest.json`
334
+
335
+ ```json
336
+ {
337
+ "id": "task-tracker",
338
+ "name": "Трекер задач",
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
+ // === Виджет: Интерактивный чекбокс задачи ===
350
+ const TaskWidget: React.FC<{ match: RegExpExecArray }> = ({ match }) => {
351
+ const status = match[1]; // 'x' или ' '
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
+ // === Класс плагина ===
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. Регистрация виджета
401
+ await ctx.invokeApi(
402
+ 'editor:register-widget',
403
+ 'task-tracker:checkbox',
404
+ /\[([x ])\]\s+(.+?)(?=\n|$)/g,
405
+ TaskWidget
406
+ );
407
+
408
+ // 2. Регистрация настроек
409
+ await ctx.invokeApi('settings:register-tab', {
410
+ id: 'task-tracker',
411
+ label: 'Трекер задач',
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: 'Опции',
420
+ order: 0
421
+ });
422
+
423
+ await ctx.invokeApi('settings:register-items', [
424
+ {
425
+ key: 'task-tracker.style',
426
+ type: 'select',
427
+ label: 'Стиль чекбокса',
428
+ options: [
429
+ { label: 'Круглый', value: 'round' },
430
+ { label: 'Квадратный', 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: 'Зачеркивать выполненные',
440
+ groupId: 'task-tracker-options',
441
+ order: 1,
442
+ defaultValue: true
443
+ }
444
+ ]);
445
+
446
+ // 3. Регистрация контекстного меню
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: 'Подсчитать задачи',
458
+ icon: 'list-checks',
459
+ onClick: () => this.countTasks(payload.path)
460
+ }
461
+ ];
462
+ }
463
+ );
464
+
465
+ await ctx.invokeApi('logger:info', 'TaskTracker', 'Плагин успешно загружен!');
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
+ 'Сводка по задачам',
476
+ `${done}/${total} задач выполнено (${Math.round(done/total*100)}%)`
477
+ );
478
+ }
479
+
480
+ async onunload(): Promise<void> {
481
+ await this.ctx.invokeApi('logger:info', 'TaskTracker', 'Плагин выгружен');
482
+ }
483
+ }
484
+ ```
485
+
486
+ **Использование в документах:**
487
+ ```markdown
488
+ ## Задачи на сегодня
489
+
490
+ [x] Проверить pull request
491
+ [ ] Написать документацию
492
+ [ ] Задеплоить на продакшн
493
+ ```
494
+
495
+ ---
496
+
497
+ ## Конфигурация сборки
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
+ ## Советы по разработке
542
+
543
+ 1. **Используйте `npm run watch`** для автоматической пересборки
544
+ 2. **Notehub автоматически перезагружает** плагины при изменении файлов
545
+ 3. **Откройте DevTools** (Ctrl+Shift+I) для вывода в консоль
546
+ 4. **Используйте `logger:info`** для структурированного логирования
547
+ 5. **Тестируйте инкрементально** — добавляйте функции по одной
@@ -0,0 +1,125 @@
1
+ <h1 align="center">🔌 Руководство разработчика плагинов Notehub</h1>
2
+
3
+ <p align="center">
4
+ <em>Создавайте мощные плагины для Notehub.md — расширяемого приложения для заметок</em>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="#-быстрый-старт">Быстрый старт</a> •
9
+ <a href="#-документация">Документация</a> •
10
+ <a href="#-примеры">Примеры</a> •
11
+ <a href="#-справочник-api">Справочник API</a>
12
+ </p>
13
+
14
+ ---
15
+
16
+ ## 🚀 Быстрый старт
17
+
18
+ ### Вариант 1: Генератор плагинов (Рекомендуется)
19
+
20
+ Для **внутренних плагинов** (часть монорепо):
21
+
22
+ ```bash
23
+ pnpm gen:plugin
24
+ ```
25
+
26
+ Интерактивный CLI:
27
+ 1. Запросит имя плагина (kebab-case, например `my-feature`)
28
+ 2. Предложит выбрать категорию (`system`, `ui`, `features`)
29
+ 3. Сгенерирует полную структуру плагина
30
+
31
+ **Результат:**
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
+ ### Вариант 2: Ручная настройка (Внешние плагины)
52
+
53
+ Для плагинов, загружаемых в runtime из хранилища:
54
+
55
+ #### 1. Создайте папку плагина
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
+ ## 📚 Документация
66
+
67
+ | Глава | Описание |
68
+ |-------|----------|
69
+ | [Начало работы](01-getting-started.md) | Требования, настройка, первый плагин |
70
+ | [Архитектура](02-architecture.md) | Жизненный цикл, EventBus, ApiBus |
71
+ | [Справочник API](03-api-reference.md) | Все 50+ методов API с примерами |
72
+ | [Виджеты](04-widgets.md) | Кастомные React-компоненты в заметках |
73
+ | [Настройки](05-settings.md) | Добавление UI конфигурации |
74
+ | [Контекстное меню](06-context-menu.md) | Интеграция правой кнопки мыши |
75
+ | [Примеры](07-examples.md) | Полные рабочие плагины |
76
+
77
+ ---
78
+
79
+ ## 💡 Что могут делать плагины?
80
+
81
+ | Возможность | API |
82
+ |-------------|-----|
83
+ | 📁 Чтение/запись файлов | `fs:read-text-file`, `fs:write-text-file` |
84
+ | ⚙️ Сохранение настроек | `config:get`, `config:set` |
85
+ | 🎨 Регистрация тем | `theme:register`, `theme:set` |
86
+ | 🧩 Создание виджетов | `editor:register-widget` |
87
+ | 📋 Контекстные меню | `context-menu:register` |
88
+ | 💬 Показ диалогов | `dialog:alert`, `dialog:confirm` |
89
+ | 📡 Подписка на события | `ctx.subscribe()` |
90
+
91
+ ---
92
+
93
+ ## 🎯 Примеры
94
+
95
+ ### Hello World
96
+ ```typescript
97
+ ctx.registerApi('hello:greet', (name: string) => `Привет, ${name}!`);
98
+ ```
99
+
100
+ ### Виджет прогресс-бара
101
+ ```typescript
102
+ await ctx.invokeApi('editor:register-widget', 'progress', /\[progress:(\d+)\]/g,
103
+ ({ match }) => <ProgressBar value={parseInt(match[1])} />);
104
+ ```
105
+
106
+ ### Наблюдатель за файлами
107
+ ```typescript
108
+ ctx.subscribe<{ path: string }>('explorer:file-selected', (payload) => {
109
+ console.log('Выбрано:', payload.path);
110
+ });
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 🔗 Ссылки
116
+
117
+ - [Главный README](../../README.md)
118
+ - [Пакет API](../../packages/api)
119
+ - [Примеры плагинов](../../packages/plugins)
120
+
121
+ ---
122
+
123
+ <p align="center">
124
+ <strong>Удачной разработки! 🎉</strong>
125
+ </p>