@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,293 @@
|
|
|
1
|
+
# Виджеты (Порталы)
|
|
2
|
+
|
|
3
|
+
Порталы — это кастомные React-компоненты, которые рендерятся инлайн в редакторе, заменяя совпадающие текстовые паттерны.
|
|
4
|
+
|
|
5
|
+
## Как работают порталы
|
|
6
|
+
|
|
7
|
+
1. Вы определяете **regex-паттерн**, который ищет текст в документе
|
|
8
|
+
2. Вы предоставляете **React-компонент** для рендеринга каждого совпадения
|
|
9
|
+
3. Notehub заменяет совпадающий текст вашим компонентом в **режиме просмотра**
|
|
10
|
+
4. Когда курсор входит в совпадение, переключается в **режим редактирования** (показывает исходный текст)
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Режим просмотра: [████████░░] 80% ← Ваш отрендеренный компонент
|
|
14
|
+
Режим редактирования: [progress:80] ← Исходный текст виден когда курсор внутри
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Регистрация виджета
|
|
18
|
+
|
|
19
|
+
Используйте API `editor:register-widget`:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
await ctx.invokeApi(
|
|
23
|
+
'editor:register-widget',
|
|
24
|
+
'unique-id', // Уникальный идентификатор
|
|
25
|
+
/regex-pattern/g, // Паттерн для поиска (ДОЛЖЕН иметь флаг 'g')
|
|
26
|
+
ReactComponent // Компонент для рендеринга
|
|
27
|
+
);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Пропсы компонента
|
|
31
|
+
|
|
32
|
+
Ваш компонент получает массив результатов regex:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
interface WidgetProps {
|
|
36
|
+
match: RegExpExecArray;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Массив `match` содержит:
|
|
41
|
+
- `match[0]` — Полная совпавшая строка
|
|
42
|
+
- `match[1]`, `match[2]`, ... — Захваченные группы
|
|
43
|
+
|
|
44
|
+
## Полный пример: Прогресс-бар
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { NotehubPlugin, PluginContext } from '@notehub/api';
|
|
48
|
+
import React from 'react';
|
|
49
|
+
|
|
50
|
+
// Компонент виджета
|
|
51
|
+
const ProgressBar: React.FC<{ match: RegExpExecArray }> = ({ match }) => {
|
|
52
|
+
const percentage = parseInt(match[1], 10);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<span style={{
|
|
56
|
+
display: 'inline-flex',
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
gap: '8px',
|
|
59
|
+
padding: '2px 8px',
|
|
60
|
+
background: 'var(--nh-bg-surface)',
|
|
61
|
+
borderRadius: '4px',
|
|
62
|
+
}}>
|
|
63
|
+
<span style={{
|
|
64
|
+
width: '100px',
|
|
65
|
+
height: '8px',
|
|
66
|
+
background: 'var(--nh-bg-secondary)',
|
|
67
|
+
borderRadius: '4px',
|
|
68
|
+
overflow: 'hidden',
|
|
69
|
+
}}>
|
|
70
|
+
<span style={{
|
|
71
|
+
width: `${percentage}%`,
|
|
72
|
+
height: '100%',
|
|
73
|
+
background: 'var(--nh-accent-primary)',
|
|
74
|
+
display: 'block',
|
|
75
|
+
borderRadius: '4px',
|
|
76
|
+
transition: 'width 0.3s ease',
|
|
77
|
+
}} />
|
|
78
|
+
</span>
|
|
79
|
+
<span style={{ fontSize: '12px', color: 'var(--nh-text-muted)' }}>
|
|
80
|
+
{percentage}%
|
|
81
|
+
</span>
|
|
82
|
+
</span>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Плагин
|
|
87
|
+
export default class ProgressBarPlugin extends NotehubPlugin {
|
|
88
|
+
async onload(ctx: PluginContext): Promise<void> {
|
|
89
|
+
// Совпадение: [progress:XX] где XX — число
|
|
90
|
+
await ctx.invokeApi(
|
|
91
|
+
'editor:register-widget',
|
|
92
|
+
'progress-bar',
|
|
93
|
+
/\[progress:(\d+)\]/g,
|
|
94
|
+
ProgressBar
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
await ctx.invokeApi('logger:info', 'ProgressBar', 'Виджет зарегистрирован');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async onunload(): Promise<void> {
|
|
101
|
+
// Виджет автоматически отменяет регистрацию!
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Использование в документах:**
|
|
107
|
+
```markdown
|
|
108
|
+
Завершенность проекта: [progress:75]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Пример: Кликабельная кнопка
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const ButtonWidget: React.FC<{ match: RegExpExecArray }> = ({ match }) => {
|
|
117
|
+
const label = match[1];
|
|
118
|
+
const action = match[2];
|
|
119
|
+
|
|
120
|
+
const handleClick = async () => {
|
|
121
|
+
console.log(`Кнопка нажата: ${action}`);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<button
|
|
126
|
+
onClick={handleClick}
|
|
127
|
+
style={{
|
|
128
|
+
background: 'var(--nh-accent-primary)',
|
|
129
|
+
color: 'var(--nh-button-text, #fff)',
|
|
130
|
+
border: 'none',
|
|
131
|
+
borderRadius: '4px',
|
|
132
|
+
padding: '4px 12px',
|
|
133
|
+
cursor: 'pointer',
|
|
134
|
+
fontSize: 'inherit',
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
{label}
|
|
138
|
+
</button>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Регистрация
|
|
143
|
+
await ctx.invokeApi(
|
|
144
|
+
'editor:register-widget',
|
|
145
|
+
'btn-widget',
|
|
146
|
+
/\[btn:([^\]:]+):([^\]]+)\]/g,
|
|
147
|
+
ButtonWidget
|
|
148
|
+
);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Использование:**
|
|
152
|
+
```markdown
|
|
153
|
+
Нажмите сюда: [btn:Отправить:action-submit]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Пример: Статус-бейдж
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const StatusBadge: React.FC<{ match: RegExpExecArray }> = ({ match }) => {
|
|
162
|
+
const status = match[1].toLowerCase();
|
|
163
|
+
|
|
164
|
+
const colors: Record<string, { bg: string; text: string }> = {
|
|
165
|
+
done: { bg: '#22c55e20', text: '#22c55e' },
|
|
166
|
+
'in-progress': { bg: '#f59e0b20', text: '#f59e0b' },
|
|
167
|
+
todo: { bg: '#6b728020', text: '#6b7280' },
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const style = colors[status] || colors.todo;
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<span style={{
|
|
174
|
+
padding: '2px 8px',
|
|
175
|
+
borderRadius: '12px',
|
|
176
|
+
fontSize: '12px',
|
|
177
|
+
fontWeight: 500,
|
|
178
|
+
background: style.bg,
|
|
179
|
+
color: style.text,
|
|
180
|
+
}}>
|
|
181
|
+
{match[1]}
|
|
182
|
+
</span>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
await ctx.invokeApi(
|
|
187
|
+
'editor:register-widget',
|
|
188
|
+
'status-badge',
|
|
189
|
+
/\[status:([^\]]+)\]/g,
|
|
190
|
+
StatusBadge
|
|
191
|
+
);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Использование:**
|
|
195
|
+
```markdown
|
|
196
|
+
Задача 1 [status:Done]
|
|
197
|
+
Задача 2 [status:In-Progress]
|
|
198
|
+
Задача 3 [status:TODO]
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Лучшие практики Regex
|
|
204
|
+
|
|
205
|
+
### 1. Всегда используйте глобальный флаг (`g`)
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// ✅ Хорошо
|
|
209
|
+
/\[progress:(\d+)\]/g
|
|
210
|
+
|
|
211
|
+
// ❌ Плохо — не найдет множественные вхождения
|
|
212
|
+
/\[progress:(\d+)\]/
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 2. Используйте группы захвата для динамического контента
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// Захватывает две группы: label и value
|
|
219
|
+
/\[meter:([^:]+):(\d+)\]/g
|
|
220
|
+
// match[1] = label
|
|
221
|
+
// match[2] = value
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### 3. Экранируйте специальные символы
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// Совпадение [!note] — скобки нужно экранировать
|
|
228
|
+
/\[!note\]/g
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 4. Будьте конкретны, чтобы избежать ложных совпадений
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// ✅ Хорошо — конкретный паттерн
|
|
235
|
+
/\[progress:(\d{1,3})\]/g
|
|
236
|
+
|
|
237
|
+
// ❌ Плохо — слишком жадный
|
|
238
|
+
/\[.*\]/g // Совпадает со ВСЕМ контентом в скобках!
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Советы по стилизации
|
|
244
|
+
|
|
245
|
+
### Используйте CSS-переменные
|
|
246
|
+
|
|
247
|
+
Используйте цвета темы для согласованной стилизации:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
style={{
|
|
251
|
+
background: 'var(--nh-bg-surface)',
|
|
252
|
+
color: 'var(--nh-text-primary)',
|
|
253
|
+
border: '1px solid var(--nh-border-subtle)',
|
|
254
|
+
}}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Доступные CSS-переменные:
|
|
258
|
+
- `--nh-bg-main`, `--nh-bg-sidebar`, `--nh-bg-surface`
|
|
259
|
+
- `--nh-text-primary`, `--nh-text-secondary`, `--nh-text-muted`
|
|
260
|
+
- `--nh-accent-primary`, `--nh-accent-secondary`
|
|
261
|
+
- `--nh-border-accent`, `--nh-border-subtle`
|
|
262
|
+
|
|
263
|
+
### Держите это инлайн
|
|
264
|
+
|
|
265
|
+
Виджеты рендерятся встроенными в текст. Используйте `display: inline-flex` или `inline-block`:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
style={{
|
|
269
|
+
display: 'inline-flex',
|
|
270
|
+
alignItems: 'center',
|
|
271
|
+
verticalAlign: 'middle',
|
|
272
|
+
}}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Отмена регистрации виджетов
|
|
278
|
+
|
|
279
|
+
Виджеты **автоматически отменяют регистрацию** при выгрузке вашего плагина.
|
|
280
|
+
|
|
281
|
+
Для ручной отмены:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
await ctx.invokeApi('editor:unregister-widget', 'my-widget-id');
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Следующие шаги
|
|
290
|
+
|
|
291
|
+
- Добавьте **[Настройки](05-settings.md)** для конфигурации ваших виджетов
|
|
292
|
+
- Изучите **[Контекстные меню](06-context-menu.md)**
|
|
293
|
+
- Смотрите **[Полные примеры](07-examples.md)**
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Интеграция настроек
|
|
2
|
+
|
|
3
|
+
Добавление параметров конфигурации в ваш плагин через Settings API.
|
|
4
|
+
|
|
5
|
+
## Структура настроек
|
|
6
|
+
|
|
7
|
+
Настройки организованы в иерархию:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Модальное окно настроек
|
|
11
|
+
└── Вкладка (например, "Мой плагин")
|
|
12
|
+
└── Группа (например, "Внешний вид")
|
|
13
|
+
└── Элемент (например, "Включить темный режим")
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Шаг 1: Регистрация вкладки
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
await ctx.invokeApi('settings:register-tab', {
|
|
20
|
+
id: 'my-plugin', // Уникальный идентификатор
|
|
21
|
+
label: 'Мой плагин', // Отображаемое имя
|
|
22
|
+
icon: 'puzzle', // Имя иконки Lucide (kebab-case)
|
|
23
|
+
order: 100 // Позиция (меньше = первее)
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Шаг 2: Регистрация группы
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
await ctx.invokeApi('settings:register-group', {
|
|
31
|
+
id: 'my-plugin-general', // Уникальный идентификатор
|
|
32
|
+
tabId: 'my-plugin', // ID родительской вкладки
|
|
33
|
+
label: 'Основные', // Отображаемое имя
|
|
34
|
+
order: 0 // Позиция внутри вкладки
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Шаг 3: Регистрация элементов
|
|
39
|
+
|
|
40
|
+
### Toggle (Булевый переключатель)
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
await ctx.invokeApi('settings:register-item', {
|
|
44
|
+
key: 'my-plugin.enabled',
|
|
45
|
+
type: 'toggle',
|
|
46
|
+
label: 'Включить плагин',
|
|
47
|
+
description: 'Включение или выключение плагина',
|
|
48
|
+
groupId: 'my-plugin-general',
|
|
49
|
+
order: 0,
|
|
50
|
+
defaultValue: true
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Text (Текстовое поле)
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
await ctx.invokeApi('settings:register-item', {
|
|
58
|
+
key: 'my-plugin.prefix',
|
|
59
|
+
type: 'text',
|
|
60
|
+
label: 'Кастомный префикс',
|
|
61
|
+
description: 'Текст для добавления перед элементами',
|
|
62
|
+
placeholder: 'Введите префикс...',
|
|
63
|
+
groupId: 'my-plugin-general',
|
|
64
|
+
order: 1,
|
|
65
|
+
defaultValue: ''
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Number (Числовое поле)
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
await ctx.invokeApi('settings:register-item', {
|
|
73
|
+
key: 'my-plugin.max-items',
|
|
74
|
+
type: 'number',
|
|
75
|
+
label: 'Максимум элементов',
|
|
76
|
+
description: 'Ограничение количества отображаемых элементов',
|
|
77
|
+
groupId: 'my-plugin-general',
|
|
78
|
+
order: 2,
|
|
79
|
+
min: 1,
|
|
80
|
+
max: 100,
|
|
81
|
+
step: 1,
|
|
82
|
+
defaultValue: 10
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Select (Выпадающий список)
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
await ctx.invokeApi('settings:register-item', {
|
|
90
|
+
key: 'my-plugin.theme',
|
|
91
|
+
type: 'select',
|
|
92
|
+
label: 'Тема виджета',
|
|
93
|
+
description: 'Выбор визуального стиля',
|
|
94
|
+
groupId: 'my-plugin-general',
|
|
95
|
+
order: 3,
|
|
96
|
+
options: [
|
|
97
|
+
{ label: 'По умолчанию', value: 'default' },
|
|
98
|
+
{ label: 'Компактный', value: 'compact' },
|
|
99
|
+
{ label: 'Минимальный', value: 'minimal' }
|
|
100
|
+
],
|
|
101
|
+
defaultValue: 'default'
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Color (Выбор цвета)
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
await ctx.invokeApi('settings:register-item', {
|
|
109
|
+
key: 'my-plugin.accent-color',
|
|
110
|
+
type: 'color',
|
|
111
|
+
label: 'Акцентный цвет',
|
|
112
|
+
description: 'Основной цвет для выделений',
|
|
113
|
+
groupId: 'my-plugin-general',
|
|
114
|
+
order: 4,
|
|
115
|
+
defaultValue: '#3b82f6'
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Чтение значений настроек
|
|
122
|
+
|
|
123
|
+
Используйте API `config:get` для чтения настроек:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const isEnabled = await ctx.invokeApi<boolean>('config:get', 'my-plugin.enabled', true);
|
|
127
|
+
const prefix = await ctx.invokeApi<string>('config:get', 'my-plugin.prefix', '');
|
|
128
|
+
const maxItems = await ctx.invokeApi<number>('config:get', 'my-plugin.max-items', 10);
|
|
129
|
+
const theme = await ctx.invokeApi<string>('config:get', 'my-plugin.theme', 'default');
|
|
130
|
+
const color = await ctx.invokeApi<string>('config:get', 'my-plugin.accent-color', '#3b82f6');
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Полный пример
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { NotehubPlugin, PluginContext } from '@notehub/api';
|
|
139
|
+
|
|
140
|
+
export default class ConfigurablePlugin extends NotehubPlugin {
|
|
141
|
+
async onload(ctx: PluginContext): Promise<void> {
|
|
142
|
+
// Регистрация вкладки настроек
|
|
143
|
+
await ctx.invokeApi('settings:register-tab', {
|
|
144
|
+
id: 'my-plugin',
|
|
145
|
+
label: 'Мой плагин',
|
|
146
|
+
icon: 'settings-2',
|
|
147
|
+
order: 100
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Регистрация группы
|
|
151
|
+
await ctx.invokeApi('settings:register-group', {
|
|
152
|
+
id: 'my-plugin-appearance',
|
|
153
|
+
tabId: 'my-plugin',
|
|
154
|
+
label: 'Внешний вид',
|
|
155
|
+
order: 0
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Регистрация элементов
|
|
159
|
+
await ctx.invokeApi('settings:register-items', [
|
|
160
|
+
{
|
|
161
|
+
key: 'my-plugin.show-icons',
|
|
162
|
+
type: 'toggle',
|
|
163
|
+
label: 'Показывать иконки',
|
|
164
|
+
groupId: 'my-plugin-appearance',
|
|
165
|
+
order: 0,
|
|
166
|
+
defaultValue: true
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
key: 'my-plugin.icon-size',
|
|
170
|
+
type: 'select',
|
|
171
|
+
label: 'Размер иконок',
|
|
172
|
+
groupId: 'my-plugin-appearance',
|
|
173
|
+
order: 1,
|
|
174
|
+
options: [
|
|
175
|
+
{ label: 'Маленький', value: 16 },
|
|
176
|
+
{ label: 'Средний', value: 24 },
|
|
177
|
+
{ label: 'Большой', value: 32 }
|
|
178
|
+
],
|
|
179
|
+
defaultValue: 24
|
|
180
|
+
}
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
// Использование значений
|
|
184
|
+
const showIcons = await ctx.invokeApi<boolean>('config:get', 'my-plugin.show-icons', true);
|
|
185
|
+
const iconSize = await ctx.invokeApi<number>('config:get', 'my-plugin.icon-size', 24);
|
|
186
|
+
|
|
187
|
+
await ctx.invokeApi('logger:info', 'MyPlugin',
|
|
188
|
+
`Настройки: showIcons=${showIcons}, iconSize=${iconSize}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async onunload(): Promise<void> {
|
|
192
|
+
// Элементы настроек автоматически удаляются!
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Пакетная регистрация
|
|
200
|
+
|
|
201
|
+
Для множества элементов используйте пакетные API:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// Регистрация нескольких вкладок
|
|
205
|
+
await ctx.invokeApi('settings:register-tabs', [
|
|
206
|
+
{ id: 'tab1', label: 'Вкладка 1', icon: 'star', order: 0 },
|
|
207
|
+
{ id: 'tab2', label: 'Вкладка 2', icon: 'heart', order: 1 }
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
// Регистрация нескольких групп
|
|
211
|
+
await ctx.invokeApi('settings:register-groups', [
|
|
212
|
+
{ id: 'group1', tabId: 'tab1', label: 'Группа 1', order: 0 },
|
|
213
|
+
{ id: 'group2', tabId: 'tab1', label: 'Группа 2', order: 1 }
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
// Регистрация нескольких элементов
|
|
217
|
+
await ctx.invokeApi('settings:register-items', [/* массив элементов */]);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Кастомное представление настроек
|
|
223
|
+
|
|
224
|
+
Для сложного UI настроек зарегистрируйте кастомный React-компонент:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
const MyCustomSettings: React.FC = () => {
|
|
228
|
+
return (
|
|
229
|
+
<div>
|
|
230
|
+
<h2>Кастомный UI настроек</h2>
|
|
231
|
+
{/* Ваш кастомный интерфейс */}
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
await ctx.invokeApi('settings:register-custom-view', {
|
|
237
|
+
tabId: 'my-plugin',
|
|
238
|
+
view: MyCustomSettings
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Программное управление
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// Открыть модальное окно настроек
|
|
248
|
+
await ctx.invokeApi('settings:open');
|
|
249
|
+
|
|
250
|
+
// Закрыть модальное окно настроек
|
|
251
|
+
await ctx.invokeApi('settings:close');
|
|
252
|
+
|
|
253
|
+
// Переключить модальное окно настроек
|
|
254
|
+
await ctx.invokeApi('settings:toggle');
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Определения типов
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
interface SettingsTabDef {
|
|
263
|
+
id: string; // Уникальный идентификатор
|
|
264
|
+
label: string; // Отображаемый текст
|
|
265
|
+
icon: string; // Имя иконки Lucide
|
|
266
|
+
order: number; // Порядок сортировки
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
interface SettingsGroupDef {
|
|
270
|
+
id: string; // Уникальный идентификатор
|
|
271
|
+
tabId: string; // ID родительской вкладки
|
|
272
|
+
label: string; // Отображаемый текст
|
|
273
|
+
order: number; // Порядок сортировки
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
interface SettingsItemDef {
|
|
277
|
+
key: string; // Ключ конфига (например, 'my-plugin.option')
|
|
278
|
+
type: 'toggle' | 'text' | 'number' | 'select' | 'color';
|
|
279
|
+
label: string; // Отображаемый текст
|
|
280
|
+
description?: string;
|
|
281
|
+
groupId: string; // ID родительской группы
|
|
282
|
+
order: number; // Порядок сортировки
|
|
283
|
+
defaultValue?: unknown;
|
|
284
|
+
|
|
285
|
+
// Для 'text'
|
|
286
|
+
placeholder?: string;
|
|
287
|
+
|
|
288
|
+
// Для 'number'
|
|
289
|
+
min?: number;
|
|
290
|
+
max?: number;
|
|
291
|
+
step?: number;
|
|
292
|
+
|
|
293
|
+
// Для 'select'
|
|
294
|
+
options?: Array<{ label: string; value: unknown }>;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Следующие шаги
|
|
301
|
+
|
|
302
|
+
- Изучите интеграцию **[Контекстных меню](06-context-menu.md)**
|
|
303
|
+
- Смотрите **[Полные примеры](07-examples.md)**
|