@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
|
+
# Context Menu Integration
|
|
2
|
+
|
|
3
|
+
Add custom items to right-click menus throughout Notehub.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Context menus are dynamic - providers are called when the menu is triggered and can return different items based on what was clicked.
|
|
8
|
+
|
|
9
|
+
## Registering a Menu Provider
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const unsubscribe = await ctx.invokeApi<() => void>(
|
|
13
|
+
'context-menu:register',
|
|
14
|
+
contextId, // Where the menu appears
|
|
15
|
+
provider // Function that returns menu items
|
|
16
|
+
);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Context IDs
|
|
20
|
+
|
|
21
|
+
| Context ID | Triggered On | Payload |
|
|
22
|
+
|------------|-------------|---------|
|
|
23
|
+
| `explorer-item` | File/folder in explorer | `{ path: string, isDirectory: boolean }` |
|
|
24
|
+
|
|
25
|
+
## Menu Item Types
|
|
26
|
+
|
|
27
|
+
### Action
|
|
28
|
+
|
|
29
|
+
A clickable menu item:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
{
|
|
33
|
+
type: 'action',
|
|
34
|
+
id: 'my-action', // Unique identifier
|
|
35
|
+
label: 'My Action', // Display text
|
|
36
|
+
icon: 'star', // Lucide icon (optional)
|
|
37
|
+
color: 'var(--nh-danger)', // CSS color (optional)
|
|
38
|
+
disabled: false, // Grey out if true
|
|
39
|
+
onClick: (payload) => {
|
|
40
|
+
// Handle click
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Separator
|
|
46
|
+
|
|
47
|
+
A visual divider:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
{
|
|
51
|
+
type: 'separator'
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Submenu
|
|
56
|
+
|
|
57
|
+
Nested menu items:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
{
|
|
61
|
+
type: 'submenu',
|
|
62
|
+
label: 'More Options',
|
|
63
|
+
icon: 'more-horizontal',
|
|
64
|
+
items: [
|
|
65
|
+
{ type: 'action', id: 'sub-1', label: 'Option 1', onClick: () => {} },
|
|
66
|
+
{ type: 'action', id: 'sub-2', label: 'Option 2', onClick: () => {} }
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Complete Example
|
|
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
|
+
// Register menu provider for explorer items
|
|
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
|
+
// Only show for markdown files
|
|
99
|
+
if (payload.path.endsWith('.md')) {
|
|
100
|
+
items.push({
|
|
101
|
+
type: 'action' as const,
|
|
102
|
+
id: 'count-words',
|
|
103
|
+
label: 'Count Words',
|
|
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
|
+
'Word Count',
|
|
114
|
+
`${wordCount} words`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Separator
|
|
121
|
+
if (items.length > 0) {
|
|
122
|
+
items.push({ type: 'separator' as const });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Add a submenu with export options
|
|
126
|
+
items.push({
|
|
127
|
+
type: 'submenu' as const,
|
|
128
|
+
label: 'Export As',
|
|
129
|
+
icon: 'file-output',
|
|
130
|
+
items: [
|
|
131
|
+
{
|
|
132
|
+
type: 'action' as const,
|
|
133
|
+
id: 'export-txt',
|
|
134
|
+
label: 'Plain Text',
|
|
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
|
+
// Dangerous action (shows in red)
|
|
147
|
+
items.push({
|
|
148
|
+
type: 'action' as const,
|
|
149
|
+
id: 'archive',
|
|
150
|
+
label: 'Move to Archive',
|
|
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', `Exporting ${path} as ${format}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async archiveFile(path: string, ctx: PluginContext) {
|
|
164
|
+
const confirmed = await ctx.invokeApi<boolean>(
|
|
165
|
+
'dialog:confirm',
|
|
166
|
+
'Archive File',
|
|
167
|
+
`Move ${path} to archive?`
|
|
168
|
+
);
|
|
169
|
+
if (confirmed) {
|
|
170
|
+
// Move file logic
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async onunload(): Promise<void> {
|
|
175
|
+
// Manual cleanup (optional - auto-cleaned on unload)
|
|
176
|
+
this.unsubscribe?.();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Dynamic Menu Items
|
|
184
|
+
|
|
185
|
+
Menu providers are called every time the menu opens. You can return different items based on:
|
|
186
|
+
|
|
187
|
+
### File Type
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
(payload: ExplorerPayload) => {
|
|
191
|
+
if (payload.path.endsWith('.md')) {
|
|
192
|
+
return [/* markdown-specific items */];
|
|
193
|
+
} else if (payload.path.endsWith('.png')) {
|
|
194
|
+
return [/* image-specific items */];
|
|
195
|
+
}
|
|
196
|
+
return [/* generic items */];
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Directory vs File
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
(payload: ExplorerPayload) => {
|
|
204
|
+
if (payload.isDirectory) {
|
|
205
|
+
return [
|
|
206
|
+
{ type: 'action', id: 'new-file', label: 'New File Here', ... }
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
return [
|
|
210
|
+
{ type: 'action', id: 'duplicate', label: 'Duplicate File', ... }
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Async Providers
|
|
216
|
+
|
|
217
|
+
Providers can be async:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
async (payload: ExplorerPayload) => {
|
|
221
|
+
const metadata = await loadMetadata(payload.path);
|
|
222
|
+
return [/* items based on metadata */];
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Icon Reference
|
|
229
|
+
|
|
230
|
+
Icons use [Lucide](https://lucide.dev/icons/) icon names in kebab-case:
|
|
231
|
+
|
|
232
|
+
| Icon Name | Description |
|
|
233
|
+
|-----------|-------------|
|
|
234
|
+
| `file` | Generic file |
|
|
235
|
+
| `folder` | Folder |
|
|
236
|
+
| `star` | Star/favorite |
|
|
237
|
+
| `trash-2` | Delete/trash |
|
|
238
|
+
| `copy` | Copy |
|
|
239
|
+
| `scissors` | Cut |
|
|
240
|
+
| `clipboard` | Paste |
|
|
241
|
+
| `edit` | Edit |
|
|
242
|
+
| `eye` | View |
|
|
243
|
+
| `download` | Download |
|
|
244
|
+
| `upload` | Upload |
|
|
245
|
+
| `archive` | Archive |
|
|
246
|
+
| `more-horizontal` | More options |
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Type Definitions
|
|
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
|
+
## Next Steps
|
|
282
|
+
|
|
283
|
+
- See **[Complete Examples](07-examples.md)** for full plugin code
|