@leeoohoo/ui-apps-devkit 0.1.0 → 0.1.2

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.
Files changed (62) hide show
  1. package/README.md +75 -60
  2. package/bin/chatos-uiapp.js +4 -4
  3. package/package.json +26 -20
  4. package/src/cli.js +53 -53
  5. package/src/commands/dev.js +14 -14
  6. package/src/commands/init.js +131 -129
  7. package/src/commands/install.js +47 -46
  8. package/src/commands/pack.js +72 -72
  9. package/src/commands/validate.js +138 -80
  10. package/src/lib/args.js +49 -49
  11. package/src/lib/config.js +29 -29
  12. package/src/lib/fs.js +78 -78
  13. package/src/lib/path-boundary.js +16 -16
  14. package/src/lib/plugin.js +45 -45
  15. package/src/lib/state-constants.js +2 -0
  16. package/src/lib/template.js +172 -168
  17. package/src/sandbox/server.js +1957 -692
  18. package/templates/basic/README.md +78 -54
  19. package/templates/basic/chatos.config.json +5 -5
  20. package/templates/basic/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +214 -181
  21. package/templates/basic/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +75 -74
  22. package/templates/basic/docs/CHATOS_UI_APPS_HOST_API.md +136 -123
  23. package/templates/basic/docs/CHATOS_UI_APPS_OVERVIEW.md +112 -107
  24. package/templates/basic/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +242 -227
  25. package/templates/basic/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -0
  26. package/templates/basic/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +45 -0
  27. package/templates/basic/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  28. package/templates/basic/plugin/apps/app/compact.mjs +41 -0
  29. package/templates/basic/plugin/apps/app/index.mjs +287 -263
  30. package/templates/basic/plugin/apps/app/mcp-prompt.en.md +7 -7
  31. package/templates/basic/plugin/apps/app/mcp-prompt.zh.md +7 -7
  32. package/templates/basic/plugin/apps/app/mcp-server.mjs +15 -15
  33. package/templates/basic/plugin/backend/index.mjs +37 -37
  34. package/templates/basic/template.json +7 -7
  35. package/templates/notepad/README.md +55 -24
  36. package/templates/notepad/chatos.config.json +4 -4
  37. package/templates/notepad/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +214 -181
  38. package/templates/notepad/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +75 -74
  39. package/templates/notepad/docs/CHATOS_UI_APPS_HOST_API.md +136 -123
  40. package/templates/notepad/docs/CHATOS_UI_APPS_OVERVIEW.md +112 -107
  41. package/templates/notepad/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +242 -227
  42. package/templates/notepad/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -0
  43. package/templates/notepad/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +45 -0
  44. package/templates/notepad/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  45. package/templates/notepad/plugin/apps/app/api.mjs +30 -30
  46. package/templates/notepad/plugin/apps/app/compact.mjs +41 -0
  47. package/templates/notepad/plugin/apps/app/dom.mjs +14 -14
  48. package/templates/notepad/plugin/apps/app/ds-tree.mjs +35 -35
  49. package/templates/notepad/plugin/apps/app/index.mjs +1056 -1056
  50. package/templates/notepad/plugin/apps/app/layers.mjs +338 -338
  51. package/templates/notepad/plugin/apps/app/markdown.mjs +120 -120
  52. package/templates/notepad/plugin/apps/app/mcp-prompt.en.md +22 -22
  53. package/templates/notepad/plugin/apps/app/mcp-prompt.zh.md +22 -22
  54. package/templates/notepad/plugin/apps/app/mcp-server.mjs +206 -199
  55. package/templates/notepad/plugin/apps/app/styles.mjs +355 -355
  56. package/templates/notepad/plugin/apps/app/tags.mjs +21 -21
  57. package/templates/notepad/plugin/apps/app/ui.mjs +280 -280
  58. package/templates/notepad/plugin/backend/index.mjs +99 -99
  59. package/templates/notepad/plugin/plugin.json +23 -23
  60. package/templates/notepad/plugin/shared/notepad-paths.mjs +59 -41
  61. package/templates/notepad/plugin/shared/notepad-store.mjs +765 -765
  62. package/templates/notepad/template.json +8 -8
@@ -1,21 +1,21 @@
1
- export function tagsToText(tags) {
2
- return (Array.isArray(tags) ? tags : []).join(', ');
3
- }
4
-
5
- export function parseTags(input) {
6
- const raw = String(input ?? '');
7
- const parts = raw
8
- .split(',')
9
- .map((p) => p.trim())
10
- .filter(Boolean);
11
- const out = [];
12
- const seen = new Set();
13
- for (const tag of parts) {
14
- const key = tag.toLowerCase();
15
- if (seen.has(key)) continue;
16
- seen.add(key);
17
- out.push(tag);
18
- }
19
- return out;
20
- }
21
-
1
+ export function tagsToText(tags) {
2
+ return (Array.isArray(tags) ? tags : []).join(', ');
3
+ }
4
+
5
+ export function parseTags(input) {
6
+ const raw = String(input ?? '');
7
+ const parts = raw
8
+ .split(',')
9
+ .map((p) => p.trim())
10
+ .filter(Boolean);
11
+ const out = [];
12
+ const seen = new Set();
13
+ for (const tag of parts) {
14
+ const key = tag.toLowerCase();
15
+ if (seen.has(key)) continue;
16
+ seen.add(key);
17
+ out.push(tag);
18
+ }
19
+ return out;
20
+ }
21
+
@@ -1,280 +1,280 @@
1
- import { isElement } from './dom.mjs';
2
- import { NOTEPAD_MANAGER_STYLES } from './styles.mjs';
3
-
4
- export function createNotepadManagerUi({ container, slots, ctx, bridgeEnabled }) {
5
- if (!container) throw new Error('container is required');
6
-
7
- const headerSlot = isElement(slots?.header) ? slots.header : null;
8
-
9
- const style = document.createElement('style');
10
- style.textContent = NOTEPAD_MANAGER_STYLES;
11
-
12
- const root = document.createElement('div');
13
- root.className = 'np-root';
14
- root.dataset.editorMode = 'preview';
15
- root.appendChild(style);
16
-
17
- const header = document.createElement('div');
18
- header.className = 'np-header';
19
-
20
- const headerLeft = document.createElement('div');
21
- headerLeft.style.display = 'flex';
22
- headerLeft.style.flexDirection = 'column';
23
- headerLeft.style.gap = '2px';
24
-
25
- const title = document.createElement('div');
26
- title.className = 'np-title';
27
- title.textContent = '记事本';
28
-
29
- const meta = document.createElement('div');
30
- meta.className = 'np-meta';
31
- meta.textContent = `${ctx?.pluginId || ''}:${ctx?.appId || ''} · bridge=${bridgeEnabled ? 'enabled' : 'disabled'}`;
32
-
33
- headerLeft.appendChild(title);
34
- headerLeft.appendChild(meta);
35
-
36
- const headerRight = document.createElement('div');
37
- headerRight.style.display = 'flex';
38
- headerRight.style.alignItems = 'center';
39
- headerRight.style.gap = '8px';
40
- headerRight.style.flexWrap = 'wrap';
41
-
42
- const btnNewFolder = document.createElement('button');
43
- btnNewFolder.type = 'button';
44
- btnNewFolder.className = 'np-btn np-btn-icon';
45
- btnNewFolder.title = '新建文件夹';
46
- btnNewFolder.setAttribute('aria-label', '新建文件夹');
47
- const btnNewFolderIcon = document.createElement('span');
48
- btnNewFolderIcon.className = 'ds-tree-icon ds-tree-icon-new-folder';
49
- btnNewFolder.appendChild(btnNewFolderIcon);
50
-
51
- const btnNewNote = document.createElement('button');
52
- btnNewNote.type = 'button';
53
- btnNewNote.className = 'np-btn np-btn-icon';
54
- btnNewNote.title = '新建笔记';
55
- btnNewNote.setAttribute('aria-label', '新建笔记');
56
- const btnNewNoteIcon = document.createElement('span');
57
- btnNewNoteIcon.className = 'ds-tree-icon ds-tree-icon-new-note';
58
- btnNewNote.appendChild(btnNewNoteIcon);
59
-
60
- const btnSave = document.createElement('button');
61
- btnSave.type = 'button';
62
- btnSave.className = 'np-btn';
63
- btnSave.textContent = '保存';
64
-
65
- const btnDelete = document.createElement('button');
66
- btnDelete.type = 'button';
67
- btnDelete.className = 'np-btn';
68
- btnDelete.textContent = '删除';
69
-
70
- const btnCopy = document.createElement('button');
71
- btnCopy.type = 'button';
72
- btnCopy.className = 'np-btn';
73
- btnCopy.textContent = '复制';
74
- btnCopy.title = '复制当前笔记内容';
75
-
76
- const btnToggleEdit = document.createElement('button');
77
- btnToggleEdit.type = 'button';
78
- btnToggleEdit.className = 'np-btn';
79
- btnToggleEdit.textContent = '编辑';
80
- btnToggleEdit.title = '切换编辑/预览';
81
-
82
- const statusPill = document.createElement('div');
83
- statusPill.className = 'np-pill';
84
- statusPill.dataset.tone = 'bad';
85
- statusPill.textContent = 'Notes: initializing...';
86
-
87
- headerRight.appendChild(statusPill);
88
-
89
- header.appendChild(headerLeft);
90
- header.appendChild(headerRight);
91
-
92
- const grid = document.createElement('div');
93
- grid.className = 'np-grid';
94
-
95
- const leftCard = document.createElement('div');
96
- leftCard.className = 'np-card';
97
- const leftHeader = document.createElement('div');
98
- leftHeader.className = 'np-card-header';
99
- leftHeader.textContent = '分类与检索';
100
- const leftBody = document.createElement('div');
101
- leftBody.className = 'np-card-body';
102
-
103
- const createHint = document.createElement('div');
104
- createHint.className = 'np-meta np-create-hint';
105
- createHint.textContent = '新笔记将创建在:根目录';
106
-
107
- const searchInput = document.createElement('input');
108
- searchInput.className = 'np-input';
109
- searchInput.type = 'text';
110
- searchInput.placeholder = '搜索标题/文件夹/内容…';
111
-
112
- const btnClearSearch = document.createElement('button');
113
- btnClearSearch.type = 'button';
114
- btnClearSearch.className = 'np-btn np-btn-icon';
115
- btnClearSearch.title = '清空搜索';
116
- btnClearSearch.setAttribute('aria-label', '清空搜索');
117
- btnClearSearch.textContent = '×';
118
-
119
- const searchRow = document.createElement('div');
120
- searchRow.className = 'np-row np-row-compact';
121
- searchRow.appendChild(searchInput);
122
- searchRow.appendChild(btnClearSearch);
123
-
124
- const folderSection = document.createElement('div');
125
- const folderTitle = document.createElement('div');
126
- folderTitle.className = 'np-section-title np-section-title-row';
127
- const folderTitleLabel = document.createElement('div');
128
- folderTitleLabel.textContent = '笔记';
129
- const folderTitleActions = document.createElement('div');
130
- folderTitleActions.className = 'np-section-actions';
131
- folderTitleActions.appendChild(btnNewNote);
132
- folderTitleActions.appendChild(btnNewFolder);
133
- folderTitle.appendChild(folderTitleLabel);
134
- folderTitle.appendChild(folderTitleActions);
135
- const folderList = document.createElement('div');
136
- folderList.className = 'ds-tree';
137
- folderSection.appendChild(folderTitle);
138
- folderSection.appendChild(folderList);
139
- folderSection.appendChild(createHint);
140
-
141
- const tagSection = document.createElement('div');
142
- const tagTitle = document.createElement('div');
143
- tagTitle.className = 'np-section-title';
144
- tagTitle.textContent = '标签过滤';
145
- const tagRow = document.createElement('div');
146
- tagRow.className = 'np-chip-row';
147
- tagSection.appendChild(tagTitle);
148
- tagSection.appendChild(tagRow);
149
-
150
- leftBody.appendChild(searchRow);
151
- leftBody.appendChild(folderSection);
152
-
153
- leftCard.appendChild(leftHeader);
154
- leftCard.appendChild(leftBody);
155
-
156
- const rightCard = document.createElement('div');
157
- rightCard.className = 'np-card';
158
- const rightHeader = document.createElement('div');
159
- rightHeader.className = 'np-card-header';
160
- rightHeader.textContent = '';
161
- const rightHeaderTitle = document.createElement('div');
162
- rightHeaderTitle.textContent = '编辑与预览';
163
- const rightHeaderActions = document.createElement('div');
164
- rightHeaderActions.className = 'np-row';
165
- rightHeaderActions.appendChild(btnToggleEdit);
166
- rightHeaderActions.appendChild(btnCopy);
167
- rightHeaderActions.appendChild(btnSave);
168
- rightHeaderActions.appendChild(btnDelete);
169
- rightHeader.appendChild(rightHeaderTitle);
170
- rightHeader.appendChild(rightHeaderActions);
171
- const rightBody = document.createElement('div');
172
- rightBody.className = 'np-card-body';
173
-
174
- const editorTop = document.createElement('div');
175
- editorTop.className = 'np-editor-top';
176
-
177
- const titleInput = document.createElement('input');
178
- titleInput.className = 'np-input';
179
- titleInput.type = 'text';
180
- titleInput.placeholder = '标题';
181
-
182
- const folderSelect = document.createElement('select');
183
- folderSelect.className = 'np-select';
184
- folderSelect.title = '选择文件夹';
185
-
186
- editorTop.appendChild(titleInput);
187
- editorTop.appendChild(folderSelect);
188
-
189
- const editorTopRow = document.createElement('div');
190
- editorTopRow.className = 'np-editor-top-row';
191
-
192
- const tagsInput = document.createElement('input');
193
- tagsInput.className = 'np-input';
194
- tagsInput.type = 'text';
195
- tagsInput.placeholder = '标签(逗号分隔)';
196
-
197
- const infoBox = document.createElement('div');
198
- infoBox.className = 'np-meta';
199
- infoBox.style.alignSelf = 'center';
200
- infoBox.textContent = '未选择笔记';
201
-
202
- editorTopRow.appendChild(tagsInput);
203
- editorTopRow.appendChild(infoBox);
204
-
205
- const split = document.createElement('div');
206
- split.className = 'np-editor-split';
207
-
208
- const textarea = document.createElement('textarea');
209
- textarea.className = 'np-textarea';
210
- textarea.placeholder = '开始写 Markdown…';
211
-
212
- const preview = document.createElement('div');
213
- preview.className = 'np-preview';
214
- preview.innerHTML = '<div class=\"np-meta\">预览区</div>';
215
-
216
- split.appendChild(textarea);
217
- split.appendChild(preview);
218
-
219
- rightBody.appendChild(editorTop);
220
- rightBody.appendChild(editorTopRow);
221
- rightBody.appendChild(split);
222
-
223
- rightCard.appendChild(rightHeader);
224
- rightCard.appendChild(rightBody);
225
-
226
- grid.appendChild(leftCard);
227
- grid.appendChild(rightCard);
228
-
229
- if (headerSlot) {
230
- try {
231
- headerSlot.textContent = '';
232
- } catch {
233
- // ignore
234
- }
235
- try {
236
- headerSlot.appendChild(header);
237
- } catch {
238
- root.appendChild(header);
239
- }
240
- } else {
241
- root.appendChild(header);
242
- }
243
- root.appendChild(grid);
244
-
245
- try {
246
- container.textContent = '';
247
- } catch {
248
- // ignore
249
- }
250
- container.appendChild(root);
251
-
252
- const setStatus = (text, tone) => {
253
- statusPill.textContent = text;
254
- statusPill.dataset.tone = tone === 'ok' ? 'ok' : 'bad';
255
- };
256
-
257
- return {
258
- root,
259
- header,
260
- btnNewFolder,
261
- btnNewNote,
262
- btnSave,
263
- btnDelete,
264
- btnCopy,
265
- btnToggleEdit,
266
- statusPill,
267
- createHint,
268
- searchInput,
269
- btnClearSearch,
270
- folderList,
271
- tagRow,
272
- titleInput,
273
- folderSelect,
274
- tagsInput,
275
- infoBox,
276
- textarea,
277
- preview,
278
- setStatus,
279
- };
280
- }
1
+ import { isElement } from './dom.mjs';
2
+ import { NOTEPAD_MANAGER_STYLES } from './styles.mjs';
3
+
4
+ export function createNotepadManagerUi({ container, slots, ctx, bridgeEnabled }) {
5
+ if (!container) throw new Error('container is required');
6
+
7
+ const headerSlot = isElement(slots?.header) ? slots.header : null;
8
+
9
+ const style = document.createElement('style');
10
+ style.textContent = NOTEPAD_MANAGER_STYLES;
11
+
12
+ const root = document.createElement('div');
13
+ root.className = 'np-root';
14
+ root.dataset.editorMode = 'preview';
15
+ root.appendChild(style);
16
+
17
+ const header = document.createElement('div');
18
+ header.className = 'np-header';
19
+
20
+ const headerLeft = document.createElement('div');
21
+ headerLeft.style.display = 'flex';
22
+ headerLeft.style.flexDirection = 'column';
23
+ headerLeft.style.gap = '2px';
24
+
25
+ const title = document.createElement('div');
26
+ title.className = 'np-title';
27
+ title.textContent = '记事本';
28
+
29
+ const meta = document.createElement('div');
30
+ meta.className = 'np-meta';
31
+ meta.textContent = `${ctx?.pluginId || ''}:${ctx?.appId || ''} · bridge=${bridgeEnabled ? 'enabled' : 'disabled'}`;
32
+
33
+ headerLeft.appendChild(title);
34
+ headerLeft.appendChild(meta);
35
+
36
+ const headerRight = document.createElement('div');
37
+ headerRight.style.display = 'flex';
38
+ headerRight.style.alignItems = 'center';
39
+ headerRight.style.gap = '8px';
40
+ headerRight.style.flexWrap = 'wrap';
41
+
42
+ const btnNewFolder = document.createElement('button');
43
+ btnNewFolder.type = 'button';
44
+ btnNewFolder.className = 'np-btn np-btn-icon';
45
+ btnNewFolder.title = '新建文件夹';
46
+ btnNewFolder.setAttribute('aria-label', '新建文件夹');
47
+ const btnNewFolderIcon = document.createElement('span');
48
+ btnNewFolderIcon.className = 'ds-tree-icon ds-tree-icon-new-folder';
49
+ btnNewFolder.appendChild(btnNewFolderIcon);
50
+
51
+ const btnNewNote = document.createElement('button');
52
+ btnNewNote.type = 'button';
53
+ btnNewNote.className = 'np-btn np-btn-icon';
54
+ btnNewNote.title = '新建笔记';
55
+ btnNewNote.setAttribute('aria-label', '新建笔记');
56
+ const btnNewNoteIcon = document.createElement('span');
57
+ btnNewNoteIcon.className = 'ds-tree-icon ds-tree-icon-new-note';
58
+ btnNewNote.appendChild(btnNewNoteIcon);
59
+
60
+ const btnSave = document.createElement('button');
61
+ btnSave.type = 'button';
62
+ btnSave.className = 'np-btn';
63
+ btnSave.textContent = '保存';
64
+
65
+ const btnDelete = document.createElement('button');
66
+ btnDelete.type = 'button';
67
+ btnDelete.className = 'np-btn';
68
+ btnDelete.textContent = '删除';
69
+
70
+ const btnCopy = document.createElement('button');
71
+ btnCopy.type = 'button';
72
+ btnCopy.className = 'np-btn';
73
+ btnCopy.textContent = '复制';
74
+ btnCopy.title = '复制当前笔记内容';
75
+
76
+ const btnToggleEdit = document.createElement('button');
77
+ btnToggleEdit.type = 'button';
78
+ btnToggleEdit.className = 'np-btn';
79
+ btnToggleEdit.textContent = '编辑';
80
+ btnToggleEdit.title = '切换编辑/预览';
81
+
82
+ const statusPill = document.createElement('div');
83
+ statusPill.className = 'np-pill';
84
+ statusPill.dataset.tone = 'bad';
85
+ statusPill.textContent = 'Notes: initializing...';
86
+
87
+ headerRight.appendChild(statusPill);
88
+
89
+ header.appendChild(headerLeft);
90
+ header.appendChild(headerRight);
91
+
92
+ const grid = document.createElement('div');
93
+ grid.className = 'np-grid';
94
+
95
+ const leftCard = document.createElement('div');
96
+ leftCard.className = 'np-card';
97
+ const leftHeader = document.createElement('div');
98
+ leftHeader.className = 'np-card-header';
99
+ leftHeader.textContent = '分类与检索';
100
+ const leftBody = document.createElement('div');
101
+ leftBody.className = 'np-card-body';
102
+
103
+ const createHint = document.createElement('div');
104
+ createHint.className = 'np-meta np-create-hint';
105
+ createHint.textContent = '新笔记将创建在:根目录';
106
+
107
+ const searchInput = document.createElement('input');
108
+ searchInput.className = 'np-input';
109
+ searchInput.type = 'text';
110
+ searchInput.placeholder = '搜索标题/文件夹/内容…';
111
+
112
+ const btnClearSearch = document.createElement('button');
113
+ btnClearSearch.type = 'button';
114
+ btnClearSearch.className = 'np-btn np-btn-icon';
115
+ btnClearSearch.title = '清空搜索';
116
+ btnClearSearch.setAttribute('aria-label', '清空搜索');
117
+ btnClearSearch.textContent = '×';
118
+
119
+ const searchRow = document.createElement('div');
120
+ searchRow.className = 'np-row np-row-compact';
121
+ searchRow.appendChild(searchInput);
122
+ searchRow.appendChild(btnClearSearch);
123
+
124
+ const folderSection = document.createElement('div');
125
+ const folderTitle = document.createElement('div');
126
+ folderTitle.className = 'np-section-title np-section-title-row';
127
+ const folderTitleLabel = document.createElement('div');
128
+ folderTitleLabel.textContent = '笔记';
129
+ const folderTitleActions = document.createElement('div');
130
+ folderTitleActions.className = 'np-section-actions';
131
+ folderTitleActions.appendChild(btnNewNote);
132
+ folderTitleActions.appendChild(btnNewFolder);
133
+ folderTitle.appendChild(folderTitleLabel);
134
+ folderTitle.appendChild(folderTitleActions);
135
+ const folderList = document.createElement('div');
136
+ folderList.className = 'ds-tree';
137
+ folderSection.appendChild(folderTitle);
138
+ folderSection.appendChild(folderList);
139
+ folderSection.appendChild(createHint);
140
+
141
+ const tagSection = document.createElement('div');
142
+ const tagTitle = document.createElement('div');
143
+ tagTitle.className = 'np-section-title';
144
+ tagTitle.textContent = '标签过滤';
145
+ const tagRow = document.createElement('div');
146
+ tagRow.className = 'np-chip-row';
147
+ tagSection.appendChild(tagTitle);
148
+ tagSection.appendChild(tagRow);
149
+
150
+ leftBody.appendChild(searchRow);
151
+ leftBody.appendChild(folderSection);
152
+
153
+ leftCard.appendChild(leftHeader);
154
+ leftCard.appendChild(leftBody);
155
+
156
+ const rightCard = document.createElement('div');
157
+ rightCard.className = 'np-card';
158
+ const rightHeader = document.createElement('div');
159
+ rightHeader.className = 'np-card-header';
160
+ rightHeader.textContent = '';
161
+ const rightHeaderTitle = document.createElement('div');
162
+ rightHeaderTitle.textContent = '编辑与预览';
163
+ const rightHeaderActions = document.createElement('div');
164
+ rightHeaderActions.className = 'np-row';
165
+ rightHeaderActions.appendChild(btnToggleEdit);
166
+ rightHeaderActions.appendChild(btnCopy);
167
+ rightHeaderActions.appendChild(btnSave);
168
+ rightHeaderActions.appendChild(btnDelete);
169
+ rightHeader.appendChild(rightHeaderTitle);
170
+ rightHeader.appendChild(rightHeaderActions);
171
+ const rightBody = document.createElement('div');
172
+ rightBody.className = 'np-card-body';
173
+
174
+ const editorTop = document.createElement('div');
175
+ editorTop.className = 'np-editor-top';
176
+
177
+ const titleInput = document.createElement('input');
178
+ titleInput.className = 'np-input';
179
+ titleInput.type = 'text';
180
+ titleInput.placeholder = '标题';
181
+
182
+ const folderSelect = document.createElement('select');
183
+ folderSelect.className = 'np-select';
184
+ folderSelect.title = '选择文件夹';
185
+
186
+ editorTop.appendChild(titleInput);
187
+ editorTop.appendChild(folderSelect);
188
+
189
+ const editorTopRow = document.createElement('div');
190
+ editorTopRow.className = 'np-editor-top-row';
191
+
192
+ const tagsInput = document.createElement('input');
193
+ tagsInput.className = 'np-input';
194
+ tagsInput.type = 'text';
195
+ tagsInput.placeholder = '标签(逗号分隔)';
196
+
197
+ const infoBox = document.createElement('div');
198
+ infoBox.className = 'np-meta';
199
+ infoBox.style.alignSelf = 'center';
200
+ infoBox.textContent = '未选择笔记';
201
+
202
+ editorTopRow.appendChild(tagsInput);
203
+ editorTopRow.appendChild(infoBox);
204
+
205
+ const split = document.createElement('div');
206
+ split.className = 'np-editor-split';
207
+
208
+ const textarea = document.createElement('textarea');
209
+ textarea.className = 'np-textarea';
210
+ textarea.placeholder = '开始写 Markdown…';
211
+
212
+ const preview = document.createElement('div');
213
+ preview.className = 'np-preview';
214
+ preview.innerHTML = '<div class=\"np-meta\">预览区</div>';
215
+
216
+ split.appendChild(textarea);
217
+ split.appendChild(preview);
218
+
219
+ rightBody.appendChild(editorTop);
220
+ rightBody.appendChild(editorTopRow);
221
+ rightBody.appendChild(split);
222
+
223
+ rightCard.appendChild(rightHeader);
224
+ rightCard.appendChild(rightBody);
225
+
226
+ grid.appendChild(leftCard);
227
+ grid.appendChild(rightCard);
228
+
229
+ if (headerSlot) {
230
+ try {
231
+ headerSlot.textContent = '';
232
+ } catch {
233
+ // ignore
234
+ }
235
+ try {
236
+ headerSlot.appendChild(header);
237
+ } catch {
238
+ root.appendChild(header);
239
+ }
240
+ } else {
241
+ root.appendChild(header);
242
+ }
243
+ root.appendChild(grid);
244
+
245
+ try {
246
+ container.textContent = '';
247
+ } catch {
248
+ // ignore
249
+ }
250
+ container.appendChild(root);
251
+
252
+ const setStatus = (text, tone) => {
253
+ statusPill.textContent = text;
254
+ statusPill.dataset.tone = tone === 'ok' ? 'ok' : 'bad';
255
+ };
256
+
257
+ return {
258
+ root,
259
+ header,
260
+ btnNewFolder,
261
+ btnNewNote,
262
+ btnSave,
263
+ btnDelete,
264
+ btnCopy,
265
+ btnToggleEdit,
266
+ statusPill,
267
+ createHint,
268
+ searchInput,
269
+ btnClearSearch,
270
+ folderList,
271
+ tagRow,
272
+ titleInput,
273
+ folderSelect,
274
+ tagsInput,
275
+ infoBox,
276
+ textarea,
277
+ preview,
278
+ setStatus,
279
+ };
280
+ }