@oml/markdown 0.12.0 → 0.14.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.
Files changed (72) hide show
  1. package/out/index.d.ts +1 -0
  2. package/out/index.js +1 -0
  3. package/out/index.js.map +1 -1
  4. package/out/md/md-execution.d.ts +3 -3
  5. package/out/md/md-execution.js +1 -1
  6. package/out/md/md-execution.js.map +1 -1
  7. package/out/md/md-executor.js +6 -8
  8. package/out/md/md-executor.js.map +1 -1
  9. package/out/md/md-frontmatter.d.ts +1 -1
  10. package/out/md/md-frontmatter.js +15 -10
  11. package/out/md/md-frontmatter.js.map +1 -1
  12. package/out/md/md-runtime.js +1 -1
  13. package/out/md/md-runtime.js.map +1 -1
  14. package/out/md/md-types.d.ts +2 -2
  15. package/out/renderers/diagram-renderer.js +231 -12
  16. package/out/renderers/diagram-renderer.js.map +1 -1
  17. package/out/renderers/graph-renderer.js +2 -2
  18. package/out/renderers/graph-renderer.js.map +1 -1
  19. package/out/renderers/table-renderer.js +25 -10
  20. package/out/renderers/table-renderer.js.map +1 -1
  21. package/out/static/browser-runtime.bundle.js +489 -25
  22. package/out/static/browser-runtime.bundle.js.map +3 -3
  23. package/out/static/browser-runtime.js +258 -0
  24. package/out/static/browser-runtime.js.map +1 -1
  25. package/out/static/runtime-assets.d.ts +1 -1
  26. package/out/static/runtime-assets.js +1 -0
  27. package/out/static/runtime-assets.js.map +1 -1
  28. package/out/template/binder.d.ts +2 -0
  29. package/out/template/binder.js +56 -0
  30. package/out/template/binder.js.map +1 -0
  31. package/out/template/catalog.d.ts +8 -0
  32. package/out/template/catalog.js +26 -0
  33. package/out/template/catalog.js.map +1 -0
  34. package/out/template/compose.d.ts +7 -0
  35. package/out/template/compose.js +93 -0
  36. package/out/template/compose.js.map +1 -0
  37. package/out/template/definition.d.ts +3 -0
  38. package/out/template/definition.js +204 -0
  39. package/out/template/definition.js.map +1 -0
  40. package/out/template/engine.d.ts +8 -0
  41. package/out/template/engine.js +30 -0
  42. package/out/template/engine.js.map +1 -0
  43. package/out/template/index.d.ts +7 -0
  44. package/out/template/index.js +9 -0
  45. package/out/template/index.js.map +1 -0
  46. package/out/template/resolver.d.ts +4 -0
  47. package/out/template/resolver.js +58 -0
  48. package/out/template/resolver.js.map +1 -0
  49. package/out/template/types.d.ts +82 -0
  50. package/out/template/types.js +3 -0
  51. package/out/template/types.js.map +1 -0
  52. package/package.json +2 -2
  53. package/src/index.ts +1 -0
  54. package/src/md/md-execution.ts +3 -3
  55. package/src/md/md-executor.ts +6 -9
  56. package/src/md/md-frontmatter.ts +15 -10
  57. package/src/md/md-runtime.ts +1 -1
  58. package/src/md/md-types.ts +2 -2
  59. package/src/renderers/diagram-renderer.ts +229 -12
  60. package/src/renderers/graph-renderer.ts +2 -2
  61. package/src/renderers/table-renderer.ts +26 -10
  62. package/src/static/browser-runtime.ts +305 -0
  63. package/src/static/markdown-webview.css +13 -0
  64. package/src/static/runtime-assets.ts +1 -0
  65. package/src/template/binder.ts +70 -0
  66. package/src/template/catalog.ts +35 -0
  67. package/src/template/compose.ts +107 -0
  68. package/src/template/definition.ts +222 -0
  69. package/src/template/engine.ts +45 -0
  70. package/src/template/index.ts +9 -0
  71. package/src/template/resolver.ts +75 -0
  72. package/src/template/types.ts +111 -0
@@ -236,6 +236,12 @@ async function renderWithX6(
236
236
  container: liveCanvas,
237
237
  autoResize: false,
238
238
  grid: false,
239
+ selecting: {
240
+ enabled: true,
241
+ multiple: true,
242
+ // X6 native additive selection modifiers.
243
+ multipleSelectionModifiers: ['meta', 'ctrl'],
244
+ },
239
245
  panning: true,
240
246
  mousewheel: {
241
247
  enabled: true,
@@ -315,7 +321,7 @@ async function renderWithX6(
315
321
  bubbles: true,
316
322
  detail: {
317
323
  iri,
318
- previewEnabled: /^Mac/i.test(navigator.platform) ? event.metaKey : event.ctrlKey,
324
+ previewEnabled: event.altKey,
319
325
  anchorRect: {
320
326
  left: rect.left,
321
327
  right: rect.right,
@@ -1079,26 +1085,200 @@ async function renderWithX6(
1079
1085
  }
1080
1086
 
1081
1087
  // Compartments are structural containers: do not drag them; select parent instead.
1082
- const selectCompartmentParent = (node: any): void => {
1088
+ const selectionTargetForNode = (node: any): any => {
1083
1089
  if (!isCompartmentNode(node)) {
1084
- return;
1090
+ return node;
1085
1091
  }
1086
1092
  const parent = typeof node.getParent === 'function' ? node.getParent() : undefined;
1087
- if (!parent) {
1093
+ return parent ?? node;
1094
+ };
1095
+ const multiSelectedNodeIds = new Set<string>();
1096
+ let primarySelectedNodeId: string | undefined;
1097
+ const cssEscape = (value: string): string => value.replace(/["\\]/g, '\\$&');
1098
+ const createSvgElement = (name: string): SVGElement =>
1099
+ document.createElementNS('http://www.w3.org/2000/svg', name);
1100
+ const clearSecondaryDecoration = (container: Element): void => {
1101
+ for (const node of Array.from(container.querySelectorAll('g.oml-secondary-selection'))) {
1102
+ node.remove();
1103
+ }
1104
+ for (const node of Array.from(container.querySelectorAll('[data-oml-multi-selected-style="1"]'))) {
1105
+ if (!(node instanceof SVGElement)) {
1106
+ continue;
1107
+ }
1108
+ node.style.removeProperty('stroke');
1109
+ node.style.removeProperty('stroke-width');
1110
+ node.removeAttribute('data-oml-multi-selected-style');
1111
+ }
1112
+ };
1113
+ const renderSecondaryDecoration = (container: Element): void => {
1114
+ clearSecondaryDecoration(container);
1115
+ const graphics = container as unknown as SVGGraphicsElement;
1116
+ if (typeof graphics.getBBox !== 'function') {
1117
+ return;
1118
+ }
1119
+ let bbox: DOMRect | undefined;
1120
+ try {
1121
+ bbox = graphics.getBBox();
1122
+ } catch {
1088
1123
  return;
1089
1124
  }
1090
- if (typeof graphView.cleanSelection === 'function') {
1091
- graphView.cleanSelection();
1125
+ if (!bbox || bbox.width <= 0 || bbox.height <= 0) {
1126
+ return;
1127
+ }
1128
+ const widgetMargin = 3;
1129
+ const handleSize = 6;
1130
+ const handleHalf = handleSize / 2;
1131
+ const x = bbox.x - widgetMargin;
1132
+ const y = bbox.y - widgetMargin;
1133
+ const width = bbox.width + (widgetMargin * 2);
1134
+ const height = bbox.height + (widgetMargin * 2);
1135
+
1136
+ const group = createSvgElement('g');
1137
+ group.setAttribute('class', 'oml-secondary-selection');
1138
+ group.setAttribute('pointer-events', 'none');
1139
+
1140
+ const box = createSvgElement('rect');
1141
+ box.setAttribute('x', `${x}`);
1142
+ box.setAttribute('y', `${y}`);
1143
+ box.setAttribute('width', `${width}`);
1144
+ box.setAttribute('height', `${height}`);
1145
+ box.setAttribute('fill', 'none');
1146
+ box.setAttribute('stroke', '#000');
1147
+ box.setAttribute('stroke-width', '1');
1148
+ box.setAttribute('stroke-dasharray', '4 3');
1149
+ group.appendChild(box);
1150
+
1151
+ const points = [
1152
+ [x, y],
1153
+ [x + (width / 2), y],
1154
+ [x + width, y],
1155
+ [x, y + (height / 2)],
1156
+ [x + width, y + (height / 2)],
1157
+ [x, y + height],
1158
+ [x + (width / 2), y + height],
1159
+ [x + width, y + height],
1160
+ ];
1161
+ for (const [cx, cy] of points) {
1162
+ const handle = createSvgElement('rect');
1163
+ handle.setAttribute('x', `${cx - handleHalf}`);
1164
+ handle.setAttribute('y', `${cy - handleHalf}`);
1165
+ handle.setAttribute('width', `${handleSize}`);
1166
+ handle.setAttribute('height', `${handleSize}`);
1167
+ handle.setAttribute('rx', '3');
1168
+ handle.setAttribute('ry', '3');
1169
+ handle.setAttribute('fill', '#000');
1170
+ handle.setAttribute('stroke', '#000');
1171
+ handle.setAttribute('stroke-width', '1');
1172
+ group.appendChild(handle);
1173
+ }
1174
+ container.appendChild(group);
1175
+ };
1176
+ const refreshPrimaryWidget = (): void => {
1177
+ if (typeof nodeTransform?.clearWidgets === 'function') {
1178
+ nodeTransform.clearWidgets();
1179
+ }
1180
+ if (!primarySelectedNodeId || typeof nodeTransform?.createWidget !== 'function') {
1181
+ return;
1182
+ }
1183
+ const cell = typeof graphView.getCellById === 'function' ? graphView.getCellById(primarySelectedNodeId) : undefined;
1184
+ if (!cell) {
1185
+ return;
1186
+ }
1187
+ nodeTransform.createWidget(cell);
1188
+ };
1189
+ let applySelectionScheduled = false;
1190
+ const applyMultiSelectionClassesNow = (): void => {
1191
+ const selectedIds = new Set(multiSelectedNodeIds);
1192
+ const nodeContainers = Array.from(liveCanvas.querySelectorAll('.x6-cell[data-cell-id], .x6-node[data-cell-id]'));
1193
+ for (const element of nodeContainers) {
1194
+ const container = element as Element;
1195
+ const id = String(container.getAttribute('data-cell-id') ?? '');
1196
+ const selected = selectedIds.has(id);
1197
+ container.classList.toggle('oml-diagram-multi-selected', selected);
1198
+ if (selected && id !== primarySelectedNodeId) {
1199
+ renderSecondaryDecoration(container);
1200
+ } else {
1201
+ clearSecondaryDecoration(container);
1202
+ }
1203
+ }
1204
+ for (const id of selectedIds) {
1205
+ const selector = `.x6-cell[data-cell-id="${cssEscape(id)}"], .x6-node[data-cell-id="${cssEscape(id)}"]`;
1206
+ const domMatches = Array.from(liveCanvas.querySelectorAll(selector));
1207
+ for (const element of domMatches) {
1208
+ if (element instanceof Element) {
1209
+ element.classList.add('oml-diagram-multi-selected');
1210
+ if (id !== primarySelectedNodeId) {
1211
+ renderSecondaryDecoration(element);
1212
+ } else {
1213
+ clearSecondaryDecoration(element);
1214
+ }
1215
+ }
1216
+ }
1217
+ const cell = typeof graphView.getCellById === 'function' ? graphView.getCellById(id) : undefined;
1218
+ const view = cell && typeof graphView.findViewByCell === 'function' ? graphView.findViewByCell(cell) : undefined;
1219
+ const container = view?.container as Element | undefined;
1220
+ if (container instanceof Element) {
1221
+ container.classList.add('oml-diagram-multi-selected');
1222
+ if (id !== primarySelectedNodeId) {
1223
+ renderSecondaryDecoration(container);
1224
+ } else {
1225
+ clearSecondaryDecoration(container);
1226
+ }
1227
+ }
1228
+ }
1229
+ };
1230
+ const applyMultiSelectionClasses = (): void => {
1231
+ if (applySelectionScheduled) {
1232
+ return;
1092
1233
  }
1093
- if (typeof graphView.select === 'function') {
1094
- graphView.select(parent);
1234
+ applySelectionScheduled = true;
1235
+ window.requestAnimationFrame(() => {
1236
+ window.requestAnimationFrame(() => {
1237
+ applySelectionScheduled = false;
1238
+ applyMultiSelectionClassesNow();
1239
+ });
1240
+ });
1241
+ };
1242
+ const replaceMultiSelection = (ids: Iterable<string>): void => {
1243
+ multiSelectedNodeIds.clear();
1244
+ for (const id of ids) {
1245
+ if (!id) {
1246
+ continue;
1247
+ }
1248
+ multiSelectedNodeIds.add(id);
1095
1249
  }
1250
+ applyMultiSelectionClasses();
1251
+ };
1252
+ const clearMultiSelection = (): void => {
1253
+ multiSelectedNodeIds.clear();
1254
+ applyMultiSelectionClasses();
1096
1255
  };
1097
1256
  graphView.on('node:mousedown', ({ node, e }: { node: any; e: MouseEvent }) => {
1257
+ const targetCell = selectionTargetForNode(node);
1258
+ if (!targetCell) {
1259
+ return;
1260
+ }
1261
+ const targetId = String(targetCell?.id ?? '');
1262
+ const additive = !!(e.metaKey || e.ctrlKey || e.shiftKey);
1263
+ if (additive) {
1264
+ if (multiSelectedNodeIds.has(targetId)) {
1265
+ multiSelectedNodeIds.delete(targetId);
1266
+ if (primarySelectedNodeId === targetId) {
1267
+ primarySelectedNodeId = multiSelectedNodeIds.values().next().value;
1268
+ }
1269
+ } else if (targetId) {
1270
+ multiSelectedNodeIds.add(targetId);
1271
+ primarySelectedNodeId = targetId;
1272
+ }
1273
+ } else if (targetId) {
1274
+ replaceMultiSelection([targetId]);
1275
+ primarySelectedNodeId = targetId;
1276
+ }
1277
+ applyMultiSelectionClasses();
1278
+ refreshPrimaryWidget();
1098
1279
  if (!isCompartmentNode(node)) {
1099
1280
  return;
1100
1281
  }
1101
- selectCompartmentParent(node);
1102
1282
  e.preventDefault();
1103
1283
  e.stopPropagation();
1104
1284
  if (typeof (e as any).stopImmediatePropagation === 'function') {
@@ -1106,12 +1286,49 @@ async function renderWithX6(
1106
1286
  }
1107
1287
  });
1108
1288
  graphView.on('node:click', ({ node, e }: { node: any; e: MouseEvent }) => {
1109
- if (!isCompartmentNode(node)) {
1289
+ if (isCompartmentNode(node)) {
1290
+ refreshPrimaryWidget();
1291
+ e.preventDefault();
1292
+ e.stopPropagation();
1293
+ if (typeof (e as any).stopImmediatePropagation === 'function') {
1294
+ (e as any).stopImmediatePropagation();
1295
+ }
1296
+ }
1297
+ });
1298
+ graphView.on('node:contextmenu', ({ node, e }: { node: any; e: MouseEvent }) => {
1299
+ const targetCell = selectionTargetForNode(node);
1300
+ const targetId = String(targetCell?.id ?? '');
1301
+ if (!targetId) {
1110
1302
  return;
1111
1303
  }
1112
- selectCompartmentParent(node);
1304
+ if (!multiSelectedNodeIds.has(targetId)) {
1305
+ multiSelectedNodeIds.clear();
1306
+ multiSelectedNodeIds.add(targetId);
1307
+ primarySelectedNodeId = targetId;
1308
+ applyMultiSelectionClasses();
1309
+ refreshPrimaryWidget();
1310
+ }
1311
+ const selectedIris = [...multiSelectedNodeIds];
1312
+ liveCanvas.dispatchEvent(new CustomEvent('md-diagram-selection-contextmenu', {
1313
+ bubbles: true,
1314
+ detail: {
1315
+ iri: targetId,
1316
+ name: displayLabelFromIri(targetId),
1317
+ selectedIris,
1318
+ clientX: e.clientX,
1319
+ clientY: e.clientY,
1320
+ },
1321
+ }));
1113
1322
  e.preventDefault();
1114
1323
  e.stopPropagation();
1324
+ if (typeof (e as any).stopImmediatePropagation === 'function') {
1325
+ (e as any).stopImmediatePropagation();
1326
+ }
1327
+ });
1328
+ graphView.on('blank:mousedown', () => {
1329
+ clearMultiSelection();
1330
+ primarySelectedNodeId = undefined;
1331
+ refreshPrimaryWidget();
1115
1332
  });
1116
1333
 
1117
1334
  const sidePriority = (side: PortSide): number => {
@@ -1339,7 +1556,7 @@ function installDiagramNodeInteractions(canvas: HTMLElement, graphView: any): vo
1339
1556
  bubbles: true,
1340
1557
  detail: {
1341
1558
  iri,
1342
- previewEnabled: !!e && (/^Mac/i.test(navigator.platform) ? e.metaKey : e.ctrlKey),
1559
+ previewEnabled: !!e && e.altKey,
1343
1560
  anchorRect: {
1344
1561
  left: bbox.left,
1345
1562
  right: bbox.right,
@@ -697,7 +697,7 @@ function installGraphNodeInteractions(
697
697
  bubbles: true,
698
698
  detail: {
699
699
  iri,
700
- previewEnabled: !!e && (/^Mac/i.test(navigator.platform) ? e.metaKey : e.ctrlKey),
700
+ previewEnabled: !!e && e.altKey,
701
701
  anchorRect: {
702
702
  left: bbox.left,
703
703
  right: bbox.right,
@@ -739,7 +739,7 @@ function installGraphNodeInteractions(
739
739
  detail: {
740
740
  blockId,
741
741
  blockSource: source,
742
- contextIri: iri.trim(),
742
+ contextMemberIri: iri.trim(),
743
743
  },
744
744
  }));
745
745
  return;
@@ -146,9 +146,11 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
146
146
  cells: visibleColumnIndexes.map((columnIndex) => row.cells[columnIndex] ?? ''),
147
147
  typedCells: visibleColumnIndexes.map((columnIndex) => row.typedCells?.[columnIndex]),
148
148
  }));
149
+ const dedupeVisibleRows = hiddenColumns.size > 0 && !isTree;
150
+ const visibleRows = dedupeVisibleRows ? dedupeRowsByDisplayedCells(rowsWithIndex) : rowsWithIndex;
149
151
  root.__omlAiContext = {
150
152
  columns: visibleColumns.slice(),
151
- rows: rowsWithIndex
153
+ rows: visibleRows
152
154
  .map((row) => ({
153
155
  iri: (row.cells[0] ?? '').trim(),
154
156
  cells: row.cells.slice(),
@@ -156,7 +158,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
156
158
  .filter((row) => row.iri.length > 0),
157
159
  blockSource,
158
160
  };
159
- const columnContexts = createColumnContexts(visibleColumns, rowsWithIndex);
161
+ const columnContexts = createColumnContexts(visibleColumns, visibleRows);
160
162
  const treeModel = isTree
161
163
  ? this.createTreeModel(columns, allRowsWithIndex, visibleColumnIndexes, options)
162
164
  : undefined;
@@ -275,7 +277,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
275
277
  downloadButton.addEventListener('click', () => {
276
278
  const sourceRows = isTree && treeModel
277
279
  ? this.resolveTreeSourceRows(treeModel, visibleColumns, state.expanded, state.search, blockSource, options)
278
- : rowsWithIndex;
280
+ : visibleRows;
279
281
  if (isTree && treeModel) {
280
282
  this.requestTreeJsonDownload(visibleColumns, treeModel, sourceRows);
281
283
  return;
@@ -286,7 +288,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
286
288
  });
287
289
  controlsRight.appendChild(downloadButton);
288
290
 
289
- const uploadButton = this.createUploadCsvButton(visibleColumns, rowsWithIndex, isTree, blockSource);
291
+ const uploadButton = this.createUploadCsvButton(visibleColumns, visibleRows, isTree, blockSource);
290
292
  if (uploadButton) {
291
293
  controlsRight.appendChild(uploadButton);
292
294
  }
@@ -336,7 +338,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
336
338
  if (isTree) {
337
339
  return;
338
340
  }
339
- const filtered = this.applyFiltersAndSorting(visibleColumns, rowsWithIndex, state, blockSource, options);
341
+ const filtered = this.applyFiltersAndSorting(visibleColumns, visibleRows, state, blockSource, options);
340
342
  const totalPages = Math.max(1, Math.ceil(filtered.length / state.pageSize));
341
343
  if (state.page < totalPages - 1) {
342
344
  state.page += 1;
@@ -359,7 +361,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
359
361
  const renderPage = () => {
360
362
  const sourceRows = isTree && treeModel
361
363
  ? this.resolveTreeSourceRows(treeModel, visibleColumns, state.expanded, state.search, blockSource, options)
362
- : rowsWithIndex;
364
+ : visibleRows;
363
365
  const filtered = isTree
364
366
  ? sourceRows.slice()
365
367
  : this.applyFiltersAndSorting(visibleColumns, sourceRows, state, blockSource, options);
@@ -408,7 +410,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
408
410
  state,
409
411
  root,
410
412
  columns: visibleColumns,
411
- rows: fullyExpandedTreeRows ?? rowsWithIndex,
413
+ rows: fullyExpandedTreeRows ?? visibleRows,
412
414
  referenceCell: label,
413
415
  rerender: renderPage,
414
416
  });
@@ -463,7 +465,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
463
465
  this.onRowDoubleClick({
464
466
  root,
465
467
  columns: visibleColumns,
466
- rows: rowsWithIndex.map((row) => ({ index: row.index, cells: row.cells.slice() })),
468
+ rows: visibleRows.map((row) => ({ index: row.index, cells: row.cells.slice() })),
467
469
  row: { index: rowEntry.index, cells: rowEntry.cells.slice() },
468
470
  blockSource,
469
471
  typeOptions: this.extractTypeOptions(blockSource),
@@ -484,7 +486,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
484
486
  this.appendCustomLeftControls(controlsLeft, {
485
487
  root,
486
488
  columns: visibleColumns,
487
- rows: rowsWithIndex.map((row) => ({ index: row.index, cells: row.cells.slice() })),
489
+ rows: visibleRows.map((row) => ({ index: row.index, cells: row.cells.slice() })),
488
490
  blockSource,
489
491
  options,
490
492
  typeOptions: this.extractTypeOptions(blockSource),
@@ -494,7 +496,7 @@ export class TableMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
494
496
  this.scheduleInitialAutosize({
495
497
  root,
496
498
  columns: visibleColumns,
497
- rows: fullyExpandedTreeRows ?? rowsWithIndex,
499
+ rows: fullyExpandedTreeRows ?? visibleRows,
498
500
  state,
499
501
  rerender: renderPage,
500
502
  });
@@ -1209,6 +1211,20 @@ type TableRowData = {
1209
1211
  treeHasChildren?: boolean;
1210
1212
  };
1211
1213
 
1214
+ function dedupeRowsByDisplayedCells(rows: ReadonlyArray<TableRowData>): TableRowData[] {
1215
+ const seen = new Set<string>();
1216
+ const deduped: TableRowData[] = [];
1217
+ for (const row of rows) {
1218
+ const key = row.cells.join('\u0000');
1219
+ if (seen.has(key)) {
1220
+ continue;
1221
+ }
1222
+ seen.add(key);
1223
+ deduped.push(row);
1224
+ }
1225
+ return deduped;
1226
+ }
1227
+
1212
1228
  type TreeModel = {
1213
1229
  rowsById: Map<string, TableRowData>;
1214
1230
  childrenByParent: Map<string, string[]>;