@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,283 @@
|
|
|
1
|
+
# Интеграция контекстных меню
|
|
2
|
+
|
|
3
|
+
Добавление кастомных элементов в контекстные меню по всему Notehub.
|
|
4
|
+
|
|
5
|
+
## Обзор
|
|
6
|
+
|
|
7
|
+
Контекстные меню динамические — провайдеры вызываются при открытии меню и могут возвращать разные элементы в зависимости от того, на что кликнули.
|
|
8
|
+
|
|
9
|
+
## Регистрация провайдера меню
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const unsubscribe = await ctx.invokeApi<() => void>(
|
|
13
|
+
'context-menu:register',
|
|
14
|
+
contextId, // Где появляется меню
|
|
15
|
+
provider // Функция, возвращающая элементы меню
|
|
16
|
+
);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Идентификаторы контекстов
|
|
20
|
+
|
|
21
|
+
| ID контекста | Срабатывает на | Payload |
|
|
22
|
+
|--------------|----------------|---------|
|
|
23
|
+
| `explorer-item` | Файл/папка в проводнике | `{ path: string, isDirectory: boolean }` |
|
|
24
|
+
|
|
25
|
+
## Типы элементов меню
|
|
26
|
+
|
|
27
|
+
### Action (Действие)
|
|
28
|
+
|
|
29
|
+
Кликабельный элемент меню:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
{
|
|
33
|
+
type: 'action',
|
|
34
|
+
id: 'my-action', // Уникальный идентификатор
|
|
35
|
+
label: 'Моё действие', // Отображаемый текст
|
|
36
|
+
icon: 'star', // Иконка Lucide (опционально)
|
|
37
|
+
color: 'var(--nh-danger)', // CSS-цвет (опционально)
|
|
38
|
+
disabled: false, // Сделать серым если true
|
|
39
|
+
onClick: (payload) => {
|
|
40
|
+
// Обработка клика
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Separator (Разделитель)
|
|
46
|
+
|
|
47
|
+
Визуальный разделитель:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
{
|
|
51
|
+
type: 'separator'
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Submenu (Подменю)
|
|
56
|
+
|
|
57
|
+
Вложенные элементы меню:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
{
|
|
61
|
+
type: 'submenu',
|
|
62
|
+
label: 'Дополнительно',
|
|
63
|
+
icon: 'more-horizontal',
|
|
64
|
+
items: [
|
|
65
|
+
{ type: 'action', id: 'sub-1', label: 'Опция 1', onClick: () => {} },
|
|
66
|
+
{ type: 'action', id: 'sub-2', label: 'Опция 2', onClick: () => {} }
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Полный пример
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { NotehubPlugin, PluginContext } from '@notehub/api';
|
|
77
|
+
|
|
78
|
+
interface ExplorerPayload {
|
|
79
|
+
path: string;
|
|
80
|
+
isDirectory: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default class ContextMenuPlugin extends NotehubPlugin {
|
|
84
|
+
private unsubscribe?: () => void;
|
|
85
|
+
|
|
86
|
+
async onload(ctx: PluginContext): Promise<void> {
|
|
87
|
+
// Регистрация провайдера для элементов проводника
|
|
88
|
+
this.unsubscribe = await ctx.invokeApi(
|
|
89
|
+
'context-menu:register',
|
|
90
|
+
'explorer-item',
|
|
91
|
+
(payload: ExplorerPayload) => this.getMenuItems(payload, ctx)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private getMenuItems(payload: ExplorerPayload, ctx: PluginContext) {
|
|
96
|
+
const items = [];
|
|
97
|
+
|
|
98
|
+
// Только для markdown-файлов
|
|
99
|
+
if (payload.path.endsWith('.md')) {
|
|
100
|
+
items.push({
|
|
101
|
+
type: 'action' as const,
|
|
102
|
+
id: 'count-words',
|
|
103
|
+
label: 'Подсчитать слова',
|
|
104
|
+
icon: 'hash',
|
|
105
|
+
onClick: async () => {
|
|
106
|
+
const content = await ctx.invokeApi<string>(
|
|
107
|
+
'fs:read-text-file',
|
|
108
|
+
payload.path
|
|
109
|
+
);
|
|
110
|
+
const wordCount = content.split(/\s+/).length;
|
|
111
|
+
await ctx.invokeApi(
|
|
112
|
+
'dialog:alert',
|
|
113
|
+
'Количество слов',
|
|
114
|
+
`${wordCount} слов`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Разделитель
|
|
121
|
+
if (items.length > 0) {
|
|
122
|
+
items.push({ type: 'separator' as const });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Подменю с опциями экспорта
|
|
126
|
+
items.push({
|
|
127
|
+
type: 'submenu' as const,
|
|
128
|
+
label: 'Экспортировать как',
|
|
129
|
+
icon: 'file-output',
|
|
130
|
+
items: [
|
|
131
|
+
{
|
|
132
|
+
type: 'action' as const,
|
|
133
|
+
id: 'export-txt',
|
|
134
|
+
label: 'Простой текст',
|
|
135
|
+
onClick: () => this.exportAs(payload.path, 'txt', ctx)
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'action' as const,
|
|
139
|
+
id: 'export-html',
|
|
140
|
+
label: 'HTML',
|
|
141
|
+
onClick: () => this.exportAs(payload.path, 'html', ctx)
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Опасное действие (показывается красным)
|
|
147
|
+
items.push({
|
|
148
|
+
type: 'action' as const,
|
|
149
|
+
id: 'archive',
|
|
150
|
+
label: 'Переместить в архив',
|
|
151
|
+
icon: 'archive',
|
|
152
|
+
color: 'var(--nh-danger)',
|
|
153
|
+
onClick: () => this.archiveFile(payload.path, ctx)
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return items;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private async exportAs(path: string, format: string, ctx: PluginContext) {
|
|
160
|
+
await ctx.invokeApi('logger:info', 'ContextMenu', `Экспорт ${path} как ${format}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async archiveFile(path: string, ctx: PluginContext) {
|
|
164
|
+
const confirmed = await ctx.invokeApi<boolean>(
|
|
165
|
+
'dialog:confirm',
|
|
166
|
+
'Архивировать файл',
|
|
167
|
+
`Переместить ${path} в архив?`
|
|
168
|
+
);
|
|
169
|
+
if (confirmed) {
|
|
170
|
+
// Логика перемещения файла
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async onunload(): Promise<void> {
|
|
175
|
+
// Ручная очистка (опционально — автоочистка при выгрузке)
|
|
176
|
+
this.unsubscribe?.();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Динамические элементы меню
|
|
184
|
+
|
|
185
|
+
Провайдеры вызываются каждый раз при открытии меню. Можно возвращать разные элементы на основе:
|
|
186
|
+
|
|
187
|
+
### Типа файла
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
(payload: ExplorerPayload) => {
|
|
191
|
+
if (payload.path.endsWith('.md')) {
|
|
192
|
+
return [/* элементы для markdown */];
|
|
193
|
+
} else if (payload.path.endsWith('.png')) {
|
|
194
|
+
return [/* элементы для изображений */];
|
|
195
|
+
}
|
|
196
|
+
return [/* общие элементы */];
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Директория vs Файл
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
(payload: ExplorerPayload) => {
|
|
204
|
+
if (payload.isDirectory) {
|
|
205
|
+
return [
|
|
206
|
+
{ type: 'action', id: 'new-file', label: 'Новый файл здесь', ... }
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
return [
|
|
210
|
+
{ type: 'action', id: 'duplicate', label: 'Дублировать файл', ... }
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Асинхронные провайдеры
|
|
216
|
+
|
|
217
|
+
Провайдеры могут быть асинхронными:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
async (payload: ExplorerPayload) => {
|
|
221
|
+
const metadata = await loadMetadata(payload.path);
|
|
222
|
+
return [/* элементы на основе метаданных */];
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Справочник иконок
|
|
229
|
+
|
|
230
|
+
Иконки используют имена [Lucide](https://lucide.dev/icons/) в kebab-case:
|
|
231
|
+
|
|
232
|
+
| Имя иконки | Описание |
|
|
233
|
+
|------------|----------|
|
|
234
|
+
| `file` | Общий файл |
|
|
235
|
+
| `folder` | Папка |
|
|
236
|
+
| `star` | Звезда/избранное |
|
|
237
|
+
| `trash-2` | Удаление/корзина |
|
|
238
|
+
| `copy` | Копировать |
|
|
239
|
+
| `scissors` | Вырезать |
|
|
240
|
+
| `clipboard` | Вставить |
|
|
241
|
+
| `edit` | Редактировать |
|
|
242
|
+
| `eye` | Просмотр |
|
|
243
|
+
| `download` | Скачать |
|
|
244
|
+
| `upload` | Загрузить |
|
|
245
|
+
| `archive` | Архив |
|
|
246
|
+
| `more-horizontal` | Больше опций |
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Определения типов
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
type MenuItem = MenuAction | MenuSeparator | SubMenu;
|
|
254
|
+
|
|
255
|
+
interface MenuAction {
|
|
256
|
+
type: 'action';
|
|
257
|
+
id: string;
|
|
258
|
+
label: string;
|
|
259
|
+
icon?: string;
|
|
260
|
+
color?: string;
|
|
261
|
+
disabled?: boolean;
|
|
262
|
+
onClick: (payload: unknown) => void;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
interface MenuSeparator {
|
|
266
|
+
type: 'separator';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
interface SubMenu {
|
|
270
|
+
type: 'submenu';
|
|
271
|
+
label: string;
|
|
272
|
+
icon?: string;
|
|
273
|
+
items: MenuItem[];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
type MenuProvider = (payload: unknown) => MenuItem[] | Promise<MenuItem[]>;
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Следующие шаги
|
|
282
|
+
|
|
283
|
+
- Смотрите **[Полные примеры](07-examples.md)** для полного кода плагинов
|