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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Banner shown when one linked table is in local markdown/source view.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { WidgetType } from '@codemirror/view';
|
|
6
|
+
import { dispatchLinkedTableAction } from '../commands/open-table-workspace.js';
|
|
7
|
+
|
|
8
|
+
function cloneValue(value) {
|
|
9
|
+
if (Array.isArray(value)) return value.map(cloneValue);
|
|
10
|
+
if (value && typeof value === 'object') {
|
|
11
|
+
const out = {};
|
|
12
|
+
for (const key of Object.keys(value)) out[key] = cloneValue(value[key]);
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function buildActionDetail(block, action, extra = {}) {
|
|
19
|
+
return {
|
|
20
|
+
action,
|
|
21
|
+
tableId: block.spec.id,
|
|
22
|
+
label: block.spec.label || block.spec.id,
|
|
23
|
+
spec: cloneValue(block.spec),
|
|
24
|
+
headerFrom: block.headerFrom,
|
|
25
|
+
headerTo: block.headerTo,
|
|
26
|
+
snapshotFrom: block.snapshotFrom,
|
|
27
|
+
snapshotTo: block.snapshotTo,
|
|
28
|
+
tableFrom: block.tableFrom,
|
|
29
|
+
tableTo: block.tableTo,
|
|
30
|
+
startLine: block.startLine,
|
|
31
|
+
endLine: block.endLine,
|
|
32
|
+
...extra,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class LinkedTableSourceBannerWidget extends WidgetType {
|
|
37
|
+
constructor(block) {
|
|
38
|
+
super();
|
|
39
|
+
this.block = block;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
eq(other) {
|
|
43
|
+
return other?.block?.spec?.id === this.block.spec.id;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
toDOM(view) {
|
|
47
|
+
const container = document.createElement('div');
|
|
48
|
+
container.className = 'cm-linked-table-source-banner';
|
|
49
|
+
container.dataset.tableId = this.block.spec.id;
|
|
50
|
+
|
|
51
|
+
const text = document.createElement('div');
|
|
52
|
+
text.className = 'cm-linked-table-source-banner-text';
|
|
53
|
+
text.textContent = `${this.block.spec.label || this.block.spec.id}: markdown source view`;
|
|
54
|
+
container.appendChild(text);
|
|
55
|
+
|
|
56
|
+
const button = document.createElement('button');
|
|
57
|
+
button.className = 'cm-linked-table-source-banner-action';
|
|
58
|
+
button.type = 'button';
|
|
59
|
+
button.textContent = 'Return to linked view';
|
|
60
|
+
button.addEventListener('click', (event) => {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
event.stopPropagation();
|
|
63
|
+
dispatchLinkedTableAction(view, buildActionDetail(this.block, 'close-markdown'));
|
|
64
|
+
});
|
|
65
|
+
container.appendChild(button);
|
|
66
|
+
|
|
67
|
+
return container;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ignoreEvent() {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default {
|
|
76
|
+
LinkedTableSourceBannerWidget,
|
|
77
|
+
};
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linked-table widget for the embedded document view.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { WidgetType } from '@codemirror/view';
|
|
6
|
+
import { TableWidget, parseTable } from '../../markdown/widgets/table.js';
|
|
7
|
+
import { dispatchLinkedTableAction } from '../commands/open-table-workspace.js';
|
|
8
|
+
|
|
9
|
+
function stripCaptionMarkers(text) {
|
|
10
|
+
const trimmed = String(text || '').trim();
|
|
11
|
+
if (!trimmed) return '';
|
|
12
|
+
if ((trimmed.startsWith('_') && trimmed.endsWith('_')) || (trimmed.startsWith('*') && trimmed.endsWith('*'))) {
|
|
13
|
+
return trimmed.slice(1, -1).trim();
|
|
14
|
+
}
|
|
15
|
+
return trimmed;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function cloneValue(value) {
|
|
19
|
+
if (Array.isArray(value)) return value.map(cloneValue);
|
|
20
|
+
if (value && typeof value === 'object') {
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const key of Object.keys(value)) out[key] = cloneValue(value[key]);
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getSortableHeaderCells(table) {
|
|
29
|
+
const headerRow = table?.rows?.find((row) => row.isHeader && !row.isDelimiter);
|
|
30
|
+
if (!headerRow) return [];
|
|
31
|
+
return headerRow.cells.filter((cell) => !cell.hidden && cell.content.trim() !== '' && cell.content.trim() !== '>' && cell.content.trim() !== '^');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function inferFormats(spec) {
|
|
35
|
+
return Array.from(new Set((spec?.sources || []).map((source) => String(source.format || source.kind || '').trim()).filter(Boolean)));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildActionDetail(baseDetail, action, extra = {}) {
|
|
39
|
+
return {
|
|
40
|
+
...baseDetail,
|
|
41
|
+
action,
|
|
42
|
+
...extra,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatMaterializedAt(value) {
|
|
47
|
+
if (!value) return '';
|
|
48
|
+
const date = new Date(value);
|
|
49
|
+
if (Number.isNaN(date.getTime())) return String(value);
|
|
50
|
+
return date.toLocaleString();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class LinkedTableWidget extends WidgetType {
|
|
54
|
+
constructor(block, parsedTable, contentHash, options = {}) {
|
|
55
|
+
super();
|
|
56
|
+
this.block = block;
|
|
57
|
+
this.parsedTable = parsedTable;
|
|
58
|
+
this.contentHash = contentHash;
|
|
59
|
+
this.options = options;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
eq(other) {
|
|
63
|
+
return other?.contentHash === this.contentHash;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_baseDetail() {
|
|
67
|
+
return {
|
|
68
|
+
tableId: this.block.spec.id,
|
|
69
|
+
label: this.block.spec.label || this.block.spec.id,
|
|
70
|
+
spec: cloneValue(this.block.spec),
|
|
71
|
+
headerFrom: this.block.headerFrom,
|
|
72
|
+
headerTo: this.block.headerTo,
|
|
73
|
+
snapshotFrom: this.block.snapshotFrom,
|
|
74
|
+
snapshotTo: this.block.snapshotTo,
|
|
75
|
+
tableFrom: this.block.tableFrom,
|
|
76
|
+
tableTo: this.block.tableTo,
|
|
77
|
+
startLine: this.block.startLine,
|
|
78
|
+
endLine: this.block.endLine,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_dispatch(view, action, extra = {}) {
|
|
83
|
+
return dispatchLinkedTableAction(view, buildActionDetail(this._baseDetail(), action, extra));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_buildChrome(view) {
|
|
87
|
+
const chrome = document.createElement('div');
|
|
88
|
+
chrome.className = 'cm-linked-table-chrome';
|
|
89
|
+
|
|
90
|
+
const left = document.createElement('div');
|
|
91
|
+
left.className = 'cm-linked-table-chrome-left';
|
|
92
|
+
|
|
93
|
+
const title = document.createElement('div');
|
|
94
|
+
title.className = 'cm-linked-table-title';
|
|
95
|
+
title.textContent = this.block.spec.label || this.block.spec.id || 'Linked table';
|
|
96
|
+
left.appendChild(title);
|
|
97
|
+
|
|
98
|
+
const badges = document.createElement('div');
|
|
99
|
+
badges.className = 'cm-linked-table-badges';
|
|
100
|
+
|
|
101
|
+
const linkedBadge = document.createElement('span');
|
|
102
|
+
linkedBadge.className = 'cm-linked-table-badge cm-linked-table-badge-linked';
|
|
103
|
+
linkedBadge.textContent = 'Linked';
|
|
104
|
+
badges.appendChild(linkedBadge);
|
|
105
|
+
|
|
106
|
+
const engineBadge = document.createElement('span');
|
|
107
|
+
engineBadge.className = 'cm-linked-table-badge';
|
|
108
|
+
engineBadge.textContent = this.block.spec.engine || 'engine';
|
|
109
|
+
badges.appendChild(engineBadge);
|
|
110
|
+
|
|
111
|
+
const sourceCountBadge = document.createElement('span');
|
|
112
|
+
sourceCountBadge.className = 'cm-linked-table-badge';
|
|
113
|
+
sourceCountBadge.textContent = `${(this.block.spec.sources || []).length} source${(this.block.spec.sources || []).length === 1 ? '' : 's'}`;
|
|
114
|
+
badges.appendChild(sourceCountBadge);
|
|
115
|
+
|
|
116
|
+
for (const format of inferFormats(this.block.spec)) {
|
|
117
|
+
const badge = document.createElement('span');
|
|
118
|
+
badge.className = 'cm-linked-table-badge';
|
|
119
|
+
badge.textContent = format;
|
|
120
|
+
badges.appendChild(badge);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const statusBadge = document.createElement('span');
|
|
124
|
+
statusBadge.className = 'cm-linked-table-badge cm-linked-table-status-badge cm-linked-table-status-fresh';
|
|
125
|
+
statusBadge.textContent = 'Fresh';
|
|
126
|
+
const materializedAt = this.block.spec?.snapshot?.materializedAt;
|
|
127
|
+
if (materializedAt) {
|
|
128
|
+
statusBadge.title = `Last materialized ${formatMaterializedAt(materializedAt)}`;
|
|
129
|
+
}
|
|
130
|
+
badges.appendChild(statusBadge);
|
|
131
|
+
|
|
132
|
+
left.appendChild(badges);
|
|
133
|
+
chrome.appendChild(left);
|
|
134
|
+
|
|
135
|
+
const right = document.createElement('div');
|
|
136
|
+
right.className = 'cm-linked-table-actions';
|
|
137
|
+
|
|
138
|
+
const makeButton = (label, action, title, extra = {}) => {
|
|
139
|
+
const button = document.createElement('button');
|
|
140
|
+
button.className = 'cm-linked-table-action';
|
|
141
|
+
button.type = 'button';
|
|
142
|
+
button.textContent = label;
|
|
143
|
+
button.dataset.linkedTableAction = action;
|
|
144
|
+
if (title) button.title = title;
|
|
145
|
+
button.addEventListener('click', (event) => {
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
event.stopPropagation();
|
|
148
|
+
this._dispatch(view, action, extra);
|
|
149
|
+
});
|
|
150
|
+
return button;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
right.appendChild(makeButton('Open grid', 'open-grid', 'Open full linked-table workspace'));
|
|
154
|
+
right.appendChild(makeButton('Open source', 'open-source', 'Open the primary linked-table source file'));
|
|
155
|
+
right.appendChild(makeButton('Reveal source', 'reveal-source', 'Reveal the primary linked-table source in the host file manager'));
|
|
156
|
+
right.appendChild(makeButton('Open markdown', 'open-markdown', 'Open raw markdown for this linked table'));
|
|
157
|
+
right.appendChild(makeButton('Refresh', 'refresh', 'Refresh linked table materialization'));
|
|
158
|
+
|
|
159
|
+
chrome.appendChild(right);
|
|
160
|
+
return chrome;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_buildCaption(text, position) {
|
|
164
|
+
const captionText = stripCaptionMarkers(text);
|
|
165
|
+
if (!captionText) return null;
|
|
166
|
+
const el = document.createElement('div');
|
|
167
|
+
el.className = `cm-linked-table-caption cm-linked-table-caption-${position}`;
|
|
168
|
+
el.textContent = captionText;
|
|
169
|
+
return el;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_decorateSortableHeaders(view, tableContainer) {
|
|
173
|
+
const sortableHeaders = getSortableHeaderCells(this.parsedTable);
|
|
174
|
+
if (sortableHeaders.length === 0) return;
|
|
175
|
+
|
|
176
|
+
const headerRow = tableContainer.querySelector('thead tr');
|
|
177
|
+
if (!headerRow) return;
|
|
178
|
+
|
|
179
|
+
const domHeaders = Array.from(headerRow.querySelectorAll('th'));
|
|
180
|
+
const count = Math.min(domHeaders.length, sortableHeaders.length);
|
|
181
|
+
|
|
182
|
+
for (let index = 0; index < count; index++) {
|
|
183
|
+
const th = domHeaders[index];
|
|
184
|
+
const headerCell = sortableHeaders[index];
|
|
185
|
+
const column = headerCell.content.trim();
|
|
186
|
+
if (!column) continue;
|
|
187
|
+
|
|
188
|
+
th.classList.add('cm-linked-table-sortable');
|
|
189
|
+
th.title = `Sort by ${column}`;
|
|
190
|
+
th.dataset.linkedTableColumn = column;
|
|
191
|
+
th.dataset.linkedTableSortDirection = th.dataset.linkedTableSortDirection || 'none';
|
|
192
|
+
|
|
193
|
+
th.addEventListener('click', (event) => {
|
|
194
|
+
if (th.getAttribute('aria-disabled') === 'true') {
|
|
195
|
+
event.preventDefault();
|
|
196
|
+
event.stopPropagation();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
event.preventDefault();
|
|
201
|
+
event.stopPropagation();
|
|
202
|
+
|
|
203
|
+
const current = th.dataset.linkedTableSortDirection || 'none';
|
|
204
|
+
const next = current === 'asc' ? 'desc' : 'asc';
|
|
205
|
+
th.dataset.linkedTableSortDirection = next;
|
|
206
|
+
this._dispatch(view, 'sort', { column, direction: next });
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
toDOM(view) {
|
|
212
|
+
const container = document.createElement('div');
|
|
213
|
+
container.className = 'cm-linked-table-widget';
|
|
214
|
+
container.dataset.tableId = this.block.spec.id;
|
|
215
|
+
container.dataset.engine = this.block.spec.engine || '';
|
|
216
|
+
container.dataset.materializedAt = this.block.spec?.snapshot?.materializedAt || '';
|
|
217
|
+
|
|
218
|
+
container.appendChild(this._buildChrome(view));
|
|
219
|
+
|
|
220
|
+
const aboveCaption = this._buildCaption(this.block.captionAboveText, 'above');
|
|
221
|
+
if (aboveCaption) container.appendChild(aboveCaption);
|
|
222
|
+
|
|
223
|
+
const body = document.createElement('div');
|
|
224
|
+
body.className = 'cm-linked-table-body';
|
|
225
|
+
|
|
226
|
+
const renderedTable = new TableWidget(this.parsedTable, `linked-${this.block.spec.id}`).toDOM(view);
|
|
227
|
+
body.appendChild(renderedTable);
|
|
228
|
+
this._decorateSortableHeaders(view, renderedTable);
|
|
229
|
+
|
|
230
|
+
container.appendChild(body);
|
|
231
|
+
|
|
232
|
+
const belowCaption = this._buildCaption(this.block.captionBelowText, 'below');
|
|
233
|
+
if (belowCaption) container.appendChild(belowCaption);
|
|
234
|
+
|
|
235
|
+
return container;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
ignoreEvent() {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
get estimatedHeight() {
|
|
243
|
+
return this.options.estimatedHeight || -1;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function createLinkedTableWidgetFromBlock(block, contentHash, options = {}) {
|
|
248
|
+
const parsed = parseTable(block.tableLines || []);
|
|
249
|
+
if (!parsed || !parsed.rows || parsed.rows.length === 0) return null;
|
|
250
|
+
return new LinkedTableWidget(block, parsed, contentHash, options);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export default {
|
|
254
|
+
LinkedTableWidget,
|
|
255
|
+
createLinkedTableWidgetFromBlock,
|
|
256
|
+
};
|