mrmd-editor 0.7.1 → 0.8.0
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/package.json +3 -1
- package/src/commands.js +112 -4
- package/src/comment-syntax.js +364 -39
- package/src/config/handlers.js +1 -2
- package/src/config/schema.js +46 -4
- package/src/document-template.js +2236 -0
- package/src/frontmatter-updater.js +204 -74
- package/src/grammar.js +758 -0
- package/src/index.js +1074 -55
- package/src/keymap.js +11 -2
- package/src/markdown/block-decorations.js +108 -5
- package/src/markdown/facets.js +37 -0
- package/src/markdown/html-inline.js +9 -5
- package/src/markdown/index.js +13 -3
- package/src/markdown/inline-commands.js +256 -0
- package/src/markdown/inline-model.js +578 -0
- package/src/markdown/inline-state.js +103 -0
- package/src/markdown/renderer.js +219 -12
- package/src/markdown/styles.js +290 -3
- package/src/markdown/widgets/alert-title.js +10 -8
- package/src/markdown/widgets/frontmatter.js +0 -6
- package/src/markdown/widgets/index.js +1 -0
- package/src/markdown/widgets/list-marker.js +29 -0
- package/src/markdown/wysiwyg.js +1158 -0
- package/src/mrp-types.js +2 -0
- package/src/output-widget.js +532 -18
- package/src/page-view-pagination.js +127 -0
- package/src/runtime-lsp.js +1757 -150
- package/src/section-controls/commands.js +617 -0
- package/src/section-controls/index.js +63 -0
- package/src/section-controls/plugin.js +165 -0
- package/src/section-controls/widgets.js +936 -0
- package/src/shell/ai-menu.js +11 -0
- package/src/shell/components/context-panel.js +572 -0
- package/src/shell/components/status-bar.js +10 -2
- package/src/shell/layouts/studio.js +206 -14
- package/src/shell/orchestrator-client.js +69 -0
- package/src/spellcheck.js +166 -0
- package/src/tables/README.md +97 -0
- package/src/tables/commands/insert-linked-table.js +122 -0
- package/src/tables/commands/open-table-workspace.js +43 -0
- package/src/tables/index.js +24 -0
- package/src/tables/jobs/client.js +158 -0
- package/src/tables/parsing/anchors.js +82 -0
- package/src/tables/parsing/linked-table-blocks.js +61 -0
- package/src/tables/state/linked-table-state.js +68 -0
- package/src/tables/widgets/linked-table-source-banner.js +77 -0
- package/src/tables/widgets/linked-table-widget.js +256 -0
- package/src/tables/workspace/controller.js +616 -0
- package/src/term-pty-client.js +51 -2
- package/src/term-widget.js +43 -3
- package/src/widgets/theme-utils.js +24 -16
- package/src/widgets/theme.js +1015 -1
- package/src/runtime-codelens/detector.js +0 -279
- package/src/runtime-codelens/index.js +0 -76
- package/src/runtime-codelens/plugin.js +0 -142
- package/src/runtime-codelens/styles.js +0 -184
- package/src/runtime-codelens/widgets.js +0 -216
package/src/shell/ai-menu.js
CHANGED
|
@@ -132,6 +132,17 @@ export const AI_COMMANDS = {
|
|
|
132
132
|
requiresSelection: true,
|
|
133
133
|
description: 'Clean up markdown formatting',
|
|
134
134
|
},
|
|
135
|
+
|
|
136
|
+
// Editor actions
|
|
137
|
+
FRONTMATTER_TEMPLATE: {
|
|
138
|
+
id: 'frontmatter-template',
|
|
139
|
+
label: 'Insert Frontmatter Template',
|
|
140
|
+
icon: '¶',
|
|
141
|
+
action: 'insert-frontmatter-template',
|
|
142
|
+
type: 'action',
|
|
143
|
+
requiresSelection: false,
|
|
144
|
+
description: 'Insert scholarly YAML frontmatter at the top of the document',
|
|
145
|
+
},
|
|
135
146
|
};
|
|
136
147
|
|
|
137
148
|
/**
|
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Context side panel for markdown-managed AI context.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const CONTEXT_PANEL_STYLES = `
|
|
6
|
+
.mrmd-context-panel {
|
|
7
|
+
width: 320px;
|
|
8
|
+
min-width: 240px;
|
|
9
|
+
max-width: 420px;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
border-left: 1px solid var(--mrmd-border, #333);
|
|
13
|
+
background: var(--mrmd-panel-bg, #161b22);
|
|
14
|
+
color: var(--mrmd-fg, #c9d1d9);
|
|
15
|
+
font-family: var(--mrmd-ui-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
|
|
16
|
+
font-size: 12px;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.mrmd-context-panel--collapsed {
|
|
21
|
+
width: 38px;
|
|
22
|
+
min-width: 38px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.mrmd-context-panel__header {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 8px;
|
|
29
|
+
padding: 10px 12px;
|
|
30
|
+
border-bottom: 1px solid var(--mrmd-border, #333);
|
|
31
|
+
background: var(--mrmd-panel-header-bg, rgba(255,255,255,0.02));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.mrmd-context-panel__title {
|
|
35
|
+
flex: 1;
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
color: var(--mrmd-fg, #c9d1d9);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.mrmd-context-panel__actions {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
gap: 4px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.mrmd-context-panel__btn {
|
|
47
|
+
display: inline-flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
min-width: 26px;
|
|
51
|
+
height: 26px;
|
|
52
|
+
padding: 0 8px;
|
|
53
|
+
border: 1px solid var(--mrmd-border, #333);
|
|
54
|
+
border-radius: 6px;
|
|
55
|
+
background: var(--mrmd-bg, #0d1117);
|
|
56
|
+
color: var(--mrmd-fg, #c9d1d9);
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
font-size: 12px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.mrmd-context-panel__btn:hover {
|
|
62
|
+
border-color: var(--mrmd-accent, #58a6ff);
|
|
63
|
+
color: var(--mrmd-accent, #58a6ff);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.mrmd-context-panel__btn:disabled {
|
|
67
|
+
opacity: 0.45;
|
|
68
|
+
cursor: default;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.mrmd-context-panel__body {
|
|
72
|
+
flex: 1;
|
|
73
|
+
overflow: auto;
|
|
74
|
+
padding: 12px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.mrmd-context-panel--collapsed .mrmd-context-panel__body,
|
|
78
|
+
.mrmd-context-panel--collapsed .mrmd-context-panel__title,
|
|
79
|
+
.mrmd-context-panel--collapsed .mrmd-context-panel__actions > :not(.mrmd-context-panel__toggle) {
|
|
80
|
+
display: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.mrmd-context-panel--collapsed .mrmd-context-panel__header {
|
|
84
|
+
justify-content: center;
|
|
85
|
+
padding: 10px 6px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.mrmd-context-panel__section {
|
|
89
|
+
margin-bottom: 14px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.mrmd-context-panel__section-title {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: space-between;
|
|
96
|
+
gap: 8px;
|
|
97
|
+
font-size: 11px;
|
|
98
|
+
text-transform: uppercase;
|
|
99
|
+
letter-spacing: 0.5px;
|
|
100
|
+
color: var(--mrmd-fg-muted, #8b949e);
|
|
101
|
+
margin-bottom: 6px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.mrmd-context-panel__meta,
|
|
105
|
+
.mrmd-context-panel__message {
|
|
106
|
+
line-height: 1.45;
|
|
107
|
+
color: var(--mrmd-fg-muted, #8b949e);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.mrmd-context-panel__message {
|
|
111
|
+
padding: 12px;
|
|
112
|
+
border: 1px dashed var(--mrmd-border, #333);
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.mrmd-context-panel__path {
|
|
117
|
+
font-family: var(--mrmd-code-font, 'SF Mono', Consolas, monospace);
|
|
118
|
+
font-size: 11px;
|
|
119
|
+
color: var(--mrmd-accent, #58a6ff);
|
|
120
|
+
word-break: break-all;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.mrmd-context-panel__source-list {
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
gap: 8px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.mrmd-context-panel__source {
|
|
130
|
+
border: 1px solid var(--mrmd-border, #333);
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
padding: 8px 10px;
|
|
133
|
+
background: var(--mrmd-bg-secondary, rgba(255,255,255,0.02));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.mrmd-context-panel__source-main {
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
justify-content: space-between;
|
|
140
|
+
gap: 8px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.mrmd-context-panel__source-name {
|
|
144
|
+
font-weight: 500;
|
|
145
|
+
color: var(--mrmd-fg, #c9d1d9);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.mrmd-context-panel__source-sub {
|
|
149
|
+
margin-top: 4px;
|
|
150
|
+
color: var(--mrmd-fg-muted, #8b949e);
|
|
151
|
+
font-size: 11px;
|
|
152
|
+
line-height: 1.4;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.mrmd-context-panel__badge {
|
|
156
|
+
display: inline-flex;
|
|
157
|
+
align-items: center;
|
|
158
|
+
padding: 1px 6px;
|
|
159
|
+
border-radius: 999px;
|
|
160
|
+
border: 1px solid var(--mrmd-border, #333);
|
|
161
|
+
color: var(--mrmd-fg-muted, #8b949e);
|
|
162
|
+
font-size: 10px;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.mrmd-context-panel__footer {
|
|
166
|
+
padding-top: 6px;
|
|
167
|
+
border-top: 1px solid var(--mrmd-border, #333);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.mrmd-context-panel__tokenbar {
|
|
171
|
+
position: relative;
|
|
172
|
+
height: 8px;
|
|
173
|
+
overflow: hidden;
|
|
174
|
+
border-radius: 999px;
|
|
175
|
+
background: var(--mrmd-hover-bg, rgba(255,255,255,0.06));
|
|
176
|
+
margin: 8px 0 6px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.mrmd-context-panel__tokenfill {
|
|
180
|
+
position: absolute;
|
|
181
|
+
top: 0;
|
|
182
|
+
left: 0;
|
|
183
|
+
bottom: 0;
|
|
184
|
+
background: linear-gradient(90deg, var(--mrmd-accent, #58a6ff), var(--mrmd-success, #2ea043));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.mrmd-context-panel__row {
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
justify-content: space-between;
|
|
191
|
+
gap: 8px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.mrmd-context-panel__small {
|
|
195
|
+
font-size: 11px;
|
|
196
|
+
color: var(--mrmd-fg-muted, #8b949e);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@media (max-width: 960px) {
|
|
200
|
+
.mrmd-context-panel {
|
|
201
|
+
display: none;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
`;
|
|
205
|
+
|
|
206
|
+
let stylesInjected = false;
|
|
207
|
+
|
|
208
|
+
function injectContextPanelStyles() {
|
|
209
|
+
if (stylesInjected) return;
|
|
210
|
+
const style = document.createElement('style');
|
|
211
|
+
style.id = 'mrmd-context-panel-styles';
|
|
212
|
+
style.textContent = CONTEXT_PANEL_STYLES;
|
|
213
|
+
document.head.appendChild(style);
|
|
214
|
+
stylesInjected = true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function formatTokens(count) {
|
|
218
|
+
const value = Number(count || 0);
|
|
219
|
+
if (value >= 1000) return `${(value / 1000).toFixed(1)}k`;
|
|
220
|
+
return String(value);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function escapeHtml(value) {
|
|
224
|
+
return String(value ?? '')
|
|
225
|
+
.replace(/&/g, '&')
|
|
226
|
+
.replace(/</g, '<')
|
|
227
|
+
.replace(/>/g, '>')
|
|
228
|
+
.replace(/"/g, '"')
|
|
229
|
+
.replace(/'/g, ''');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function titleForSource(source) {
|
|
233
|
+
const type = source?.type || 'source';
|
|
234
|
+
switch (type) {
|
|
235
|
+
case 'document': return `📄 Document`;
|
|
236
|
+
case 'linked-page': return `🔗 ${source.name || 'Linked Page'}`;
|
|
237
|
+
case 'images': return `🖼️ Images`;
|
|
238
|
+
case 'runtime': return `🐍 Runtime`;
|
|
239
|
+
case 'runtime-variables': return `🐍 Variables`;
|
|
240
|
+
case 'runtime-docstrings': return `📚 Docstrings`;
|
|
241
|
+
case 'runtime-paths': return `📍 Source Paths`;
|
|
242
|
+
case 'runtime-source': return `💾 Source Code`;
|
|
243
|
+
case 'file': return `📎 ${source.path || 'File'}`;
|
|
244
|
+
case 'url': return `🌐 ${source.url || 'URL'}`;
|
|
245
|
+
case 'notes': return `📝 Notes`;
|
|
246
|
+
default: return type;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function describeSource(source) {
|
|
251
|
+
if (!source) return '';
|
|
252
|
+
if (source.type === 'document') {
|
|
253
|
+
return `mode=${source.mode || 'full'}`;
|
|
254
|
+
}
|
|
255
|
+
if (source.type === 'linked-page') {
|
|
256
|
+
return [source.path, source.depth != null ? `depth ${source.depth}` : null].filter(Boolean).join(' · ');
|
|
257
|
+
}
|
|
258
|
+
if (source.type === 'runtime') {
|
|
259
|
+
return source.available ? [source.runtimeUrl, source.runtimePort ? `port ${source.runtimePort}` : null].filter(Boolean).join(' · ') : 'No runtime attached';
|
|
260
|
+
}
|
|
261
|
+
if (source.type === 'images') {
|
|
262
|
+
return source.count != null ? `${source.count} image${source.count === 1 ? '' : 's'}` : '';
|
|
263
|
+
}
|
|
264
|
+
if (source.type === 'file') {
|
|
265
|
+
return source.path || '';
|
|
266
|
+
}
|
|
267
|
+
if (source.type === 'url') {
|
|
268
|
+
return source.url || '';
|
|
269
|
+
}
|
|
270
|
+
if (source.count != null) {
|
|
271
|
+
return `${source.count} item${source.count === 1 ? '' : 's'}`;
|
|
272
|
+
}
|
|
273
|
+
return '';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function createContextPanel(options) {
|
|
277
|
+
const {
|
|
278
|
+
container,
|
|
279
|
+
orchestratorClient,
|
|
280
|
+
shellState,
|
|
281
|
+
getCurrentDocument,
|
|
282
|
+
getEditor,
|
|
283
|
+
getAiContext,
|
|
284
|
+
onOpenRaw,
|
|
285
|
+
} = options;
|
|
286
|
+
|
|
287
|
+
injectContextPanelStyles();
|
|
288
|
+
|
|
289
|
+
const state = {
|
|
290
|
+
collapsed: false,
|
|
291
|
+
loading: false,
|
|
292
|
+
doc: null,
|
|
293
|
+
data: null,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const panel = document.createElement('aside');
|
|
297
|
+
panel.className = 'mrmd-context-panel';
|
|
298
|
+
|
|
299
|
+
const header = document.createElement('div');
|
|
300
|
+
header.className = 'mrmd-context-panel__header';
|
|
301
|
+
|
|
302
|
+
const title = document.createElement('div');
|
|
303
|
+
title.className = 'mrmd-context-panel__title';
|
|
304
|
+
title.textContent = 'Context';
|
|
305
|
+
|
|
306
|
+
const actions = document.createElement('div');
|
|
307
|
+
actions.className = 'mrmd-context-panel__actions';
|
|
308
|
+
|
|
309
|
+
const refreshBtn = document.createElement('button');
|
|
310
|
+
refreshBtn.className = 'mrmd-context-panel__btn';
|
|
311
|
+
refreshBtn.title = 'Refresh resolved context';
|
|
312
|
+
refreshBtn.textContent = '⟳';
|
|
313
|
+
|
|
314
|
+
const rawBtn = document.createElement('button');
|
|
315
|
+
rawBtn.className = 'mrmd-context-panel__btn';
|
|
316
|
+
rawBtn.title = 'Open raw context markdown';
|
|
317
|
+
rawBtn.textContent = '✏';
|
|
318
|
+
|
|
319
|
+
const materializeBtn = document.createElement('button');
|
|
320
|
+
materializeBtn.className = 'mrmd-context-panel__btn';
|
|
321
|
+
materializeBtn.title = 'Create a document-specific context from the current effective context';
|
|
322
|
+
materializeBtn.textContent = '⎘';
|
|
323
|
+
|
|
324
|
+
const toggleBtn = document.createElement('button');
|
|
325
|
+
toggleBtn.className = 'mrmd-context-panel__btn mrmd-context-panel__toggle';
|
|
326
|
+
toggleBtn.title = 'Collapse context panel';
|
|
327
|
+
toggleBtn.textContent = '›';
|
|
328
|
+
|
|
329
|
+
actions.append(refreshBtn, rawBtn, materializeBtn, toggleBtn);
|
|
330
|
+
header.append(title, actions);
|
|
331
|
+
|
|
332
|
+
const body = document.createElement('div');
|
|
333
|
+
body.className = 'mrmd-context-panel__body';
|
|
334
|
+
|
|
335
|
+
panel.append(header, body);
|
|
336
|
+
container.appendChild(panel);
|
|
337
|
+
|
|
338
|
+
function setCollapsed(collapsed) {
|
|
339
|
+
state.collapsed = collapsed;
|
|
340
|
+
panel.classList.toggle('mrmd-context-panel--collapsed', collapsed);
|
|
341
|
+
toggleBtn.textContent = collapsed ? '‹' : '›';
|
|
342
|
+
toggleBtn.title = collapsed ? 'Expand context panel' : 'Collapse context panel';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function currentContextRequest() {
|
|
346
|
+
const doc = getCurrentDocument?.();
|
|
347
|
+
const editor = getEditor?.();
|
|
348
|
+
let content = null;
|
|
349
|
+
let cursorPos = null;
|
|
350
|
+
let selection = null;
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
if (editor?.view) {
|
|
354
|
+
const view = editor.view;
|
|
355
|
+
content = view.state.doc.toString();
|
|
356
|
+
if (getAiContext) {
|
|
357
|
+
const aiContext = getAiContext(view);
|
|
358
|
+
cursorPos = aiContext.cursorPos;
|
|
359
|
+
selection = { from: aiContext.selectionFrom, to: aiContext.selectionTo };
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} catch {
|
|
363
|
+
// Ignore and fall back to document-only resolve.
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return { doc, content, cursorPos, selection, ensureExists: true };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function render() {
|
|
370
|
+
const doc = state.doc;
|
|
371
|
+
const data = state.data;
|
|
372
|
+
const hiddenDoc = !doc || doc.startsWith('_');
|
|
373
|
+
body.innerHTML = '';
|
|
374
|
+
|
|
375
|
+
refreshBtn.disabled = !doc || state.loading || hiddenDoc;
|
|
376
|
+
rawBtn.disabled = !doc;
|
|
377
|
+
materializeBtn.disabled = !doc || state.loading || hiddenDoc;
|
|
378
|
+
|
|
379
|
+
if (!doc) {
|
|
380
|
+
const message = document.createElement('div');
|
|
381
|
+
message.className = 'mrmd-context-panel__message';
|
|
382
|
+
message.textContent = 'No document open.';
|
|
383
|
+
body.appendChild(message);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (hiddenDoc) {
|
|
388
|
+
const message = document.createElement('div');
|
|
389
|
+
message.className = 'mrmd-context-panel__message';
|
|
390
|
+
message.innerHTML = 'Context panel is disabled for infrastructure documents like <code>_assets/context/*.md</code>. Use the raw editor to edit this file directly.';
|
|
391
|
+
body.appendChild(message);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (state.loading && !data) {
|
|
396
|
+
const message = document.createElement('div');
|
|
397
|
+
message.className = 'mrmd-context-panel__message';
|
|
398
|
+
message.textContent = 'Resolving context…';
|
|
399
|
+
body.appendChild(message);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (!data) {
|
|
404
|
+
const message = document.createElement('div');
|
|
405
|
+
message.className = 'mrmd-context-panel__message';
|
|
406
|
+
message.textContent = 'No resolved context yet.';
|
|
407
|
+
body.appendChild(message);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const overview = document.createElement('section');
|
|
412
|
+
overview.className = 'mrmd-context-panel__section';
|
|
413
|
+
overview.innerHTML = `
|
|
414
|
+
<div class="mrmd-context-panel__section-title">
|
|
415
|
+
<span>Active Context</span>
|
|
416
|
+
<span class="mrmd-context-panel__badge">${data.contextFileSource || 'document'}</span>
|
|
417
|
+
</div>
|
|
418
|
+
<div class="mrmd-context-panel__meta">
|
|
419
|
+
<div class="mrmd-context-panel__path">${escapeHtml(data.contextFilePath || '')}</div>
|
|
420
|
+
<div class="mrmd-context-panel__small">${data.usingDefault ? 'Using project default context' : 'Using document-specific context'}</div>
|
|
421
|
+
</div>
|
|
422
|
+
`;
|
|
423
|
+
body.appendChild(overview);
|
|
424
|
+
|
|
425
|
+
const sourcesSection = document.createElement('section');
|
|
426
|
+
sourcesSection.className = 'mrmd-context-panel__section';
|
|
427
|
+
|
|
428
|
+
const titleRow = document.createElement('div');
|
|
429
|
+
titleRow.className = 'mrmd-context-panel__section-title';
|
|
430
|
+
titleRow.innerHTML = `<span>Resolved Sources</span><span>${(data.sources || []).length}</span>`;
|
|
431
|
+
sourcesSection.appendChild(titleRow);
|
|
432
|
+
|
|
433
|
+
const list = document.createElement('div');
|
|
434
|
+
list.className = 'mrmd-context-panel__source-list';
|
|
435
|
+
|
|
436
|
+
for (const source of data.sources || []) {
|
|
437
|
+
const item = document.createElement('div');
|
|
438
|
+
item.className = 'mrmd-context-panel__source';
|
|
439
|
+
const sub = describeSource(source);
|
|
440
|
+
item.innerHTML = `
|
|
441
|
+
<div class="mrmd-context-panel__source-main">
|
|
442
|
+
<div class="mrmd-context-panel__source-name">${escapeHtml(titleForSource(source))}</div>
|
|
443
|
+
<div class="mrmd-context-panel__badge">${formatTokens(source.tokens || 0)} tok</div>
|
|
444
|
+
</div>
|
|
445
|
+
${sub ? `<div class="mrmd-context-panel__source-sub">${escapeHtml(sub)}</div>` : ''}
|
|
446
|
+
`;
|
|
447
|
+
list.appendChild(item);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (!list.childElementCount) {
|
|
451
|
+
const empty = document.createElement('div');
|
|
452
|
+
empty.className = 'mrmd-context-panel__message';
|
|
453
|
+
empty.textContent = 'No context sources resolved. Edit the context markdown to add sources.';
|
|
454
|
+
sourcesSection.appendChild(empty);
|
|
455
|
+
} else {
|
|
456
|
+
sourcesSection.appendChild(list);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
body.appendChild(sourcesSection);
|
|
460
|
+
|
|
461
|
+
const footer = document.createElement('section');
|
|
462
|
+
footer.className = 'mrmd-context-panel__section mrmd-context-panel__footer';
|
|
463
|
+
const percent = Math.max(0, Math.min(100, ((data.tokenEstimate || 0) / 8000) * 100));
|
|
464
|
+
footer.innerHTML = `
|
|
465
|
+
<div class="mrmd-context-panel__row">
|
|
466
|
+
<span>Total</span>
|
|
467
|
+
<strong>${formatTokens(data.tokenEstimate || 0)} tokens</strong>
|
|
468
|
+
</div>
|
|
469
|
+
<div class="mrmd-context-panel__tokenbar">
|
|
470
|
+
<div class="mrmd-context-panel__tokenfill" style="width: ${percent}%;"></div>
|
|
471
|
+
</div>
|
|
472
|
+
<div class="mrmd-context-panel__row mrmd-context-panel__small">
|
|
473
|
+
<span>Budget reference</span>
|
|
474
|
+
<span>8k tokens</span>
|
|
475
|
+
</div>
|
|
476
|
+
${(data.images || []).length ? `<div class="mrmd-context-panel__small" style="margin-top:8px;">${data.images.length} image reference${data.images.length === 1 ? '' : 's'} resolved</div>` : ''}
|
|
477
|
+
`;
|
|
478
|
+
body.appendChild(footer);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function refresh() {
|
|
482
|
+
const request = currentContextRequest();
|
|
483
|
+
state.doc = request.doc;
|
|
484
|
+
render();
|
|
485
|
+
if (!request.doc || request.doc.startsWith('_')) return;
|
|
486
|
+
|
|
487
|
+
state.loading = true;
|
|
488
|
+
render();
|
|
489
|
+
try {
|
|
490
|
+
state.data = await orchestratorClient.resolveContext(request);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
state.data = {
|
|
493
|
+
contextFileSource: 'error',
|
|
494
|
+
contextFilePath: '_assets/context',
|
|
495
|
+
usingDefault: false,
|
|
496
|
+
tokenEstimate: 0,
|
|
497
|
+
sources: [],
|
|
498
|
+
images: [],
|
|
499
|
+
};
|
|
500
|
+
const message = document.createElement('div');
|
|
501
|
+
message.className = 'mrmd-context-panel__message';
|
|
502
|
+
message.textContent = `Failed to resolve context: ${error.message}`;
|
|
503
|
+
body.innerHTML = '';
|
|
504
|
+
body.appendChild(message);
|
|
505
|
+
state.loading = false;
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
state.loading = false;
|
|
509
|
+
render();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async function openRaw() {
|
|
513
|
+
const doc = getCurrentDocument?.();
|
|
514
|
+
if (!doc) return;
|
|
515
|
+
if (doc.startsWith('_')) return;
|
|
516
|
+
|
|
517
|
+
try {
|
|
518
|
+
const current = await orchestratorClient.getContext(doc);
|
|
519
|
+
if (current.source === 'default' || current.source === 'builtin') {
|
|
520
|
+
await orchestratorClient.saveContext(doc, current.content);
|
|
521
|
+
} else if (!current.exists) {
|
|
522
|
+
await orchestratorClient.initContext(doc);
|
|
523
|
+
}
|
|
524
|
+
const rawPath = `_assets/context/${doc.endsWith('.md') ? doc : `${doc}.md`}`;
|
|
525
|
+
await onOpenRaw?.(rawPath);
|
|
526
|
+
} catch (error) {
|
|
527
|
+
console.error('[ContextPanel] Failed to open raw context:', error);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function materialize() {
|
|
532
|
+
const doc = getCurrentDocument?.();
|
|
533
|
+
if (!doc) return;
|
|
534
|
+
try {
|
|
535
|
+
const current = await orchestratorClient.getContext(doc);
|
|
536
|
+
await orchestratorClient.saveContext(doc, current.content);
|
|
537
|
+
await refresh();
|
|
538
|
+
} catch (error) {
|
|
539
|
+
console.error('[ContextPanel] Failed to materialize context:', error);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
refreshBtn.addEventListener('click', () => refresh());
|
|
544
|
+
rawBtn.addEventListener('click', () => openRaw());
|
|
545
|
+
materializeBtn.addEventListener('click', () => materialize());
|
|
546
|
+
toggleBtn.addEventListener('click', () => setCollapsed(!state.collapsed));
|
|
547
|
+
|
|
548
|
+
const unsubFile = shellState.onPath('file.path', () => {
|
|
549
|
+
state.doc = getCurrentDocument?.();
|
|
550
|
+
refresh();
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
render();
|
|
554
|
+
|
|
555
|
+
return {
|
|
556
|
+
element: panel,
|
|
557
|
+
async refresh() {
|
|
558
|
+
await refresh();
|
|
559
|
+
},
|
|
560
|
+
setDocument(doc) {
|
|
561
|
+
state.doc = doc;
|
|
562
|
+
render();
|
|
563
|
+
},
|
|
564
|
+
setEditor() {
|
|
565
|
+
render();
|
|
566
|
+
},
|
|
567
|
+
destroy() {
|
|
568
|
+
unsubFile?.();
|
|
569
|
+
panel.remove();
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
}
|
|
@@ -272,6 +272,14 @@ function createFilesSegment({ shellState, orchestratorClient, handlers, onCleanu
|
|
|
272
272
|
onClick: () => handlers.onOpenFilePicker?.(),
|
|
273
273
|
});
|
|
274
274
|
|
|
275
|
+
if (handlers.onImportLinkedTable && handlers.supportsLinkedTableImport?.() !== false) {
|
|
276
|
+
items.push({
|
|
277
|
+
icon: '▦',
|
|
278
|
+
label: 'Import Linked Table...',
|
|
279
|
+
onClick: () => handlers.onImportLinkedTable?.(),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
275
283
|
items.push({
|
|
276
284
|
icon: '➕',
|
|
277
285
|
label: 'New File...',
|
|
@@ -1124,7 +1132,7 @@ function createAiSegment({ shellState, handlers, onCleanup }) {
|
|
|
1124
1132
|
// Known dark themes for proper icon display
|
|
1125
1133
|
const DARK_THEMES = new Set([
|
|
1126
1134
|
'midnight', 'moonlight', 'github', 'nord', 'nord-outputs',
|
|
1127
|
-
'grayscale-dark',
|
|
1135
|
+
'grayscale-dark', 'newsprint-dark', 'plain-dark',
|
|
1128
1136
|
]);
|
|
1129
1137
|
|
|
1130
1138
|
// Custom themes storage key
|
|
@@ -1277,7 +1285,7 @@ function createThemeSegment({ editorRef, shellState, handlers, onCleanup }) {
|
|
|
1277
1285
|
}
|
|
1278
1286
|
|
|
1279
1287
|
// Check for name conflicts with built-in themes
|
|
1280
|
-
const builtInThemes = ['midnight', 'daylight', 'moonlight', 'github', 'nord', 'nord-outputs', 'grayscale-dark', 'grayscale-light', 'openresponses'];
|
|
1288
|
+
const builtInThemes = ['midnight', 'daylight', 'moonlight', 'github', 'nord', 'nord-outputs', 'grayscale-dark', 'grayscale-light', 'openresponses', 'newsprint-dark', 'newsprint-light', 'plain-dark', 'plain-light'];
|
|
1281
1289
|
if (builtInThemes.includes(theme.name)) {
|
|
1282
1290
|
alert(`Cannot use reserved theme name "${theme.name}". Please rename your theme.`);
|
|
1283
1291
|
return;
|