@oml/markdown 0.11.0 → 0.13.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/diagram-renderer.js +160 -1
- 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 +4 -2
- package/out/renderers/table-renderer.js +104 -38
- 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 +7452 -1297
- 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/diagram-renderer.ts +167 -1
- 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 +151 -39
- 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 = {
|
|
@@ -35,6 +35,20 @@ type TableRootWithAiContext = HTMLDivElement & {
|
|
|
35
35
|
};
|
|
36
36
|
};
|
|
37
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
|
+
|
|
38
52
|
export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
39
53
|
protected readonly tableKinds: ReadonlyArray<TableBlockKind> = ['table'];
|
|
40
54
|
|
|
@@ -52,9 +66,11 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
52
66
|
const tableRoot = this.renderInteractiveTable(
|
|
53
67
|
result.payload?.columns ?? [],
|
|
54
68
|
result.payload?.rows ?? [],
|
|
69
|
+
result.payload?.rowsTyped,
|
|
55
70
|
stylesheet,
|
|
56
71
|
result.options,
|
|
57
|
-
result.blockSource
|
|
72
|
+
result.blockSource,
|
|
73
|
+
result.blockId
|
|
58
74
|
);
|
|
59
75
|
if (typeof result.blockId === 'string' && result.blockId.length > 0) {
|
|
60
76
|
tableRoot.dataset.tableBlockId = result.blockId;
|
|
@@ -96,14 +112,20 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
96
112
|
protected renderInteractiveTable(
|
|
97
113
|
columns: string[],
|
|
98
114
|
rows: string[][],
|
|
115
|
+
rowsTyped: MdTableCell[][] | undefined,
|
|
99
116
|
stylesheet: CompiledTableStyleRule[],
|
|
100
117
|
options: Record<string, unknown> | undefined,
|
|
101
|
-
blockSource?: string
|
|
118
|
+
blockSource?: string,
|
|
119
|
+
blockId?: string
|
|
102
120
|
): HTMLElement {
|
|
103
121
|
const root = document.createElement('div') as TableRootWithAiContext;
|
|
104
122
|
root.className = 'table-root graph-root';
|
|
105
123
|
const isTree = this.tableKinds.includes('tree');
|
|
106
|
-
const allRowsWithIndex = rows.map((cells, index) => ({
|
|
124
|
+
const allRowsWithIndex = rows.map((cells, index) => ({
|
|
125
|
+
index,
|
|
126
|
+
cells,
|
|
127
|
+
typedCells: rowsTyped?.[index],
|
|
128
|
+
}));
|
|
107
129
|
const containmentColumns = isTree ? resolveContainmentColumns(columns, options) : [];
|
|
108
130
|
const baseVisibleColumnIndexes = isTree
|
|
109
131
|
? columns.map((_, index) => index).filter((index) => !containmentColumns.includes(index))
|
|
@@ -112,6 +134,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
112
134
|
const baseRowsWithIndex = allRowsWithIndex.map((row) => ({
|
|
113
135
|
...row,
|
|
114
136
|
cells: baseVisibleColumnIndexes.map((columnIndex) => row.cells[columnIndex] ?? ''),
|
|
137
|
+
typedCells: baseVisibleColumnIndexes.map((columnIndex) => row.typedCells?.[columnIndex]),
|
|
115
138
|
}));
|
|
116
139
|
const selectorRowsByIndex = new Map(baseRowsWithIndex.map((row) => [row.index, row] as const));
|
|
117
140
|
const baseColumnContexts = createColumnContexts(baseVisibleColumns, baseRowsWithIndex);
|
|
@@ -121,6 +144,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
121
144
|
const rowsWithIndex = allRowsWithIndex.map((row) => ({
|
|
122
145
|
...row,
|
|
123
146
|
cells: visibleColumnIndexes.map((columnIndex) => row.cells[columnIndex] ?? ''),
|
|
147
|
+
typedCells: visibleColumnIndexes.map((columnIndex) => row.typedCells?.[columnIndex]),
|
|
124
148
|
}));
|
|
125
149
|
root.__omlAiContext = {
|
|
126
150
|
columns: visibleColumns.slice(),
|
|
@@ -139,19 +163,36 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
139
163
|
const fullyExpandedTreeRows = isTree && treeModel
|
|
140
164
|
? this.flattenAllTreeRows(treeModel)
|
|
141
165
|
: undefined;
|
|
166
|
+
const persistedState = blockId
|
|
167
|
+
? (window as TableRendererWindow).__omlGetPersistedTableUiState?.(blockId)
|
|
168
|
+
: undefined;
|
|
142
169
|
|
|
143
170
|
const state = {
|
|
144
|
-
search: '',
|
|
145
|
-
pageSize: 50,
|
|
146
|
-
page: 0,
|
|
147
|
-
sortKey: '' as string,
|
|
148
|
-
sortDir: 'asc' as 'asc' | 'desc',
|
|
149
|
-
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,
|
|
150
177
|
columnWidths: new Array<number | undefined>(columns.length).fill(undefined),
|
|
151
178
|
initialAutosizeApplied: false,
|
|
152
179
|
expanded: new Set<string>(treeModel?.expandableNodes ?? []),
|
|
153
180
|
};
|
|
154
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
|
+
|
|
155
196
|
const controls = document.createElement('div');
|
|
156
197
|
controls.className = 'table-controls';
|
|
157
198
|
|
|
@@ -168,9 +209,11 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
168
209
|
}
|
|
169
210
|
pageSize.appendChild(option);
|
|
170
211
|
}
|
|
212
|
+
pageSize.value = String(state.pageSize);
|
|
171
213
|
pageSize.addEventListener('change', () => {
|
|
172
214
|
state.pageSize = Number.parseInt(pageSize.value, 10);
|
|
173
215
|
state.page = 0;
|
|
216
|
+
persistUiState();
|
|
174
217
|
renderPage();
|
|
175
218
|
});
|
|
176
219
|
if (!isTree) {
|
|
@@ -205,9 +248,11 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
205
248
|
searchInput.type = 'search';
|
|
206
249
|
searchInput.className = 'tree-filter';
|
|
207
250
|
searchInput.placeholder = 'Filter...';
|
|
251
|
+
searchInput.value = state.search;
|
|
208
252
|
searchInput.addEventListener('input', () => {
|
|
209
253
|
state.search = searchInput.value.trim().toLowerCase();
|
|
210
254
|
state.page = 0;
|
|
255
|
+
persistUiState();
|
|
211
256
|
renderPage();
|
|
212
257
|
});
|
|
213
258
|
controlsRight.appendChild(searchInput);
|
|
@@ -229,13 +274,13 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
229
274
|
downloadButton.appendChild(iconSvg);
|
|
230
275
|
downloadButton.addEventListener('click', () => {
|
|
231
276
|
const sourceRows = isTree && treeModel
|
|
232
|
-
? this.resolveTreeSourceRows(treeModel, visibleColumns, state.expanded, state.search)
|
|
277
|
+
? this.resolveTreeSourceRows(treeModel, visibleColumns, state.expanded, state.search, blockSource, options)
|
|
233
278
|
: rowsWithIndex;
|
|
234
279
|
if (isTree && treeModel) {
|
|
235
280
|
this.requestTreeJsonDownload(visibleColumns, treeModel, sourceRows);
|
|
236
281
|
return;
|
|
237
282
|
}
|
|
238
|
-
const filtered = this.applyFiltersAndSorting(
|
|
283
|
+
const filtered = this.applyFiltersAndSorting(visibleColumns, sourceRows, state, blockSource, options);
|
|
239
284
|
const csvExport = this.transformCsvDownloadData(visibleColumns, filtered.map((entry) => entry.cells));
|
|
240
285
|
this.requestCsvDownload(csvExport.columns, csvExport.rows);
|
|
241
286
|
});
|
|
@@ -283,6 +328,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
283
328
|
}
|
|
284
329
|
if (state.page > 0) {
|
|
285
330
|
state.page -= 1;
|
|
331
|
+
persistUiState();
|
|
286
332
|
renderPage();
|
|
287
333
|
}
|
|
288
334
|
});
|
|
@@ -290,10 +336,11 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
290
336
|
if (isTree) {
|
|
291
337
|
return;
|
|
292
338
|
}
|
|
293
|
-
const filtered = this.applyFiltersAndSorting(
|
|
339
|
+
const filtered = this.applyFiltersAndSorting(visibleColumns, rowsWithIndex, state, blockSource, options);
|
|
294
340
|
const totalPages = Math.max(1, Math.ceil(filtered.length / state.pageSize));
|
|
295
341
|
if (state.page < totalPages - 1) {
|
|
296
342
|
state.page += 1;
|
|
343
|
+
persistUiState();
|
|
297
344
|
renderPage();
|
|
298
345
|
}
|
|
299
346
|
});
|
|
@@ -311,9 +358,11 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
311
358
|
|
|
312
359
|
const renderPage = () => {
|
|
313
360
|
const sourceRows = isTree && treeModel
|
|
314
|
-
? this.resolveTreeSourceRows(treeModel, visibleColumns, state.expanded, state.search)
|
|
361
|
+
? this.resolveTreeSourceRows(treeModel, visibleColumns, state.expanded, state.search, blockSource, options)
|
|
315
362
|
: rowsWithIndex;
|
|
316
|
-
const filtered = isTree
|
|
363
|
+
const filtered = isTree
|
|
364
|
+
? sourceRows.slice()
|
|
365
|
+
: this.applyFiltersAndSorting(visibleColumns, sourceRows, state, blockSource, options);
|
|
317
366
|
const total = filtered.length;
|
|
318
367
|
const totalTreeRows = isTree
|
|
319
368
|
? (fullyExpandedTreeRows?.length ?? total)
|
|
@@ -321,6 +370,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
321
370
|
const totalPages = isTree ? 1 : Math.max(1, Math.ceil(total / state.pageSize));
|
|
322
371
|
if (!isTree && state.page >= totalPages) {
|
|
323
372
|
state.page = totalPages - 1;
|
|
373
|
+
persistUiState();
|
|
324
374
|
}
|
|
325
375
|
const gridTemplateColumns = this.buildGridTemplate(visibleColumns.length, state.columnWidths);
|
|
326
376
|
|
|
@@ -348,6 +398,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
348
398
|
}
|
|
349
399
|
state.hasUserSort = true;
|
|
350
400
|
state.page = 0;
|
|
401
|
+
persistUiState();
|
|
351
402
|
renderPage();
|
|
352
403
|
});
|
|
353
404
|
}
|
|
@@ -375,6 +426,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
375
426
|
const rowContext = createRowContext(baseVisibleColumns, selectorRow);
|
|
376
427
|
for (let columnIndex = 0; columnIndex < visibleColumns.length; columnIndex += 1) {
|
|
377
428
|
const value = rowEntry.cells[columnIndex] ?? '';
|
|
429
|
+
const typedValue = rowEntry.typedCells?.[columnIndex];
|
|
378
430
|
const td = document.createElement('div');
|
|
379
431
|
td.className = 'table-cell';
|
|
380
432
|
const valueElement = this.renderValue(this.formatCellDisplayValue(value, {
|
|
@@ -382,7 +434,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
382
434
|
columns: visibleColumns,
|
|
383
435
|
blockSource,
|
|
384
436
|
options,
|
|
385
|
-
}));
|
|
437
|
+
}), typedValue);
|
|
386
438
|
if (isTree && columnIndex === 0) {
|
|
387
439
|
const treeCell = this.renderTreeCell(valueElement, rowEntry, treeModel, state.expanded, () => renderPage());
|
|
388
440
|
td.appendChild(treeCell);
|
|
@@ -457,15 +509,23 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
457
509
|
model: TreeModel,
|
|
458
510
|
columns: string[],
|
|
459
511
|
expanded: ReadonlySet<string>,
|
|
460
|
-
search: string
|
|
512
|
+
search: string,
|
|
513
|
+
blockSource?: string,
|
|
514
|
+
options?: Record<string, unknown>
|
|
461
515
|
): TableRowData[] {
|
|
462
516
|
if (search) {
|
|
463
|
-
return this.filterTreeRowsBySearch(model, columns, search);
|
|
517
|
+
return this.filterTreeRowsBySearch(model, columns, search, blockSource, options);
|
|
464
518
|
}
|
|
465
519
|
return this.flattenTreeRows(model, expanded);
|
|
466
520
|
}
|
|
467
521
|
|
|
468
|
-
private filterTreeRowsBySearch(
|
|
522
|
+
private filterTreeRowsBySearch(
|
|
523
|
+
model: TreeModel,
|
|
524
|
+
columns: string[],
|
|
525
|
+
search: string,
|
|
526
|
+
blockSource?: string,
|
|
527
|
+
options?: Record<string, unknown>
|
|
528
|
+
): TableRowData[] {
|
|
469
529
|
const fullyExpanded = this.flattenTreeRows(model, new Set<string>(model.expandableNodes));
|
|
470
530
|
const matchedIds = new Set<string>();
|
|
471
531
|
for (const row of fullyExpanded) {
|
|
@@ -473,7 +533,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
473
533
|
if (!nodeId) {
|
|
474
534
|
continue;
|
|
475
535
|
}
|
|
476
|
-
if (matchesFilterQuery(columns, row
|
|
536
|
+
if (matchesFilterQuery(columns, this.getDisplayFilterCells(row, columns, blockSource, options), search)) {
|
|
477
537
|
matchedIds.add(nodeId);
|
|
478
538
|
}
|
|
479
539
|
}
|
|
@@ -517,6 +577,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
517
577
|
const projectRow = (row: TableRowData): TableRowData => ({
|
|
518
578
|
...row,
|
|
519
579
|
cells: visibleColumnIndexes.map((columnIndex) => row.cells[columnIndex] ?? ''),
|
|
580
|
+
typedCells: visibleColumnIndexes.map((columnIndex) => row.typedCells?.[columnIndex]),
|
|
520
581
|
});
|
|
521
582
|
for (const row of allRows) {
|
|
522
583
|
const id = row.cells[idColumnIndex] ?? '';
|
|
@@ -563,7 +624,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
563
624
|
const syntheticAll = new Array<string>(allColumns.length).fill('');
|
|
564
625
|
syntheticAll[idColumnIndex] = child;
|
|
565
626
|
const synthetic = visibleColumnIndexes.map((columnIndex) => syntheticAll[columnIndex] ?? '');
|
|
566
|
-
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) };
|
|
567
628
|
rowsById.set(child, syntheticRow);
|
|
568
629
|
order.push(child);
|
|
569
630
|
}
|
|
@@ -716,9 +777,15 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
716
777
|
columns: string[],
|
|
717
778
|
rows: TableRowData[],
|
|
718
779
|
state: { search: string; sortKey: string; sortDir: 'asc' | 'desc'; hasUserSort: boolean },
|
|
780
|
+
blockSource?: string,
|
|
781
|
+
options?: Record<string, unknown>
|
|
719
782
|
): TableRowData[] {
|
|
720
783
|
const filtered = state.search
|
|
721
|
-
? rows.filter((row) => matchesFilterQuery(
|
|
784
|
+
? rows.filter((row) => matchesFilterQuery(
|
|
785
|
+
columns,
|
|
786
|
+
this.getDisplayFilterCells(row, columns, blockSource, options),
|
|
787
|
+
state.search
|
|
788
|
+
))
|
|
722
789
|
: rows.slice();
|
|
723
790
|
if (!state.hasUserSort) {
|
|
724
791
|
return filtered;
|
|
@@ -735,6 +802,27 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
735
802
|
});
|
|
736
803
|
}
|
|
737
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
|
+
|
|
738
826
|
private applyStylesheet(
|
|
739
827
|
cellElement: HTMLElement,
|
|
740
828
|
valueElement: HTMLElement | undefined,
|
|
@@ -910,13 +998,12 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
910
998
|
headerFontOverride?: string
|
|
911
999
|
): number {
|
|
912
1000
|
const headerText = `${columns[columnIndex] ?? ''} ▲`;
|
|
913
|
-
const values = rows.map((row) => formatCellDisplayText(row.cells[columnIndex] ?? ''));
|
|
914
1001
|
const valueFont = typeof referenceCellOrFont === 'string'
|
|
915
1002
|
? referenceCellOrFont
|
|
916
1003
|
: this.resolveFont(referenceCellOrFont);
|
|
917
1004
|
const headerFont = headerFontOverride ?? valueFont;
|
|
918
|
-
const widestValue =
|
|
919
|
-
const width = this.
|
|
1005
|
+
const widestValue = rows.reduce((max, row) => {
|
|
1006
|
+
const width = this.measureRenderedCellContentWidth(row, columnIndex, columns, valueFont);
|
|
920
1007
|
return Math.max(max, width);
|
|
921
1008
|
}, 0);
|
|
922
1009
|
const headerWidth = this.measureTextWidth(headerText, headerFont);
|
|
@@ -946,6 +1033,41 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
946
1033
|
}, 0);
|
|
947
1034
|
}
|
|
948
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
|
+
|
|
949
1071
|
private resolveFont(referenceCell: HTMLElement): string {
|
|
950
1072
|
const computed = window.getComputedStyle(referenceCell);
|
|
951
1073
|
const lineHeight = computed.lineHeight && computed.lineHeight !== 'normal' ? computed.lineHeight : computed.fontSize;
|
|
@@ -974,11 +1096,8 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
974
1096
|
return raw;
|
|
975
1097
|
}
|
|
976
1098
|
|
|
977
|
-
private renderValue(raw: string): HTMLElement {
|
|
978
|
-
|
|
979
|
-
value.className = 'table-value';
|
|
980
|
-
appendTokenizedValueParts(value, raw, 'table-value-part');
|
|
981
|
-
return value;
|
|
1099
|
+
private renderValue(raw: string, typedValue?: MdTableCell): HTMLElement {
|
|
1100
|
+
return this.renderCellValue(raw, typedValue);
|
|
982
1101
|
}
|
|
983
1102
|
|
|
984
1103
|
protected createUploadCsvButton(
|
|
@@ -1084,6 +1203,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
|
|
|
1084
1203
|
type TableRowData = {
|
|
1085
1204
|
index: number;
|
|
1086
1205
|
cells: string[];
|
|
1206
|
+
typedCells?: Array<MdTableCell | undefined>;
|
|
1087
1207
|
treeNodeId?: string;
|
|
1088
1208
|
treeDepth?: number;
|
|
1089
1209
|
treeHasChildren?: boolean;
|
|
@@ -1418,14 +1538,6 @@ function resolveHiddenColumns(
|
|
|
1418
1538
|
return hidden;
|
|
1419
1539
|
}
|
|
1420
1540
|
|
|
1421
|
-
function formatCellDisplayText(raw: string): string {
|
|
1422
|
-
const parts = raw.split(/[\s,]+/).filter((entry) => entry.length > 0);
|
|
1423
|
-
if (parts.length === 0) {
|
|
1424
|
-
return raw;
|
|
1425
|
-
}
|
|
1426
|
-
return parts.map((part) => isIriValue(part) ? shortLabelFromIri(part) : part).join(' ');
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
1541
|
type ParsedFilterTerm =
|
|
1430
1542
|
| { kind: 'global'; value: string }
|
|
1431
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) {
|