@oml/markdown 0.10.0 → 0.12.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/out/md/md-execution.d.ts +16 -0
- package/out/md/md-executor.d.ts +1 -0
- package/out/md/md-executor.js +219 -35
- package/out/md/md-executor.js.map +1 -1
- package/out/renderers/chart-renderer.js +72 -4
- package/out/renderers/chart-renderer.js.map +1 -1
- package/out/renderers/diagram-renderer.js +896 -245
- package/out/renderers/diagram-renderer.js.map +1 -1
- package/out/renderers/graph-renderer.js +452 -18
- package/out/renderers/graph-renderer.js.map +1 -1
- package/out/renderers/matrix-renderer.d.ts +0 -2
- package/out/renderers/matrix-renderer.js +45 -40
- package/out/renderers/matrix-renderer.js.map +1 -1
- package/out/renderers/renderer.d.ts +4 -1
- package/out/renderers/renderer.js +98 -0
- package/out/renderers/renderer.js.map +1 -1
- package/out/renderers/table-renderer.d.ts +12 -2
- package/out/renderers/table-renderer.js +126 -39
- package/out/renderers/table-renderer.js.map +1 -1
- package/out/renderers/types.d.ts +16 -0
- package/out/renderers/wikilink-utils.d.ts +1 -0
- package/out/renderers/wikilink-utils.js +60 -32
- package/out/renderers/wikilink-utils.js.map +1 -1
- package/out/static/browser-runtime.bundle.js +8011 -1292
- package/out/static/browser-runtime.bundle.js.map +4 -4
- package/out/static/browser-runtime.js +15 -2
- package/out/static/browser-runtime.js.map +1 -1
- package/package.json +2 -2
- package/src/md/md-execution.ts +20 -0
- package/src/md/md-executor.ts +268 -40
- package/src/renderers/chart-renderer.ts +93 -2
- package/src/renderers/diagram-renderer.ts +964 -253
- package/src/renderers/graph-renderer.ts +512 -12
- package/src/renderers/matrix-renderer.ts +57 -44
- package/src/renderers/renderer.ts +105 -1
- package/src/renderers/table-renderer.ts +190 -41
- package/src/renderers/types.ts +20 -0
- package/src/renderers/wikilink-utils.ts +66 -31
- package/src/static/browser-runtime.ts +20 -2
- package/src/static/markdown-webview.css +44 -15
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
2
|
|
|
3
3
|
import { QueryMarkdownBlockRenderer, type TableBlockKind } from './renderer.js';
|
|
4
|
-
import type { MdBlockExecutionResult } from './types.js';
|
|
5
|
-
import {
|
|
4
|
+
import type { MdBlockExecutionResult, MdTableCell } from './types.js';
|
|
5
|
+
import { shortLabelFromIri } from './wikilink-utils.js';
|
|
6
6
|
|
|
7
7
|
type TableEditorActionKind = 'add' | 'remove' | 'validate' | 'link' | 'undo' | 'redo' | 'ai' | 'properties';
|
|
8
8
|
type TableEditorActionContext = {
|
|
@@ -27,6 +27,28 @@ type TableEditorActionContext = {
|
|
|
27
27
|
pointerY?: number;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
type TableRootWithAiContext = HTMLDivElement & {
|
|
31
|
+
__omlAiContext?: {
|
|
32
|
+
columns: string[];
|
|
33
|
+
rows: Array<{ iri: string; cells: string[] }>;
|
|
34
|
+
blockSource?: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type PersistedTableUiState = {
|
|
39
|
+
search: string;
|
|
40
|
+
pageSize: number;
|
|
41
|
+
page: number;
|
|
42
|
+
sortKey: string;
|
|
43
|
+
sortDir: 'asc' | 'desc';
|
|
44
|
+
hasUserSort: boolean;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type TableRendererWindow = Window & typeof globalThis & {
|
|
48
|
+
__omlGetPersistedTableUiState?: (blockId: string) => Partial<PersistedTableUiState> | undefined;
|
|
49
|
+
__omlSetPersistedTableUiState?: (blockId: string, state: PersistedTableUiState) => void;
|
|
50
|
+
};
|
|
51
|
+
|
|
30
52
|
export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
31
53
|
protected readonly tableKinds: ReadonlyArray<TableBlockKind> = ['table'];
|
|
32
54
|
|
|
@@ -44,9 +66,11 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
44
66
|
const tableRoot = this.renderInteractiveTable(
|
|
45
67
|
result.payload?.columns ?? [],
|
|
46
68
|
result.payload?.rows ?? [],
|
|
69
|
+
result.payload?.rowsTyped,
|
|
47
70
|
stylesheet,
|
|
48
71
|
result.options,
|
|
49
|
-
result.blockSource
|
|
72
|
+
result.blockSource,
|
|
73
|
+
result.blockId
|
|
50
74
|
);
|
|
51
75
|
if (typeof result.blockId === 'string' && result.blockId.length > 0) {
|
|
52
76
|
tableRoot.dataset.tableBlockId = result.blockId;
|
|
@@ -88,14 +112,20 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
88
112
|
protected renderInteractiveTable(
|
|
89
113
|
columns: string[],
|
|
90
114
|
rows: string[][],
|
|
115
|
+
rowsTyped: MdTableCell[][] | undefined,
|
|
91
116
|
stylesheet: CompiledTableStyleRule[],
|
|
92
117
|
options: Record<string, unknown> | undefined,
|
|
93
|
-
blockSource?: string
|
|
118
|
+
blockSource?: string,
|
|
119
|
+
blockId?: string
|
|
94
120
|
): HTMLElement {
|
|
95
|
-
const root = document.createElement('div');
|
|
121
|
+
const root = document.createElement('div') as TableRootWithAiContext;
|
|
96
122
|
root.className = 'table-root graph-root';
|
|
97
123
|
const isTree = this.tableKinds.includes('tree');
|
|
98
|
-
const allRowsWithIndex = rows.map((cells, index) => ({
|
|
124
|
+
const allRowsWithIndex = rows.map((cells, index) => ({
|
|
125
|
+
index,
|
|
126
|
+
cells,
|
|
127
|
+
typedCells: rowsTyped?.[index],
|
|
128
|
+
}));
|
|
99
129
|
const containmentColumns = isTree ? resolveContainmentColumns(columns, options) : [];
|
|
100
130
|
const baseVisibleColumnIndexes = isTree
|
|
101
131
|
? columns.map((_, index) => index).filter((index) => !containmentColumns.includes(index))
|
|
@@ -104,6 +134,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
104
134
|
const baseRowsWithIndex = allRowsWithIndex.map((row) => ({
|
|
105
135
|
...row,
|
|
106
136
|
cells: baseVisibleColumnIndexes.map((columnIndex) => row.cells[columnIndex] ?? ''),
|
|
137
|
+
typedCells: baseVisibleColumnIndexes.map((columnIndex) => row.typedCells?.[columnIndex]),
|
|
107
138
|
}));
|
|
108
139
|
const selectorRowsByIndex = new Map(baseRowsWithIndex.map((row) => [row.index, row] as const));
|
|
109
140
|
const baseColumnContexts = createColumnContexts(baseVisibleColumns, baseRowsWithIndex);
|
|
@@ -113,7 +144,18 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
113
144
|
const rowsWithIndex = allRowsWithIndex.map((row) => ({
|
|
114
145
|
...row,
|
|
115
146
|
cells: visibleColumnIndexes.map((columnIndex) => row.cells[columnIndex] ?? ''),
|
|
147
|
+
typedCells: visibleColumnIndexes.map((columnIndex) => row.typedCells?.[columnIndex]),
|
|
116
148
|
}));
|
|
149
|
+
root.__omlAiContext = {
|
|
150
|
+
columns: visibleColumns.slice(),
|
|
151
|
+
rows: rowsWithIndex
|
|
152
|
+
.map((row) => ({
|
|
153
|
+
iri: (row.cells[0] ?? '').trim(),
|
|
154
|
+
cells: row.cells.slice(),
|
|
155
|
+
}))
|
|
156
|
+
.filter((row) => row.iri.length > 0),
|
|
157
|
+
blockSource,
|
|
158
|
+
};
|
|
117
159
|
const columnContexts = createColumnContexts(visibleColumns, rowsWithIndex);
|
|
118
160
|
const treeModel = isTree
|
|
119
161
|
? this.createTreeModel(columns, allRowsWithIndex, visibleColumnIndexes, options)
|
|
@@ -121,19 +163,36 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
121
163
|
const fullyExpandedTreeRows = isTree && treeModel
|
|
122
164
|
? this.flattenAllTreeRows(treeModel)
|
|
123
165
|
: undefined;
|
|
166
|
+
const persistedState = blockId
|
|
167
|
+
? (window as TableRendererWindow).__omlGetPersistedTableUiState?.(blockId)
|
|
168
|
+
: undefined;
|
|
124
169
|
|
|
125
170
|
const state = {
|
|
126
|
-
search: '',
|
|
127
|
-
pageSize: 50,
|
|
128
|
-
page: 0,
|
|
129
|
-
sortKey: '' as string,
|
|
130
|
-
sortDir: 'asc' as 'asc' | 'desc',
|
|
131
|
-
hasUserSort: false,
|
|
171
|
+
search: persistedState?.search ?? '',
|
|
172
|
+
pageSize: persistedState?.pageSize ?? 50,
|
|
173
|
+
page: persistedState?.page ?? 0,
|
|
174
|
+
sortKey: persistedState?.sortKey ?? '' as string,
|
|
175
|
+
sortDir: persistedState?.sortDir ?? 'asc' as 'asc' | 'desc',
|
|
176
|
+
hasUserSort: persistedState?.hasUserSort ?? false,
|
|
132
177
|
columnWidths: new Array<number | undefined>(columns.length).fill(undefined),
|
|
133
178
|
initialAutosizeApplied: false,
|
|
134
179
|
expanded: new Set<string>(treeModel?.expandableNodes ?? []),
|
|
135
180
|
};
|
|
136
181
|
|
|
182
|
+
const persistUiState = () => {
|
|
183
|
+
if (!blockId) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
(window as TableRendererWindow).__omlSetPersistedTableUiState?.(blockId, {
|
|
187
|
+
search: state.search,
|
|
188
|
+
pageSize: state.pageSize,
|
|
189
|
+
page: state.page,
|
|
190
|
+
sortKey: state.sortKey,
|
|
191
|
+
sortDir: state.sortDir,
|
|
192
|
+
hasUserSort: state.hasUserSort,
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
|
|
137
196
|
const controls = document.createElement('div');
|
|
138
197
|
controls.className = 'table-controls';
|
|
139
198
|
|
|
@@ -150,9 +209,11 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
150
209
|
}
|
|
151
210
|
pageSize.appendChild(option);
|
|
152
211
|
}
|
|
212
|
+
pageSize.value = String(state.pageSize);
|
|
153
213
|
pageSize.addEventListener('change', () => {
|
|
154
214
|
state.pageSize = Number.parseInt(pageSize.value, 10);
|
|
155
215
|
state.page = 0;
|
|
216
|
+
persistUiState();
|
|
156
217
|
renderPage();
|
|
157
218
|
});
|
|
158
219
|
if (!isTree) {
|
|
@@ -187,9 +248,11 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
187
248
|
searchInput.type = 'search';
|
|
188
249
|
searchInput.className = 'tree-filter';
|
|
189
250
|
searchInput.placeholder = 'Filter...';
|
|
251
|
+
searchInput.value = state.search;
|
|
190
252
|
searchInput.addEventListener('input', () => {
|
|
191
253
|
state.search = searchInput.value.trim().toLowerCase();
|
|
192
254
|
state.page = 0;
|
|
255
|
+
persistUiState();
|
|
193
256
|
renderPage();
|
|
194
257
|
});
|
|
195
258
|
controlsRight.appendChild(searchInput);
|
|
@@ -211,17 +274,23 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
211
274
|
downloadButton.appendChild(iconSvg);
|
|
212
275
|
downloadButton.addEventListener('click', () => {
|
|
213
276
|
const sourceRows = isTree && treeModel
|
|
214
|
-
? this.resolveTreeSourceRows(treeModel, visibleColumns, state.expanded, state.search)
|
|
277
|
+
? this.resolveTreeSourceRows(treeModel, visibleColumns, state.expanded, state.search, blockSource, options)
|
|
215
278
|
: rowsWithIndex;
|
|
216
279
|
if (isTree && treeModel) {
|
|
217
280
|
this.requestTreeJsonDownload(visibleColumns, treeModel, sourceRows);
|
|
218
281
|
return;
|
|
219
282
|
}
|
|
220
|
-
const filtered = this.applyFiltersAndSorting(
|
|
221
|
-
this.
|
|
283
|
+
const filtered = this.applyFiltersAndSorting(visibleColumns, sourceRows, state, blockSource, options);
|
|
284
|
+
const csvExport = this.transformCsvDownloadData(visibleColumns, filtered.map((entry) => entry.cells));
|
|
285
|
+
this.requestCsvDownload(csvExport.columns, csvExport.rows);
|
|
222
286
|
});
|
|
223
287
|
controlsRight.appendChild(downloadButton);
|
|
224
288
|
|
|
289
|
+
const uploadButton = this.createUploadCsvButton(visibleColumns, rowsWithIndex, isTree, blockSource);
|
|
290
|
+
if (uploadButton) {
|
|
291
|
+
controlsRight.appendChild(uploadButton);
|
|
292
|
+
}
|
|
293
|
+
|
|
225
294
|
controls.appendChild(controlsLeft);
|
|
226
295
|
controls.appendChild(controlsRight);
|
|
227
296
|
root.appendChild(controls);
|
|
@@ -259,6 +328,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
259
328
|
}
|
|
260
329
|
if (state.page > 0) {
|
|
261
330
|
state.page -= 1;
|
|
331
|
+
persistUiState();
|
|
262
332
|
renderPage();
|
|
263
333
|
}
|
|
264
334
|
});
|
|
@@ -266,10 +336,11 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
266
336
|
if (isTree) {
|
|
267
337
|
return;
|
|
268
338
|
}
|
|
269
|
-
const filtered = this.applyFiltersAndSorting(
|
|
339
|
+
const filtered = this.applyFiltersAndSorting(visibleColumns, rowsWithIndex, state, blockSource, options);
|
|
270
340
|
const totalPages = Math.max(1, Math.ceil(filtered.length / state.pageSize));
|
|
271
341
|
if (state.page < totalPages - 1) {
|
|
272
342
|
state.page += 1;
|
|
343
|
+
persistUiState();
|
|
273
344
|
renderPage();
|
|
274
345
|
}
|
|
275
346
|
});
|
|
@@ -287,9 +358,11 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
287
358
|
|
|
288
359
|
const renderPage = () => {
|
|
289
360
|
const sourceRows = isTree && treeModel
|
|
290
|
-
? this.resolveTreeSourceRows(treeModel, visibleColumns, state.expanded, state.search)
|
|
361
|
+
? this.resolveTreeSourceRows(treeModel, visibleColumns, state.expanded, state.search, blockSource, options)
|
|
291
362
|
: rowsWithIndex;
|
|
292
|
-
const filtered = isTree
|
|
363
|
+
const filtered = isTree
|
|
364
|
+
? sourceRows.slice()
|
|
365
|
+
: this.applyFiltersAndSorting(visibleColumns, sourceRows, state, blockSource, options);
|
|
293
366
|
const total = filtered.length;
|
|
294
367
|
const totalTreeRows = isTree
|
|
295
368
|
? (fullyExpandedTreeRows?.length ?? total)
|
|
@@ -297,6 +370,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
297
370
|
const totalPages = isTree ? 1 : Math.max(1, Math.ceil(total / state.pageSize));
|
|
298
371
|
if (!isTree && state.page >= totalPages) {
|
|
299
372
|
state.page = totalPages - 1;
|
|
373
|
+
persistUiState();
|
|
300
374
|
}
|
|
301
375
|
const gridTemplateColumns = this.buildGridTemplate(visibleColumns.length, state.columnWidths);
|
|
302
376
|
|
|
@@ -324,6 +398,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
324
398
|
}
|
|
325
399
|
state.hasUserSort = true;
|
|
326
400
|
state.page = 0;
|
|
401
|
+
persistUiState();
|
|
327
402
|
renderPage();
|
|
328
403
|
});
|
|
329
404
|
}
|
|
@@ -351,6 +426,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
351
426
|
const rowContext = createRowContext(baseVisibleColumns, selectorRow);
|
|
352
427
|
for (let columnIndex = 0; columnIndex < visibleColumns.length; columnIndex += 1) {
|
|
353
428
|
const value = rowEntry.cells[columnIndex] ?? '';
|
|
429
|
+
const typedValue = rowEntry.typedCells?.[columnIndex];
|
|
354
430
|
const td = document.createElement('div');
|
|
355
431
|
td.className = 'table-cell';
|
|
356
432
|
const valueElement = this.renderValue(this.formatCellDisplayValue(value, {
|
|
@@ -358,7 +434,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
358
434
|
columns: visibleColumns,
|
|
359
435
|
blockSource,
|
|
360
436
|
options,
|
|
361
|
-
}));
|
|
437
|
+
}), typedValue);
|
|
362
438
|
if (isTree && columnIndex === 0) {
|
|
363
439
|
const treeCell = this.renderTreeCell(valueElement, rowEntry, treeModel, state.expanded, () => renderPage());
|
|
364
440
|
td.appendChild(treeCell);
|
|
@@ -433,15 +509,23 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
433
509
|
model: TreeModel,
|
|
434
510
|
columns: string[],
|
|
435
511
|
expanded: ReadonlySet<string>,
|
|
436
|
-
search: string
|
|
512
|
+
search: string,
|
|
513
|
+
blockSource?: string,
|
|
514
|
+
options?: Record<string, unknown>
|
|
437
515
|
): TableRowData[] {
|
|
438
516
|
if (search) {
|
|
439
|
-
return this.filterTreeRowsBySearch(model, columns, search);
|
|
517
|
+
return this.filterTreeRowsBySearch(model, columns, search, blockSource, options);
|
|
440
518
|
}
|
|
441
519
|
return this.flattenTreeRows(model, expanded);
|
|
442
520
|
}
|
|
443
521
|
|
|
444
|
-
private filterTreeRowsBySearch(
|
|
522
|
+
private filterTreeRowsBySearch(
|
|
523
|
+
model: TreeModel,
|
|
524
|
+
columns: string[],
|
|
525
|
+
search: string,
|
|
526
|
+
blockSource?: string,
|
|
527
|
+
options?: Record<string, unknown>
|
|
528
|
+
): TableRowData[] {
|
|
445
529
|
const fullyExpanded = this.flattenTreeRows(model, new Set<string>(model.expandableNodes));
|
|
446
530
|
const matchedIds = new Set<string>();
|
|
447
531
|
for (const row of fullyExpanded) {
|
|
@@ -449,7 +533,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
449
533
|
if (!nodeId) {
|
|
450
534
|
continue;
|
|
451
535
|
}
|
|
452
|
-
if (matchesFilterQuery(columns, row
|
|
536
|
+
if (matchesFilterQuery(columns, this.getDisplayFilterCells(row, columns, blockSource, options), search)) {
|
|
453
537
|
matchedIds.add(nodeId);
|
|
454
538
|
}
|
|
455
539
|
}
|
|
@@ -493,6 +577,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
493
577
|
const projectRow = (row: TableRowData): TableRowData => ({
|
|
494
578
|
...row,
|
|
495
579
|
cells: visibleColumnIndexes.map((columnIndex) => row.cells[columnIndex] ?? ''),
|
|
580
|
+
typedCells: visibleColumnIndexes.map((columnIndex) => row.typedCells?.[columnIndex]),
|
|
496
581
|
});
|
|
497
582
|
for (const row of allRows) {
|
|
498
583
|
const id = row.cells[idColumnIndex] ?? '';
|
|
@@ -539,7 +624,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
539
624
|
const syntheticAll = new Array<string>(allColumns.length).fill('');
|
|
540
625
|
syntheticAll[idColumnIndex] = child;
|
|
541
626
|
const synthetic = visibleColumnIndexes.map((columnIndex) => syntheticAll[columnIndex] ?? '');
|
|
542
|
-
const syntheticRow: TableRowData = { index: allRows.length + rowsById.size, cells: synthetic };
|
|
627
|
+
const syntheticRow: TableRowData = { index: allRows.length + rowsById.size, cells: synthetic, typedCells: new Array(visibleColumnIndexes.length).fill(undefined) };
|
|
543
628
|
rowsById.set(child, syntheticRow);
|
|
544
629
|
order.push(child);
|
|
545
630
|
}
|
|
@@ -692,9 +777,15 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
692
777
|
columns: string[],
|
|
693
778
|
rows: TableRowData[],
|
|
694
779
|
state: { search: string; sortKey: string; sortDir: 'asc' | 'desc'; hasUserSort: boolean },
|
|
780
|
+
blockSource?: string,
|
|
781
|
+
options?: Record<string, unknown>
|
|
695
782
|
): TableRowData[] {
|
|
696
783
|
const filtered = state.search
|
|
697
|
-
? rows.filter((row) => matchesFilterQuery(
|
|
784
|
+
? rows.filter((row) => matchesFilterQuery(
|
|
785
|
+
columns,
|
|
786
|
+
this.getDisplayFilterCells(row, columns, blockSource, options),
|
|
787
|
+
state.search
|
|
788
|
+
))
|
|
698
789
|
: rows.slice();
|
|
699
790
|
if (!state.hasUserSort) {
|
|
700
791
|
return filtered;
|
|
@@ -711,6 +802,27 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
711
802
|
});
|
|
712
803
|
}
|
|
713
804
|
|
|
805
|
+
private getDisplayFilterCells(
|
|
806
|
+
row: TableRowData,
|
|
807
|
+
columns: string[],
|
|
808
|
+
blockSource?: string,
|
|
809
|
+
options?: Record<string, unknown>
|
|
810
|
+
): string[] {
|
|
811
|
+
return columns.map((_, columnIndex) => {
|
|
812
|
+
const raw = row.cells[columnIndex] ?? '';
|
|
813
|
+
const typedValue = row.typedCells?.[columnIndex];
|
|
814
|
+
if (typedValue?.values?.length) {
|
|
815
|
+
return this.formatCellDisplayText(raw, typedValue);
|
|
816
|
+
}
|
|
817
|
+
return this.formatCellDisplayValue(raw, {
|
|
818
|
+
columnIndex,
|
|
819
|
+
columns,
|
|
820
|
+
blockSource,
|
|
821
|
+
options,
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
714
826
|
private applyStylesheet(
|
|
715
827
|
cellElement: HTMLElement,
|
|
716
828
|
valueElement: HTMLElement | undefined,
|
|
@@ -886,13 +998,12 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
886
998
|
headerFontOverride?: string
|
|
887
999
|
): number {
|
|
888
1000
|
const headerText = `${columns[columnIndex] ?? ''} ▲`;
|
|
889
|
-
const values = rows.map((row) => formatCellDisplayText(row.cells[columnIndex] ?? ''));
|
|
890
1001
|
const valueFont = typeof referenceCellOrFont === 'string'
|
|
891
1002
|
? referenceCellOrFont
|
|
892
1003
|
: this.resolveFont(referenceCellOrFont);
|
|
893
1004
|
const headerFont = headerFontOverride ?? valueFont;
|
|
894
|
-
const widestValue =
|
|
895
|
-
const width = this.
|
|
1005
|
+
const widestValue = rows.reduce((max, row) => {
|
|
1006
|
+
const width = this.measureRenderedCellContentWidth(row, columnIndex, columns, valueFont);
|
|
896
1007
|
return Math.max(max, width);
|
|
897
1008
|
}, 0);
|
|
898
1009
|
const headerWidth = this.measureTextWidth(headerText, headerFont);
|
|
@@ -922,6 +1033,41 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
922
1033
|
}, 0);
|
|
923
1034
|
}
|
|
924
1035
|
|
|
1036
|
+
private measureRenderedCellContentWidth(
|
|
1037
|
+
row: TableRowData,
|
|
1038
|
+
columnIndex: number,
|
|
1039
|
+
columns: string[],
|
|
1040
|
+
font: string
|
|
1041
|
+
): number {
|
|
1042
|
+
const rawValue = row.cells[columnIndex] ?? '';
|
|
1043
|
+
const typedValue = row.typedCells?.[columnIndex];
|
|
1044
|
+
const formattedValue = this.formatCellDisplayValue(rawValue, {
|
|
1045
|
+
columnIndex,
|
|
1046
|
+
columns,
|
|
1047
|
+
});
|
|
1048
|
+
const valueElement = this.renderValue(formattedValue, typedValue);
|
|
1049
|
+
const decorated = this.decorateCellValueElement({
|
|
1050
|
+
row: { index: row.index, cells: row.cells },
|
|
1051
|
+
columnIndex,
|
|
1052
|
+
columns,
|
|
1053
|
+
value: rawValue,
|
|
1054
|
+
valueElement,
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
const probe = document.createElement('div');
|
|
1058
|
+
probe.style.position = 'fixed';
|
|
1059
|
+
probe.style.left = '-10000px';
|
|
1060
|
+
probe.style.top = '-10000px';
|
|
1061
|
+
probe.style.visibility = 'hidden';
|
|
1062
|
+
probe.style.whiteSpace = 'nowrap';
|
|
1063
|
+
probe.style.font = font;
|
|
1064
|
+
probe.appendChild(decorated);
|
|
1065
|
+
document.body.appendChild(probe);
|
|
1066
|
+
const width = probe.getBoundingClientRect().width;
|
|
1067
|
+
probe.remove();
|
|
1068
|
+
return width;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
925
1071
|
private resolveFont(referenceCell: HTMLElement): string {
|
|
926
1072
|
const computed = window.getComputedStyle(referenceCell);
|
|
927
1073
|
const lineHeight = computed.lineHeight && computed.lineHeight !== 'normal' ? computed.lineHeight : computed.fontSize;
|
|
@@ -950,11 +1096,21 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
950
1096
|
return raw;
|
|
951
1097
|
}
|
|
952
1098
|
|
|
953
|
-
private renderValue(raw: string): HTMLElement {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1099
|
+
private renderValue(raw: string, typedValue?: MdTableCell): HTMLElement {
|
|
1100
|
+
return this.renderCellValue(raw, typedValue);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
protected createUploadCsvButton(
|
|
1104
|
+
_columns: string[],
|
|
1105
|
+
_rows: Array<{ index: number; cells: string[] }>,
|
|
1106
|
+
_isTree: boolean,
|
|
1107
|
+
_blockSource: string | undefined
|
|
1108
|
+
): HTMLElement | undefined {
|
|
1109
|
+
return undefined;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
protected transformCsvDownloadData(columns: string[], rows: string[][]): { columns: string[]; rows: string[][] } {
|
|
1113
|
+
return { columns, rows };
|
|
958
1114
|
}
|
|
959
1115
|
|
|
960
1116
|
private requestCsvDownload(columns: string[], rows: string[][]): void {
|
|
@@ -1047,6 +1203,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
1047
1203
|
type TableRowData = {
|
|
1048
1204
|
index: number;
|
|
1049
1205
|
cells: string[];
|
|
1206
|
+
typedCells?: Array<MdTableCell | undefined>;
|
|
1050
1207
|
treeNodeId?: string;
|
|
1051
1208
|
treeDepth?: number;
|
|
1052
1209
|
treeHasChildren?: boolean;
|
|
@@ -1381,14 +1538,6 @@ function resolveHiddenColumns(
|
|
|
1381
1538
|
return hidden;
|
|
1382
1539
|
}
|
|
1383
1540
|
|
|
1384
|
-
function formatCellDisplayText(raw: string): string {
|
|
1385
|
-
const parts = raw.split(/[\s,]+/).filter((entry) => entry.length > 0);
|
|
1386
|
-
if (parts.length === 0) {
|
|
1387
|
-
return raw;
|
|
1388
|
-
}
|
|
1389
|
-
return parts.map((part) => isIriValue(part) ? shortLabelFromIri(part) : part).join(' ');
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
1541
|
type ParsedFilterTerm =
|
|
1393
1542
|
| { kind: 'global'; value: string }
|
|
1394
1543
|
| { kind: 'scoped'; columns: string[]; value: string };
|
package/src/renderers/types.ts
CHANGED
|
@@ -2,9 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
export type MdExecutionStatus = 'ok' | 'error' | 'unimplemented';
|
|
4
4
|
|
|
5
|
+
export type MdTableCellValueKind = 'iri' | 'literal' | 'bnode' | 'unknown';
|
|
6
|
+
|
|
7
|
+
export interface MdTableCellValue {
|
|
8
|
+
kind: MdTableCellValueKind;
|
|
9
|
+
value: string;
|
|
10
|
+
datatype?: string;
|
|
11
|
+
language?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type MdTableCellKind = 'iri' | 'literal' | 'bnode' | 'unknown';
|
|
15
|
+
|
|
16
|
+
export interface MdTableCell {
|
|
17
|
+
kind: MdTableCellKind;
|
|
18
|
+
value: string;
|
|
19
|
+
values: MdTableCellValue[];
|
|
20
|
+
datatype?: string;
|
|
21
|
+
language?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
5
24
|
export interface MdTablePayload {
|
|
6
25
|
columns: string[];
|
|
7
26
|
rows: string[][];
|
|
27
|
+
rowsTyped?: MdTableCell[][];
|
|
8
28
|
}
|
|
9
29
|
|
|
10
30
|
export interface MdBlockExecutionResult {
|
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
2
|
|
|
3
|
+
import MarkdownIt from 'markdown-it';
|
|
4
|
+
import type { RuleInline } from 'markdown-it/lib/parser_inline.mjs';
|
|
3
5
|
import { displayLabelFromIri } from './renderer.js';
|
|
4
6
|
|
|
7
|
+
const INLINE_MARKDOWN_RENDERER = new MarkdownIt({
|
|
8
|
+
html: true,
|
|
9
|
+
linkify: true,
|
|
10
|
+
typographer: false,
|
|
11
|
+
});
|
|
12
|
+
|
|
5
13
|
export function appendInlineValue(container: HTMLElement, value: string): void {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const index = match.index ?? 0;
|
|
10
|
-
if (index > cursor) {
|
|
11
|
-
appendPlainSegment(container, value.slice(cursor, index));
|
|
12
|
-
}
|
|
13
|
-
const iri = (match[1] ?? '').trim();
|
|
14
|
-
if (iri.length > 0) {
|
|
15
|
-
container.appendChild(createWikiLinkElement(iri, shortLabelFromIri(iri)));
|
|
16
|
-
} else {
|
|
17
|
-
container.appendChild(document.createTextNode(match[0] ?? ''));
|
|
18
|
-
}
|
|
19
|
-
cursor = index + (match[0]?.length ?? 0);
|
|
20
|
-
}
|
|
21
|
-
if (cursor < value.length) {
|
|
22
|
-
appendPlainSegment(container, value.slice(cursor));
|
|
14
|
+
const rendered = INLINE_MARKDOWN_RENDERER.renderInline(value);
|
|
15
|
+
if (!rendered) {
|
|
16
|
+
return;
|
|
23
17
|
}
|
|
18
|
+
const template = document.createElement('template');
|
|
19
|
+
template.innerHTML = rendered;
|
|
20
|
+
container.append(...Array.from(template.content.childNodes));
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
export function appendTokenizedValueParts(container: HTMLElement, raw: string, partClass: string): void {
|
|
@@ -43,8 +40,8 @@ export function appendTokenizedValueParts(container: HTMLElement, raw: string, p
|
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
fragment.dataset.value = token;
|
|
46
|
-
if (
|
|
47
|
-
fragment.appendChild(
|
|
43
|
+
if (isUrlValue(token)) {
|
|
44
|
+
fragment.appendChild(createExternalLinkElement(token));
|
|
48
45
|
} else {
|
|
49
46
|
appendInlineValue(fragment, token);
|
|
50
47
|
}
|
|
@@ -77,21 +74,26 @@ export function createWikiLinkElement(iri: string, label: string): HTMLAnchorEle
|
|
|
77
74
|
return link;
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
function
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
container.appendChild(createWikiLinkElement(part, shortLabelFromIri(part)));
|
|
77
|
+
export function isUrlValue(value: string): boolean {
|
|
78
|
+
const trimmed = value.trim();
|
|
79
|
+
if (!trimmed) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const parsed = new URL(trimmed);
|
|
84
|
+
return parsed.protocol.length > 1;
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
92
87
|
}
|
|
93
88
|
}
|
|
94
89
|
|
|
90
|
+
function createExternalLinkElement(href: string, label?: string): HTMLAnchorElement {
|
|
91
|
+
const link = document.createElement('a');
|
|
92
|
+
link.href = href;
|
|
93
|
+
link.textContent = label ?? href;
|
|
94
|
+
return link;
|
|
95
|
+
}
|
|
96
|
+
|
|
95
97
|
function parseWikilinkToken(value: string): string | undefined {
|
|
96
98
|
const match = /^\[\[([^\]]+)\]\]$/.exec(value);
|
|
97
99
|
if (!match) {
|
|
@@ -100,3 +102,36 @@ function parseWikilinkToken(value: string): string | undefined {
|
|
|
100
102
|
const iri = (match[1] ?? '').trim();
|
|
101
103
|
return iri.length > 0 ? iri : undefined;
|
|
102
104
|
}
|
|
105
|
+
|
|
106
|
+
const wikilinkRule: RuleInline = (state, silent): boolean => {
|
|
107
|
+
const { src } = state;
|
|
108
|
+
const start = state.pos;
|
|
109
|
+
if (src.charCodeAt(start) !== 0x5b || src.charCodeAt(start + 1) !== 0x5b) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
const close = src.indexOf(']]', start + 2);
|
|
113
|
+
if (close < 0) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const iri = src.slice(start + 2, close).trim();
|
|
117
|
+
if (!iri) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (!silent) {
|
|
121
|
+
const label = shortLabelFromIri(iri);
|
|
122
|
+
const open = state.push('link_open', 'a', 1);
|
|
123
|
+
open.attrSet('class', 'wikilink');
|
|
124
|
+
open.attrSet('iri', iri);
|
|
125
|
+
open.attrSet('href', '#');
|
|
126
|
+
open.attrSet('title', iri);
|
|
127
|
+
|
|
128
|
+
const text = state.push('text', '', 0);
|
|
129
|
+
text.content = label;
|
|
130
|
+
|
|
131
|
+
state.push('link_close', 'a', -1);
|
|
132
|
+
}
|
|
133
|
+
state.pos = close + 2;
|
|
134
|
+
return true;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
INLINE_MARKDOWN_RENDERER.inline.ruler.before('link', 'wiki-link', wikilinkRule);
|
|
@@ -110,7 +110,6 @@ function applyWikilinks(
|
|
|
110
110
|
const span = document.createElement('span');
|
|
111
111
|
span.className = 'wikilink';
|
|
112
112
|
span.setAttribute('iri', iri);
|
|
113
|
-
span.title = iri;
|
|
114
113
|
span.textContent = label;
|
|
115
114
|
link.replaceWith(span);
|
|
116
115
|
continue;
|
|
@@ -123,7 +122,6 @@ function applyWikilinks(
|
|
|
123
122
|
const span = document.createElement('span');
|
|
124
123
|
span.className = 'wikilink';
|
|
125
124
|
span.setAttribute('iri', iri);
|
|
126
|
-
span.title = iri;
|
|
127
125
|
span.textContent = label;
|
|
128
126
|
link.replaceWith(span);
|
|
129
127
|
}
|
|
@@ -172,6 +170,25 @@ function setupDownloadHandler(): void {
|
|
|
172
170
|
});
|
|
173
171
|
}
|
|
174
172
|
|
|
173
|
+
function setupIriNavigationHandler(
|
|
174
|
+
wikilinkIndex: Record<string, string>,
|
|
175
|
+
iriAliasIndex: Record<string, string>,
|
|
176
|
+
linkingEnabled: boolean
|
|
177
|
+
): void {
|
|
178
|
+
window.addEventListener('md-navigate-iri', (event: Event) => {
|
|
179
|
+
const detail = (event as CustomEvent<{ iri?: string }>).detail;
|
|
180
|
+
const iri = typeof detail?.iri === 'string' ? detail.iri.trim() : '';
|
|
181
|
+
if (!iri || !linkingEnabled) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const href = resolveWikiHref(iri, wikilinkIndex, iriAliasIndex);
|
|
185
|
+
if (!href) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
window.location.assign(href);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
175
192
|
function getMdKindFromCodeElement(code: Element): string | undefined {
|
|
176
193
|
for (const className of Array.from(code.classList)) {
|
|
177
194
|
if (!className.startsWith('language-')) {
|
|
@@ -211,6 +228,7 @@ async function applyExecutionResults(): Promise<void> {
|
|
|
211
228
|
applyWikilinks(document, wikilinkIndex, iriAliasIndex, linkingEnabled);
|
|
212
229
|
// Re-apply wikilinks for dynamic renderer re-renders (tables, filters, paging, etc.).
|
|
213
230
|
installWikilinkObserver(wikilinkIndex, iriAliasIndex, linkingEnabled);
|
|
231
|
+
setupIriNavigationHandler(wikilinkIndex, iriAliasIndex, linkingEnabled);
|
|
214
232
|
|
|
215
233
|
const manifest = parseJsonNode<ManifestEntry[]>('oml-md-block-manifest', []);
|
|
216
234
|
if (!Array.isArray(manifest) || manifest.length === 0) {
|