@oml/markdown 0.14.17 → 0.16.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 (38) hide show
  1. package/out/md/md-frontmatter.d.ts +1 -1
  2. package/out/md/md-frontmatter.js +6 -11
  3. package/out/md/md-frontmatter.js.map +1 -1
  4. package/out/md/md-runtime.js +26 -7
  5. package/out/md/md-runtime.js.map +1 -1
  6. package/out/md/md-types.d.ts +2 -2
  7. package/out/renderers/table-renderer.js +12 -4
  8. package/out/renderers/table-renderer.js.map +1 -1
  9. package/out/static/browser-runtime.bundle.js +718 -14
  10. package/out/static/browser-runtime.bundle.js.map +3 -3
  11. package/out/static/browser-runtime.js +748 -1
  12. package/out/static/browser-runtime.js.map +1 -1
  13. package/out/static/runtime-assets.d.ts +1 -1
  14. package/out/static/runtime-assets.js +3 -0
  15. package/out/static/runtime-assets.js.map +1 -1
  16. package/out/template/binder.js +156 -32
  17. package/out/template/binder.js.map +1 -1
  18. package/out/template/compose.d.ts +1 -2
  19. package/out/template/compose.js +7 -41
  20. package/out/template/compose.js.map +1 -1
  21. package/out/template/definition.js +0 -11
  22. package/out/template/definition.js.map +1 -1
  23. package/out/template/engine.js +1 -1
  24. package/out/template/engine.js.map +1 -1
  25. package/out/template/types.d.ts +0 -2
  26. package/package.json +2 -2
  27. package/src/md/md-frontmatter.ts +6 -11
  28. package/src/md/md-runtime.ts +29 -7
  29. package/src/md/md-types.ts +2 -2
  30. package/src/renderers/table-renderer.ts +13 -4
  31. package/src/static/browser-runtime.ts +803 -1
  32. package/src/static/markdown-webview.css +88 -1
  33. package/src/static/runtime-assets.ts +3 -0
  34. package/src/template/binder.ts +165 -35
  35. package/src/template/compose.ts +8 -45
  36. package/src/template/definition.ts +0 -12
  37. package/src/template/engine.ts +1 -1
  38. package/src/template/types.ts +0 -9
@@ -43,10 +43,80 @@ pre > code.language-diagram,
43
43
  pre > code.language-list,
44
44
  pre > code.language-text,
45
45
  pre > code.language-matrix,
46
- pre > code.language-table-editor {
46
+ pre > code.language-table-editor,
47
+ pre > code.language-js,
48
+ pre > code.language-javascript,
49
+ pre > code.language-python,
50
+ pre > code.language-r {
47
51
  display: none;
48
52
  }
49
53
 
54
+ .oml-md-js-result {
55
+ margin: 1rem 0;
56
+ }
57
+
58
+ .oml-md-js-result:empty {
59
+ display: none;
60
+ }
61
+
62
+ pre.oml-md-js-text-output {
63
+ font-family: var(--vscode-editor-font-family, monospace);
64
+ font-size: 0.9em;
65
+ background: var(--vscode-textBlockQuote-background, var(--oml-static-background, #f6f8fa));
66
+ border: 1px solid var(--vscode-editorWidget-border, var(--oml-static-border, #d0d7de));
67
+ border-radius: 6px !important;
68
+ padding: 0.5rem 0.75rem !important;
69
+ margin: 0.25rem 0;
70
+ white-space: pre-wrap;
71
+ overflow: visible !important;
72
+ color: var(--vscode-editor-foreground, var(--oml-static-foreground, #24292f));
73
+ }
74
+
75
+ .oml-md-js-html-output {
76
+ margin: 0.25rem 0;
77
+ color: var(--vscode-editor-foreground, var(--oml-static-foreground, #24292f));
78
+ }
79
+
80
+ .oml-md-js-html-output ul,
81
+ .oml-md-js-html-output ol {
82
+ padding-left: 2em;
83
+ }
84
+
85
+ .oml-md-js-html-output li {
86
+ color: var(--vscode-editor-foreground, var(--oml-static-foreground, #24292f));
87
+ }
88
+
89
+ .oml-md-js-error {
90
+ border: 1px solid var(--vscode-inputValidation-errorBorder, #f14c4c);
91
+ background: var(--vscode-inputValidation-errorBackground, rgba(241, 76, 76, 0.1));
92
+ color: var(--vscode-errorForeground, #f14c4c);
93
+ border-radius: 6px;
94
+ padding: 0.5rem 0.75rem;
95
+ margin: 0.25rem 0;
96
+ font-family: var(--vscode-editor-font-family, monospace);
97
+ font-size: 0.9em;
98
+ white-space: pre-wrap;
99
+ }
100
+
101
+ .oml-md-js-empty {
102
+ color: var(--vscode-descriptionForeground, #666);
103
+ font-style: italic;
104
+ margin: 0.25rem 0;
105
+ }
106
+
107
+ pre.oml-md-js-stderr {
108
+ font-family: var(--vscode-editor-font-family, monospace);
109
+ font-size: 0.85em;
110
+ color: var(--vscode-descriptionForeground, #888);
111
+ background: transparent;
112
+ border: none;
113
+ border-radius: 0 !important;
114
+ padding: 0 !important;
115
+ margin: 0;
116
+ white-space: pre-wrap;
117
+ overflow: visible !important;
118
+ }
119
+
50
120
  h1,
51
121
  h2,
52
122
  h3,
@@ -349,6 +419,7 @@ pre {
349
419
  text-align: left;
350
420
  font-weight: 600;
351
421
  border-bottom: 1px solid var(--vscode-editorWidget-border);
422
+ border-right: 1px solid var(--vscode-editorWidget-border);
352
423
  padding: 8px 10px;
353
424
  background: var(--vscode-editor-background);
354
425
  cursor: pointer;
@@ -358,12 +429,21 @@ pre {
358
429
  user-select: none;
359
430
  }
360
431
 
432
+ .oml-md-table thead th:last-child {
433
+ border-right: none;
434
+ }
435
+
361
436
  .oml-md-table tbody td {
362
437
  border-bottom: 1px solid var(--vscode-editorWidget-border);
438
+ border-right: 1px solid var(--vscode-editorWidget-border);
363
439
  padding: 6px 10px;
364
440
  vertical-align: top;
365
441
  }
366
442
 
443
+ .oml-md-table tbody td:last-child {
444
+ border-right: none;
445
+ }
446
+
367
447
  .vscode-dark th {
368
448
  border-color: rgb(255 255 255 / 69%);
369
449
  }
@@ -726,6 +806,8 @@ pre {
726
806
  align-items: center;
727
807
  gap: 0.5rem;
728
808
  margin-left: auto;
809
+ font-family: var(--vscode-font-family, var(--markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", system-ui, "Ubuntu", "Droid Sans", sans-serif));
810
+ font-size: 0.85rem;
729
811
  }
730
812
 
731
813
  .oml-md-result .table-pagination {
@@ -745,6 +827,7 @@ pre {
745
827
  background: transparent;
746
828
  color: var(--vscode-foreground);
747
829
  cursor: pointer;
830
+ font-family: inherit;
748
831
  font-size: 1rem;
749
832
  font-weight: 600;
750
833
  line-height: 1;
@@ -764,6 +847,10 @@ pre {
764
847
  .oml-md-result .table-page-label {
765
848
  min-width: 36px;
766
849
  text-align: center;
850
+ font-family: inherit;
851
+ font-size: inherit;
852
+ font-weight: 400;
853
+ line-height: 1.2;
767
854
  }
768
855
 
769
856
  .oml-md-result .graph-canvas-root {
@@ -3,6 +3,9 @@
3
3
  export const STATIC_MARKDOWN_RUNTIME_BUNDLE_FILE = 'browser-runtime.bundle.js';
4
4
 
5
5
  export const STATIC_MARKDOWN_RUNTIME_CSS = `
6
+ /* Prevent first-paint flicker before static runtime hydration restores content. */
7
+ body { visibility: hidden; }
8
+
6
9
  .oml-md-result { margin: 1rem 0; }
7
10
  .oml-md-result-message {
8
11
  border: 1px solid #e5b567;
@@ -13,58 +13,188 @@ export function bindTemplateParameters(
13
13
  for (const parameter of parameters) {
14
14
  const explicit = explicitArgs[parameter.id];
15
15
  if (explicit !== undefined) {
16
- values[parameter.id] = explicit;
16
+ values[parameter.id] = coerceParameterValue(parameter, explicit);
17
17
  continue;
18
18
  }
19
- const bound = resolveValueFromContext(parameter, context);
20
- if (bound !== undefined) {
21
- values[parameter.id] = bound;
22
- continue;
23
- }
24
- if (parameter.defaultValue !== undefined) {
25
- values[parameter.id] = parameter.defaultValue;
19
+ const defaultValue = resolveTemplateDefaultValue(parameter.defaultValue, context);
20
+ if (defaultValue !== undefined) {
21
+ values[parameter.id] = coerceParameterValue(parameter, defaultValue);
26
22
  continue;
27
23
  }
28
24
  if (parameter.required) {
29
25
  missingRequired.push(parameter.id);
26
+ continue;
30
27
  }
28
+ values[parameter.id] = naturalDefaultValue(parameter.type);
31
29
  }
32
30
  return { values, missingRequired };
33
31
  }
34
32
 
35
- function resolveValueFromContext(
36
- parameter: TemplateParameterDefinition,
33
+ function resolveTemplateDefaultValue(
34
+ value: TemplateValue | undefined,
37
35
  context: TemplateInvocationContext
38
36
  ): TemplateValue | undefined {
39
- switch (parameter.from) {
40
- case 'context.member':
41
- return context.focus?.memberIri;
42
- case 'context.ontology':
43
- return context.model?.ontologyIri;
44
- case 'context.modelUri':
45
- return context.model?.modelUri;
46
- case 'context.selection[*]':
47
- return context.selection?.iris ?? [];
48
- default:
49
- return resolveSelectionIndexedBinding(parameter.from, context);
37
+ if (value === undefined) {
38
+ return undefined;
39
+ }
40
+ if (typeof value !== 'string') {
41
+ return value;
50
42
  }
43
+ return interpolateContextExpression(value, context);
51
44
  }
52
45
 
53
- function resolveSelectionIndexedBinding(
54
- source: TemplateParameterDefinition['from'],
55
- context: TemplateInvocationContext
56
- ): TemplateValue | undefined {
57
- if (!source || !source.startsWith('context.selection[')) {
58
- return undefined;
46
+ function interpolateContextExpression(source: string, context: TemplateInvocationContext): string | undefined {
47
+ let unresolved = false;
48
+ const output = source.replace(/\$\{context\.([^}]+)\}/g, (_match, token: string) => {
49
+ const resolved = resolveContextToken(token.trim(), context);
50
+ if (resolved === undefined) {
51
+ unresolved = true;
52
+ return '';
53
+ }
54
+ return resolved;
55
+ });
56
+ return unresolved ? undefined : output;
57
+ }
58
+
59
+ function resolveContextToken(token: string, context: TemplateInvocationContext): string | undefined {
60
+ const indexedSelection = /^selection\[(\d+)]$/.exec(token);
61
+ if (indexedSelection) {
62
+ const index = Number.parseInt(indexedSelection[1] ?? '', 10);
63
+ if (!Number.isFinite(index) || index < 0) {
64
+ return undefined;
65
+ }
66
+ const selection = context.selection?.iris ?? [];
67
+ const value = selection[index];
68
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
59
69
  }
60
- const match = /^context\.selection\[(\d+)]$/.exec(source);
61
- if (!match) {
62
- return undefined;
70
+ const key = token;
71
+ const vars = context.vars ?? {};
72
+ const direct = vars[key];
73
+ if (typeof direct === 'string') {
74
+ return direct;
63
75
  }
64
- const index = Number.parseInt(match[1] ?? '', 10);
65
- if (!Number.isFinite(index) || index < 0) {
66
- return undefined;
76
+ if (typeof direct === 'number' || typeof direct === 'boolean') {
77
+ return String(direct);
78
+ }
79
+ if (key === 'ontology') {
80
+ return context.model?.ontologyIri?.trim() || undefined;
81
+ }
82
+ if (key === 'member') {
83
+ return context.focus?.memberIri?.trim() || undefined;
84
+ }
85
+ if (key === 'selection') {
86
+ const selection = context.selection?.iris ?? [];
87
+ return selection.join(', ');
88
+ }
89
+ return undefined;
90
+ }
91
+
92
+ function coerceParameterValue(parameter: TemplateParameterDefinition, value: TemplateValue): TemplateValue {
93
+ switch (parameter.type) {
94
+ case 'string':
95
+ case 'iri':
96
+ return coerceString(parameter, value);
97
+ case 'number':
98
+ return coerceNumber(parameter, value);
99
+ case 'boolean':
100
+ return coerceBoolean(parameter, value);
101
+ case 'iri[]':
102
+ return coerceIriArray(parameter, value);
103
+ case 'json':
104
+ return coerceJson(parameter, value);
105
+ }
106
+ }
107
+
108
+ function coerceString(parameter: TemplateParameterDefinition, value: TemplateValue): string {
109
+ if (typeof value === 'string') {
110
+ return value;
111
+ }
112
+ if (typeof value === 'number' || typeof value === 'boolean') {
113
+ return String(value);
114
+ }
115
+ throw new Error(`Template parameter '${parameter.id}' must be a string.`);
116
+ }
117
+
118
+ function coerceNumber(parameter: TemplateParameterDefinition, value: TemplateValue): number {
119
+ if (typeof value === 'number' && Number.isFinite(value)) {
120
+ return value;
121
+ }
122
+ if (typeof value === 'string') {
123
+ const parsed = Number(value.trim());
124
+ if (Number.isFinite(parsed)) {
125
+ return parsed;
126
+ }
127
+ }
128
+ throw new Error(`Template parameter '${parameter.id}' must be a number.`);
129
+ }
130
+
131
+ function coerceBoolean(parameter: TemplateParameterDefinition, value: TemplateValue): boolean {
132
+ if (typeof value === 'boolean') {
133
+ return value;
134
+ }
135
+ if (typeof value === 'string') {
136
+ const normalized = value.trim().toLowerCase();
137
+ if (normalized === 'true') {
138
+ return true;
139
+ }
140
+ if (normalized === 'false') {
141
+ return false;
142
+ }
143
+ }
144
+ throw new Error(`Template parameter '${parameter.id}' must be a boolean.`);
145
+ }
146
+
147
+ function coerceIriArray(parameter: TemplateParameterDefinition, value: TemplateValue): string[] {
148
+ if (Array.isArray(value) && value.every((entry) => typeof entry === 'string')) {
149
+ return value;
150
+ }
151
+ if (typeof value === 'string') {
152
+ const normalized = value.trim();
153
+ if (!normalized) {
154
+ return [];
155
+ }
156
+ return normalized.split(',').map((entry) => entry.trim()).filter((entry) => entry.length > 0);
157
+ }
158
+ throw new Error(`Template parameter '${parameter.id}' must be an array of strings.`);
159
+ }
160
+
161
+ function coerceJson(parameter: TemplateParameterDefinition, value: TemplateValue): TemplateValue {
162
+ if (value === null || typeof value === 'object') {
163
+ return value;
164
+ }
165
+ if (typeof value === 'string') {
166
+ try {
167
+ const parsed = JSON.parse(value) as unknown;
168
+ if (
169
+ parsed === null
170
+ || typeof parsed === 'string'
171
+ || typeof parsed === 'number'
172
+ || typeof parsed === 'boolean'
173
+ || (Array.isArray(parsed) && parsed.every((entry) => typeof entry === 'string'))
174
+ || (typeof parsed === 'object' && parsed !== null)
175
+ ) {
176
+ return parsed as TemplateValue;
177
+ }
178
+ throw new Error('unsupported-json');
179
+ } catch {
180
+ throw new Error(`Template parameter '${parameter.id}' must be valid JSON.`);
181
+ }
182
+ }
183
+ throw new Error(`Template parameter '${parameter.id}' must be JSON.`);
184
+ }
185
+
186
+ function naturalDefaultValue(type: TemplateParameterDefinition['type']): TemplateValue {
187
+ switch (type) {
188
+ case 'number':
189
+ return 0;
190
+ case 'boolean':
191
+ return false;
192
+ case 'iri[]':
193
+ return [];
194
+ case 'json':
195
+ return {};
196
+ case 'string':
197
+ case 'iri':
198
+ return '';
67
199
  }
68
- const iris = context.selection?.iris ?? [];
69
- return iris[index];
70
200
  }
@@ -1,48 +1,36 @@
1
1
  // Copyright (c) 2026 Modelware. All rights reserved.
2
2
 
3
3
  import { parse } from 'yaml';
4
- import type { TemplateBindingSource, TemplateValue } from './types.js';
4
+ import type { TemplateValue } from './types.js';
5
5
 
6
6
  export interface TemplateComposeDirective {
7
7
  id: string;
8
8
  args: Record<string, TemplateValue>;
9
- bind: Record<string, TemplateBindingSource>;
10
9
  }
11
10
 
12
11
  export function parseTemplateComposeDirective(source: string): TemplateComposeDirective | undefined {
13
12
  const normalized = source.replace(/^\uFEFF/, '');
14
- const frontMatterMatch = /^(---[ \t]*\r?\n[\s\S]*?\r?\n---[ \t]*)([\s\S]*)$/.exec(normalized);
15
- if (!frontMatterMatch) {
16
- return undefined;
17
- }
18
- const trailing = frontMatterMatch[2] ?? '';
19
- if (trailing.trim().length > 0) {
20
- return undefined;
21
- }
22
- const yamlBody = frontMatterMatch[1]
23
- .replace(/^---[ \t]*\r?\n/, '')
24
- .replace(/\r?\n---[ \t]*$/, '');
25
- const parsed = parse(yamlBody);
13
+ const parsed = parse(normalized);
26
14
  if (!isRecord(parsed)) {
27
15
  return undefined;
28
16
  }
29
- const id = normalizeString(parsed.id);
17
+ const id = normalizeString(parsed.template);
30
18
  if (!id) {
31
19
  return undefined;
32
20
  }
33
- const args = parseArgs(parsed.args);
34
- const bind = parseBind(parsed.bind);
35
- return { id, args, bind };
21
+ const args = parseArgs(parsed, ['template']);
22
+ return { id, args };
36
23
  }
37
24
 
38
- function parseArgs(value: unknown): Record<string, TemplateValue> {
25
+ function parseArgs(value: unknown, excludedKeys: string[] = []): Record<string, TemplateValue> {
39
26
  if (!isRecord(value)) {
40
27
  return {};
41
28
  }
29
+ const excluded = new Set(excludedKeys);
42
30
  const args: Record<string, TemplateValue> = {};
43
31
  for (const [key, raw] of Object.entries(value)) {
44
32
  const normalizedKey = normalizeString(key);
45
- if (!normalizedKey) {
33
+ if (!normalizedKey || excluded.has(normalizedKey)) {
46
34
  continue;
47
35
  }
48
36
  const converted = toTemplateValue(raw);
@@ -53,22 +41,6 @@ function parseArgs(value: unknown): Record<string, TemplateValue> {
53
41
  return args;
54
42
  }
55
43
 
56
- function parseBind(value: unknown): Record<string, TemplateBindingSource> {
57
- if (!isRecord(value)) {
58
- return {};
59
- }
60
- const bind: Record<string, TemplateBindingSource> = {};
61
- for (const [key, raw] of Object.entries(value)) {
62
- const normalizedKey = normalizeString(key);
63
- const source = normalizeString(raw);
64
- if (!normalizedKey || !source || !isTemplateBindingSource(source)) {
65
- continue;
66
- }
67
- bind[normalizedKey] = source;
68
- }
69
- return bind;
70
- }
71
-
72
44
  function toTemplateValue(value: unknown): TemplateValue | undefined {
73
45
  if (value === null) {
74
46
  return null;
@@ -85,15 +57,6 @@ function toTemplateValue(value: unknown): TemplateValue | undefined {
85
57
  return undefined;
86
58
  }
87
59
 
88
- function isTemplateBindingSource(value: string): value is TemplateBindingSource {
89
- return value === 'context.member'
90
- || value === 'context.ontology'
91
- || value === 'context.modelUri'
92
- || value === 'context.selection[*]'
93
- || value === 'user'
94
- || /^context\.selection\[\d+]$/.test(value);
95
- }
96
-
97
60
  function normalizeString(value: unknown): string | undefined {
98
61
  if (typeof value !== 'string') {
99
62
  return undefined;
@@ -117,8 +117,6 @@ function parseTemplateParameters(raw: unknown): TemplateParameterDefinition[] {
117
117
  continue;
118
118
  }
119
119
  const required = typeof entry.required === 'boolean' ? entry.required : undefined;
120
- const fromValue = typeof entry.from === 'string' ? entry.from.trim() : '';
121
- const from = isSupportedBindingSource(fromValue) ? fromValue : undefined;
122
120
  const description = typeof entry.description === 'string' && entry.description.trim().length > 0
123
121
  ? entry.description.trim()
124
122
  : undefined;
@@ -126,7 +124,6 @@ function parseTemplateParameters(raw: unknown): TemplateParameterDefinition[] {
126
124
  id,
127
125
  type,
128
126
  required,
129
- from,
130
127
  defaultValue: toTemplateValue(entry.defaultValue),
131
128
  description,
132
129
  });
@@ -143,15 +140,6 @@ function isSupportedParameterType(value: string): value is TemplateParameterDefi
143
140
  || value === 'json';
144
141
  }
145
142
 
146
- function isSupportedBindingSource(value: string): value is NonNullable<TemplateParameterDefinition['from']> {
147
- return value === 'context.member'
148
- || value === 'context.ontology'
149
- || value === 'context.modelUri'
150
- || value === 'context.selection[*]'
151
- || /^context\.selection\[\d+]$/.test(value)
152
- || value === 'user';
153
- }
154
-
155
143
  function toTemplateValue(value: unknown): TemplateValue | undefined {
156
144
  if (value === null) {
157
145
  return null;
@@ -28,7 +28,7 @@ export function interpolateTemplateBody(
28
28
  ): string {
29
29
  return source.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_match: string, key: string): string => {
30
30
  if (!(key in values)) {
31
- return '';
31
+ return _match;
32
32
  }
33
33
  const value = values[key];
34
34
  if (Array.isArray(value)) {
@@ -10,19 +10,10 @@ export type TemplateParameterType =
10
10
  | 'iri[]'
11
11
  | 'json';
12
12
 
13
- export type TemplateBindingSource =
14
- | 'context.member'
15
- | 'context.ontology'
16
- | 'context.modelUri'
17
- | `context.selection[${number}]`
18
- | 'context.selection[*]'
19
- | 'user';
20
-
21
13
  export interface TemplateParameterDefinition {
22
14
  id: string;
23
15
  type: TemplateParameterType;
24
16
  required?: boolean;
25
- from?: TemplateBindingSource;
26
17
  defaultValue?: TemplateValue;
27
18
  description?: string;
28
19
  }