From 036d221a8054fccdebee9758d98c731cae06dc97 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:11:27 +0000 Subject: [PATCH 01/47] Initial plan From e05d3affb39bc567be1549bbb2558271a31c3299 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:24:50 +0000 Subject: [PATCH 02/47] Add JSON plugin for inline JSON data Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- docs/dist/v1/chartifact.markdown.umd.js | 234 ++++++++++++++---- packages/markdown/src/plugins/index.ts | 2 + packages/markdown/src/plugins/interfaces.ts | 2 + packages/markdown/src/plugins/json.ts | 195 +++++++++++++++ .../features/2.4.inline-csv-data.idoc.json | 18 ++ .../json/features/json-plugin-test.idoc.json | 29 +++ 6 files changed, 434 insertions(+), 46 deletions(-) create mode 100644 packages/markdown/src/plugins/json.ts create mode 100644 packages/web-deploy/json/features/json-plugin-test.idoc.json diff --git a/docs/dist/v1/chartifact.markdown.umd.js b/docs/dist/v1/chartifact.markdown.umd.js index ef28a70b..c28f3d0b 100644 --- a/docs/dist/v1/chartifact.markdown.umd.js +++ b/docs/dist/v1/chartifact.markdown.umd.js @@ -628,10 +628,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy loading: "0.1", error: "0.5" }; - const pluginName$g = "image"; - const className$e = pluginClassName(pluginName$g); + const pluginName$h = "image"; + const className$f = pluginClassName(pluginName$h); const imagePlugin = { - ...flaggablePlugin(pluginName$g, className$e), + ...flaggablePlugin(pluginName$h, className$f), hydrateComponent: async (renderer, errorHandler, specs) => { const imageInstances = []; for (let index2 = 0; index2 < specs.length; index2++) { @@ -646,11 +646,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy container, null, (error) => { - errorHandler(error, pluginName$g, index2, "load", container, img.src); + errorHandler(error, pluginName$h, index2, "load", container, img.src); } ); const imageInstance = { - id: `${pluginName$g}-${index2}`, + id: `${pluginName$h}-${index2}`, spec, img: null, // Will be set below @@ -708,7 +708,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy if (isSafeImageUrl(src)) { tempImg.setAttribute("src", src); } else { - errorHandler(new Error(`Unsafe image URL: ${src}`), pluginName$g, instanceIndex, "load", null, src); + errorHandler(new Error(`Unsafe image URL: ${src}`), pluginName$h, instanceIndex, "load", null, src); } } tempImg.setAttribute("alt", alt); @@ -827,10 +827,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy } return null; } - const pluginName$f = "placeholders"; - const imageClassName = pluginClassName(pluginName$f + "_image"); + const pluginName$g = "placeholders"; + const imageClassName = pluginClassName(pluginName$g + "_image"); const placeholdersPlugin = { - name: pluginName$f, + name: pluginName$g, initializePlugin: async (md) => { md.use(function(md2) { md2.inline.ruler.after("emphasis", "dynamic_placeholder", function(state, silent) { @@ -951,7 +951,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy for (const element of Array.from(dynamicImages)) { const { dynamicUrl, img } = createImageLoadingLogic(element, null, (error) => { const index2 = -1; - errorHandler(error, pluginName$f, index2, "load", element, img.src); + errorHandler(error, pluginName$g, index2, "load", element, img.src); }); if (!dynamicUrl) { continue; @@ -976,7 +976,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy }); const instances = [ { - id: pluginName$f, + id: pluginName$g, initialSignals, receiveBatch: async (batch) => { var _a, _b; @@ -1099,9 +1099,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy } if (info.startsWith("json ")) { const jsonPluginName = info.slice(5).trim(); - const jsonPlugin = findPlugin(jsonPluginName); - if (jsonPlugin) { - return jsonPlugin; + const jsonPlugin2 = findPlugin(jsonPluginName); + if (jsonPlugin2) { + return jsonPlugin2; } } else if (info.startsWith("yaml ")) { const yamlPluginName = info.slice(5).trim(); @@ -1123,10 +1123,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy }; return md; } - const pluginName$e = "checkbox"; - const className$d = pluginClassName(pluginName$e); + const pluginName$f = "checkbox"; + const className$e = pluginClassName(pluginName$f); const checkboxPlugin = { - ...flaggablePlugin(pluginName$e, className$d), + ...flaggablePlugin(pluginName$f, className$e), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const checkboxInstances = []; @@ -1147,7 +1147,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy `; container.innerHTML = html; const element = container.querySelector('input[type="checkbox"]'); - const checkboxInstance = { id: `${pluginName$e}-${index2}`, spec, element }; + const checkboxInstance = { id: `${pluginName$f}-${index2}`, spec, element }; checkboxInstances.push(checkboxInstance); } const instances = checkboxInstances.map((checkboxInstance) => { @@ -1190,9 +1190,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy return instances; } }; - const pluginName$d = "#"; + const pluginName$e = "#"; const commentPlugin = { - name: pluginName$d, + name: pluginName$e, fence: (token) => { const content = token.content.trim(); return sanitizeHtmlComment(content); @@ -1421,14 +1421,14 @@ ${reconstitutedRules.join("\n\n")} } return result; } - const pluginName$c = "css"; - const className$c = pluginClassName(pluginName$c); + const pluginName$d = "css"; + const className$d = pluginClassName(pluginName$d); const cssPlugin = { - ...flaggablePlugin(pluginName$c, className$c), + ...flaggablePlugin(pluginName$d, className$d), fence: (token, index2) => { const cssContent = token.content.trim(); const categorizedCss = categorizeCss(cssContent); - return sanitizedHTML("div", { id: `${pluginName$c}-${index2}`, class: className$c }, JSON.stringify(categorizedCss), true); + return sanitizedHTML("div", { id: `${pluginName$d}-${index2}`, class: className$d }, JSON.stringify(categorizedCss), true); }, hydrateComponent: async (renderer, errorHandler, specs) => { const cssInstances = []; @@ -1451,7 +1451,7 @@ ${reconstitutedRules.join("\n\n")} target.appendChild(styleElement); comments.push(``); cssInstances.push({ - id: `${pluginName$c}-${index2}`, + id: `${pluginName$d}-${index2}`, element: styleElement }); } else { @@ -1528,18 +1528,18 @@ ${reconstitutedRules.join("\n\n")} } return result; } - const pluginName$b = "dsv"; - const className$b = pluginClassName(pluginName$b); + const pluginName$c = "dsv"; + const className$c = pluginClassName(pluginName$c); const dsvPlugin = { - name: pluginName$b, + name: pluginName$c, fence: (token, index2) => { const content = token.content.trim(); const info = token.info.trim(); const { delimiter, wasDefaultDelimiter } = parseDelimiter(info); const { variableId, wasDefaultId } = parseVariableId(info, "dsv", index2); return sanitizedHTML("pre", { - id: `${pluginName$b}-${index2}`, - class: className$b, + id: `${pluginName$c}-${index2}`, + class: className$c, style: "display:none", "data-variable-id": variableId, "data-delimiter": delimiter, @@ -1550,7 +1550,7 @@ ${reconstitutedRules.join("\n\n")} hydrateSpecs: (renderer, errorHandler) => { var _a; const flagged = []; - const containers = renderer.element.querySelectorAll(`.${className$b}`); + const containers = renderer.element.querySelectorAll(`.${className$c}`); for (const [index2, container] of Array.from(containers).entries()) { try { const variableId = container.getAttribute("data-variable-id"); @@ -1558,18 +1558,18 @@ ${reconstitutedRules.join("\n\n")} const wasDefaultId = container.getAttribute("data-was-default-id") === "true"; const wasDefaultDelimiter = container.getAttribute("data-was-default-delimiter") === "true"; if (!variableId) { - errorHandler(new Error("No variable ID found"), pluginName$b, index2, "parse", container); + errorHandler(new Error("No variable ID found"), pluginName$c, index2, "parse", container); continue; } if (!delimiter) { - errorHandler(new Error("No delimiter found"), pluginName$b, index2, "parse", container); + errorHandler(new Error("No delimiter found"), pluginName$c, index2, "parse", container); continue; } const spec = { variableId, delimiter, wasDefaultId, wasDefaultDelimiter }; const flaggableSpec = inspectDsvSpec(spec); const f = { approvedSpec: null, - pluginName: pluginName$b, + pluginName: pluginName$c, containerId: container.id }; if (flaggableSpec.hasFlags) { @@ -1580,7 +1580,7 @@ ${reconstitutedRules.join("\n\n")} } flagged.push(f); } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$c, index2, "parse", container); } } return flagged; @@ -1596,19 +1596,19 @@ ${reconstitutedRules.join("\n\n")} } const container = renderer.element.querySelector(`#${specReview.containerId}`); if (!container) { - errorHandler(new Error("Container not found"), pluginName$b, index2, "init", null); + errorHandler(new Error("Container not found"), pluginName$c, index2, "init", null); continue; } try { const content = (_a = container.textContent) == null ? void 0 : _a.trim(); if (!content) { - errorHandler(new Error("No DSV content found"), pluginName$b, index2, "parse", container); + errorHandler(new Error("No DSV content found"), pluginName$c, index2, "parse", container); continue; } const spec = specReview.approvedSpec; const data = vega.read(content, { type: "dsv", delimiter: spec.delimiter }); const dsvInstance = { - id: `${pluginName$b}-${index2}`, + id: `${pluginName$c}-${index2}`, spec, data }; @@ -1617,7 +1617,7 @@ ${reconstitutedRules.join("\n\n")} const comment = sanitizeHtmlComment(`${delimiterName} data loaded: ${data.length} rows for variable '${spec.variableId}'`); container.insertAdjacentHTML("beforebegin", comment); } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$c, index2, "parse", container); } } const instances = dsvInstances.map((dsvInstance) => { @@ -1735,8 +1735,8 @@ ${reconstitutedRules.join("\n\n")} generateRule("hero", "h1"); return cssRules.join("\n\n"); } - const pluginName$a = "google-fonts"; - const className$a = pluginClassName(pluginName$a); + const pluginName$b = "google-fonts"; + const className$b = pluginClassName(pluginName$b); function inspectGoogleFontsSpec(spec) { var _a, _b; const reasons = []; @@ -1765,7 +1765,7 @@ ${reconstitutedRules.join("\n\n")} }; } const googleFontsPlugin = { - ...flaggablePlugin(pluginName$a, className$a, inspectGoogleFontsSpec), + ...flaggablePlugin(pluginName$b, className$b, inspectGoogleFontsSpec), hydrateComponent: async (renderer, errorHandler, specs) => { const googleFontsInstances = []; let emitted = false; @@ -1836,10 +1836,10 @@ ${reconstitutedRules.join("\n\n")} return instances; } }; - const pluginName$9 = "dropdown"; - const className$9 = pluginClassName(pluginName$9); + const pluginName$a = "dropdown"; + const className$a = pluginClassName(pluginName$a); const dropdownPlugin = { - ...flaggablePlugin(pluginName$9, className$9), + ...flaggablePlugin(pluginName$a, className$a), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const dropdownInstances = []; @@ -1862,7 +1862,7 @@ ${reconstitutedRules.join("\n\n")} container.innerHTML = html; const element = container.querySelector("select"); setSelectOptions(element, spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.multiple ? [] : "")); - const dropdownInstance = { id: `${pluginName$9}-${index2}`, spec, element }; + const dropdownInstance = { id: `${pluginName$a}-${index2}`, spec, element }; dropdownInstances.push(dropdownInstance); } const instances = dropdownInstances.map((dropdownInstance, index2) => { @@ -1986,6 +1986,147 @@ ${reconstitutedRules.join("\n\n")} selectElement.appendChild(optionElement); }); } + function inspectJsonSpec(spec) { + const result = { + spec, + hasFlags: false, + reasons: [] + }; + if (spec.wasDefaultId) { + result.hasFlags = true; + result.reasons.push("No variable ID specified - using default"); + } + return result; + } + const pluginName$9 = "json"; + const className$9 = pluginClassName(pluginName$9); + const jsonPlugin = { + name: pluginName$9, + fence: (token, index2) => { + const content = token.content.trim(); + const info = token.info.trim(); + const { variableId, wasDefaultId } = parseVariableId(info, "json", index2); + return sanitizedHTML("pre", { + id: `${pluginName$9}-${index2}`, + class: className$9, + style: "display:none", + "data-variable-id": variableId, + "data-was-default-id": wasDefaultId.toString() + }, content, false); + }, + hydrateSpecs: (renderer, errorHandler) => { + var _a; + const flagged = []; + const containers = renderer.element.querySelectorAll(`.${className$9}`); + for (const [index2, container] of Array.from(containers).entries()) { + try { + const variableId = container.getAttribute("data-variable-id"); + const wasDefaultId = container.getAttribute("data-was-default-id") === "true"; + if (!variableId) { + errorHandler(new Error("No variable ID found"), pluginName$9, index2, "parse", container); + continue; + } + const spec = { variableId, wasDefaultId }; + const flaggableSpec = inspectJsonSpec(spec); + const f = { + approvedSpec: null, + pluginName: pluginName$9, + containerId: container.id + }; + if (flaggableSpec.hasFlags) { + f.blockedSpec = flaggableSpec.spec; + f.reason = ((_a = flaggableSpec.reasons) == null ? void 0 : _a.join(", ")) || "Unknown reason"; + } else { + f.approvedSpec = flaggableSpec.spec; + } + flagged.push(f); + } catch (e) { + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$9, index2, "parse", container); + } + } + return flagged; + }, + hydrateComponent: async (renderer, errorHandler, specs) => { + var _a; + const { signalBus } = renderer; + const jsonInstances = []; + for (let index2 = 0; index2 < specs.length; index2++) { + const specReview = specs[index2]; + if (!specReview.approvedSpec) { + continue; + } + const container = renderer.element.querySelector(`#${specReview.containerId}`); + if (!container) { + errorHandler(new Error("Container not found"), pluginName$9, index2, "init", null); + continue; + } + try { + const content = (_a = container.textContent) == null ? void 0 : _a.trim(); + if (!content) { + errorHandler(new Error("No JSON content found"), pluginName$9, index2, "parse", container); + continue; + } + const spec = specReview.approvedSpec; + let data; + try { + const parsed = JSON.parse(content); + if (Array.isArray(parsed)) { + data = parsed; + } else { + data = [parsed]; + } + } catch (jsonError) { + errorHandler( + new Error(`Invalid JSON: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`), + pluginName$9, + index2, + "parse", + container + ); + continue; + } + const jsonInstance = { + id: `${pluginName$9}-${index2}`, + spec, + data + }; + jsonInstances.push(jsonInstance); + const comment = sanitizeHtmlComment(`JSON data loaded: ${data.length} rows for variable '${spec.variableId}'`); + container.insertAdjacentHTML("beforebegin", comment); + } catch (e) { + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$9, index2, "parse", container); + } + } + const instances = jsonInstances.map((jsonInstance) => { + const { spec, data } = jsonInstance; + const initialSignals = [{ + name: spec.variableId, + value: data, + priority: 1, + isData: true + }]; + return { + ...jsonInstance, + initialSignals, + beginListening() { + const batch = { + [spec.variableId]: { + value: data, + isData: true + } + }; + signalBus.broadcast(jsonInstance.id, batch); + }, + getCurrentSignalValue: () => { + return data; + }, + destroy: () => { + } + }; + }); + return instances; + } + }; const pluginName$8 = "mermaid"; const className$8 = pluginClassName(pluginName$8); function inspectMermaidSpec(spec) { @@ -3918,6 +4059,7 @@ ${reconstitutedRules.join("\n\n")} registerMarkdownPlugin(googleFontsPlugin); registerMarkdownPlugin(dropdownPlugin); registerMarkdownPlugin(imagePlugin); + registerMarkdownPlugin(jsonPlugin); registerMarkdownPlugin(mermaidPlugin); registerMarkdownPlugin(numberPlugin); registerMarkdownPlugin(placeholdersPlugin); diff --git a/packages/markdown/src/plugins/index.ts b/packages/markdown/src/plugins/index.ts index 9071173e..4c2ba8e3 100644 --- a/packages/markdown/src/plugins/index.ts +++ b/packages/markdown/src/plugins/index.ts @@ -13,6 +13,7 @@ import { dsvPlugin } from './dsv.js'; import { googleFontsPlugin } from './google-fonts.js'; import { dropdownPlugin } from './dropdown.js'; import { imagePlugin } from './image.js'; +import { jsonPlugin } from './json.js'; import { mermaidPlugin } from './mermaid.js'; import { numberPlugin } from './number.js'; import { placeholdersPlugin } from './placeholders.js'; @@ -34,6 +35,7 @@ export function registerNativePlugins() { registerMarkdownPlugin(googleFontsPlugin); registerMarkdownPlugin(dropdownPlugin); registerMarkdownPlugin(imagePlugin); + registerMarkdownPlugin(jsonPlugin); registerMarkdownPlugin(mermaidPlugin); registerMarkdownPlugin(numberPlugin); registerMarkdownPlugin(placeholdersPlugin); diff --git a/packages/markdown/src/plugins/interfaces.ts b/packages/markdown/src/plugins/interfaces.ts index ab81a30e..c175ec96 100644 --- a/packages/markdown/src/plugins/interfaces.ts +++ b/packages/markdown/src/plugins/interfaces.ts @@ -7,6 +7,7 @@ export { CsvSpec } from './csv.js'; export { DropdownSpec } from './dropdown.js'; export { DsvSpec } from './dsv.js'; export { ImageSpec } from './image.js'; +export { JsonSpec } from './json.js'; export { MermaidSpec } from './mermaid.js'; export { NumberSpec } from './number.js'; export { PresetsSpec } from './presets.js'; @@ -25,6 +26,7 @@ export type PluginNames = 'dsv' | 'image' | 'google-fonts' | + 'json' | 'mermaid' | 'number' | 'placeholders' | diff --git a/packages/markdown/src/plugins/json.ts b/packages/markdown/src/plugins/json.ts new file mode 100644 index 00000000..ebe04fda --- /dev/null +++ b/packages/markdown/src/plugins/json.ts @@ -0,0 +1,195 @@ +/** +* Copyright (c) Microsoft Corporation. +* Licensed under the MIT License. +*/ + +import { Batch, IInstance, Plugin, RawFlaggableSpec } from '../factory.js'; +import { sanitizedHTML, sanitizeHtmlComment } from '../sanitize.js'; +import { pluginClassName } from './util.js'; +import { PluginNames } from './interfaces.js'; +import { SpecReview } from 'common'; +import { parseVariableId } from './dsv.js'; + +interface JsonInstance { + id: string; + spec: JsonSpec; + data: object[]; +} + +export interface JsonSpec { + variableId: string; + wasDefaultId?: boolean; +} + +function inspectJsonSpec(spec: JsonSpec): RawFlaggableSpec { + const result: RawFlaggableSpec = { + spec, + hasFlags: false, + reasons: [] + }; + + // Flag if we had to use defaults + if (spec.wasDefaultId) { + result.hasFlags = true; + result.reasons.push('No variable ID specified - using default'); + } + + return result; +} + +const pluginName: PluginNames = 'json'; +const className = pluginClassName(pluginName); + +export const jsonPlugin: Plugin = { + name: pluginName, + fence: (token, index) => { + const content = token.content.trim(); + const info = token.info.trim(); + + // Use utility function to parse variable ID + const { variableId, wasDefaultId } = parseVariableId(info, 'json', index); + + return sanitizedHTML('pre', { + id: `${pluginName}-${index}`, + class: className, + style: 'display:none', + 'data-variable-id': variableId, + 'data-was-default-id': wasDefaultId.toString() + }, content, false); + }, + hydrateSpecs: (renderer, errorHandler) => { + const flagged: SpecReview[] = []; + const containers = renderer.element.querySelectorAll(`.${className}`); + + for (const [index, container] of Array.from(containers).entries()) { + try { + const variableId = container.getAttribute('data-variable-id'); + const wasDefaultId = container.getAttribute('data-was-default-id') === 'true'; + + if (!variableId) { + errorHandler(new Error('No variable ID found'), pluginName, index, 'parse', container); + continue; + } + + const spec: JsonSpec = { variableId, wasDefaultId }; + const flaggableSpec = inspectJsonSpec(spec); + + const f: SpecReview = { + approvedSpec: null, + pluginName, + containerId: container.id + }; + + if (flaggableSpec.hasFlags) { + f.blockedSpec = flaggableSpec.spec; + f.reason = flaggableSpec.reasons?.join(', ') || 'Unknown reason'; + } else { + f.approvedSpec = flaggableSpec.spec; + } + + flagged.push(f); + } catch (e) { + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName, index, 'parse', container); + } + } + + return flagged; + }, + hydrateComponent: async (renderer, errorHandler, specs) => { + const { signalBus } = renderer; + const jsonInstances: JsonInstance[] = []; + + for (let index = 0; index < specs.length; index++) { + const specReview = specs[index]; + if (!specReview.approvedSpec) { + continue; + } + + const container = renderer.element.querySelector(`#${specReview.containerId}`); + if (!container) { + errorHandler(new Error('Container not found'), pluginName, index, 'init', null); + continue; + } + + try { + const content = container.textContent?.trim(); + if (!content) { + errorHandler(new Error('No JSON content found'), pluginName, index, 'parse', container); + continue; + } + + const spec: JsonSpec = specReview.approvedSpec; + + // Parse JSON content + let data: object[]; + try { + const parsed = JSON.parse(content); + // Ensure data is an array + if (Array.isArray(parsed)) { + data = parsed; + } else { + // If it's a single object, wrap it in an array + data = [parsed]; + } + } catch (jsonError) { + errorHandler( + new Error(`Invalid JSON: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`), + pluginName, + index, + 'parse', + container + ); + continue; + } + + const jsonInstance: JsonInstance = { + id: `${pluginName}-${index}`, + spec, + data + }; + jsonInstances.push(jsonInstance); + + // Add a safe comment before the container to show that data was loaded + const comment = sanitizeHtmlComment(`JSON data loaded: ${data.length} rows for variable '${spec.variableId}'`); + container.insertAdjacentHTML('beforebegin', comment); + + } catch (e) { + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName, index, 'parse', container); + } + } + + const instances = jsonInstances.map((jsonInstance): IInstance => { + const { spec, data } = jsonInstance; + + const initialSignals = [{ + name: spec.variableId, + value: data, + priority: 1, + isData: true, + }]; + + return { + ...jsonInstance, + initialSignals, + beginListening() { + // JSON data is static, but we broadcast it when listening begins + const batch: Batch = { + [spec.variableId]: { + value: data, + isData: true, + }, + }; + signalBus.broadcast(jsonInstance.id, batch); + }, + getCurrentSignalValue: () => { + return data; + }, + destroy: () => { + // No cleanup needed for JSON data + }, + }; + }); + + return instances; + }, +}; diff --git a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json index 7bc39955..b73acdb5 100644 --- a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json +++ b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json @@ -2,6 +2,24 @@ "$schema": "../../../../docs/schema/idoc_v1.json", "title": "Feature: Inline Delimited Data", "groups": [ + { + "groupId": "inline_json", + "elements": [ + "## Inline JSON Data", + "You can provide JSON data directly as a code block within the document using the **json plugin** with a `variableId` parameter. This is useful for structured data or when you want to include complex data objects.", + "", + "```json inventory\n[\n {\"item\": \"Keyboard\", \"price\": 79.99, \"inStock\": true},\n {\"item\": \"Mouse\", \"price\": 29.99, \"inStock\": true},\n {\"item\": \"Monitor\", \"price\": 299.99, \"inStock\": false}\n]\n```", + { + "type": "tabulator", + "dataSourceName": "inventory", + "tabulatorOptions": { + "autoColumns": true, + "layout": "fitColumns", + "maxHeight": "150px" + } + } + ] + }, { "groupId": "inline_csv", "elements": [ diff --git a/packages/web-deploy/json/features/json-plugin-test.idoc.json b/packages/web-deploy/json/features/json-plugin-test.idoc.json new file mode 100644 index 00000000..9c995498 --- /dev/null +++ b/packages/web-deploy/json/features/json-plugin-test.idoc.json @@ -0,0 +1,29 @@ +{ + "$schema": "../../../../docs/schema/idoc_v1.json", + "title": "JSON Plugin Test", + "groups": [ + { + "groupId": "json_test", + "elements": [ + "## Testing JSON Plugin", + "This example tests the new JSON plugin with inline JSON data.", + "", + "```json books\n[\n {\"title\": \"The Great Gatsby\", \"author\": \"F. Scott Fitzgerald\", \"year\": 1925, \"rating\": 4.5},\n {\"title\": \"To Kill a Mockingbird\", \"author\": \"Harper Lee\", \"year\": 1960, \"rating\": 4.8},\n {\"title\": \"1984\", \"author\": \"George Orwell\", \"year\": 1949, \"rating\": 4.7},\n {\"title\": \"Pride and Prejudice\", \"author\": \"Jane Austen\", \"year\": 1813, \"rating\": 4.6}\n]\n```", + { + "type": "tabulator", + "dataSourceName": "books", + "variableId": "selectedBooks", + "tabulatorOptions": { + "autoColumns": true, + "layout": "fitColumns", + "maxHeight": "300px", + "selectable": 1 + } + }, + "", + "### Selected Book", + "Selected: **{{selectedBooks[0].title}}** by {{selectedBooks[0].author}} ({{selectedBooks[0].year}})" + ] + } + ] +} From 0ab0f42c4784fe65e94b4081b278ebdfd3132f36 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:27:51 +0000 Subject: [PATCH 03/47] Update compiler to handle JSON format in dataLoaders Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- docs/dist/v1/chartifact.compiler.umd.js | 4 +++ docs/dist/v1/chartifact.editor.umd.js | 4 +++ docs/dist/v1/chartifact.host.umd.js | 4 +++ packages/compiler/src/loader.ts | 4 +++ .../features/json-dataloader-test.idoc.json | 30 +++++++++++++++++++ 5 files changed, 46 insertions(+) create mode 100644 packages/web-deploy/json/features/json-dataloader-test.idoc.json diff --git a/docs/dist/v1/chartifact.compiler.umd.js b/docs/dist/v1/chartifact.compiler.umd.js index 6c956f2f..333b7c24 100644 --- a/docs/dist/v1/chartifact.compiler.umd.js +++ b/docs/dist/v1/chartifact.compiler.umd.js @@ -1057,6 +1057,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy inlineDataMd = tickWrap(`dsv delimiter:${delimiter} variableId:${ds_raw}`, content); break; } + case "json": { + inlineDataMd = tickWrap(`json ${ds_raw}`, content); + break; + } default: { console.warn(`Unsupported inline data format: ${dataSource.format}, type is ${typeof dataSource.content}`); break; diff --git a/docs/dist/v1/chartifact.editor.umd.js b/docs/dist/v1/chartifact.editor.umd.js index adb27c31..0d50b9b7 100644 --- a/docs/dist/v1/chartifact.editor.umd.js +++ b/docs/dist/v1/chartifact.editor.umd.js @@ -1041,6 +1041,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy inlineDataMd = tickWrap(`dsv delimiter:${delimiter} variableId:${ds_raw}`, content); break; } + case "json": { + inlineDataMd = tickWrap(`json ${ds_raw}`, content); + break; + } default: { console.warn(`Unsupported inline data format: ${dataSource.format}, type is ${typeof dataSource.content}`); break; diff --git a/docs/dist/v1/chartifact.host.umd.js b/docs/dist/v1/chartifact.host.umd.js index 681d27c9..ea39762a 100644 --- a/docs/dist/v1/chartifact.host.umd.js +++ b/docs/dist/v1/chartifact.host.umd.js @@ -1041,6 +1041,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy inlineDataMd = tickWrap(`dsv delimiter:${delimiter} variableId:${ds_raw}`, content); break; } + case "json": { + inlineDataMd = tickWrap(`json ${ds_raw}`, content); + break; + } default: { console.warn(`Unsupported inline data format: ${dataSource.format}, type is ${typeof dataSource.content}`); break; diff --git a/packages/compiler/src/loader.ts b/packages/compiler/src/loader.ts index 0cb02d07..c9ab8ec1 100644 --- a/packages/compiler/src/loader.ts +++ b/packages/compiler/src/loader.ts @@ -65,6 +65,10 @@ export function addStaticDataLoaderToSpec(vegaScope: VegaScope, dataSource: Data inlineDataMd = tickWrap(`dsv delimiter:${delimiter} variableId:${ds_raw}`, content); break; } + case 'json': { + inlineDataMd = tickWrap(`json ${ds_raw}`, content); + break; + } default: { console.warn(`Unsupported inline data format: ${dataSource.format}, type is ${typeof dataSource.content}`); break; diff --git a/packages/web-deploy/json/features/json-dataloader-test.idoc.json b/packages/web-deploy/json/features/json-dataloader-test.idoc.json new file mode 100644 index 00000000..a4940ad2 --- /dev/null +++ b/packages/web-deploy/json/features/json-dataloader-test.idoc.json @@ -0,0 +1,30 @@ +{ + "$schema": "../../../../docs/schema/idoc_v1.json", + "title": "JSON Plugin - DataLoader String Format", + "dataLoaders": [ + { + "dataSourceName": "employees", + "type": "inline", + "format": "json", + "content": "[\n {\"name\": \"Alice Johnson\", \"department\": \"Engineering\", \"salary\": 95000},\n {\"name\": \"Bob Smith\", \"department\": \"Sales\", \"salary\": 75000},\n {\"name\": \"Carol White\", \"department\": \"Engineering\", \"salary\": 105000},\n {\"name\": \"David Brown\", \"department\": \"Marketing\", \"salary\": 68000}\n]" + } + ], + "groups": [ + { + "groupId": "dataloader_test", + "elements": [ + "## JSON DataLoader (String Format)", + "This example tests using JSON format in dataLoaders with string content.", + { + "type": "tabulator", + "dataSourceName": "employees", + "tabulatorOptions": { + "autoColumns": true, + "layout": "fitColumns", + "maxHeight": "200px" + } + } + ] + } + ] +} From 1a429b259084f5187b517de42f803bbc3fea1677 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:30:31 +0000 Subject: [PATCH 04/47] Add comprehensive JSON plugin example and documentation Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../features/json-complete-example.idoc.json | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 packages/web-deploy/json/features/json-complete-example.idoc.json diff --git a/packages/web-deploy/json/features/json-complete-example.idoc.json b/packages/web-deploy/json/features/json-complete-example.idoc.json new file mode 100644 index 00000000..17f05f10 --- /dev/null +++ b/packages/web-deploy/json/features/json-complete-example.idoc.json @@ -0,0 +1,99 @@ +{ + "$schema": "../../../../docs/schema/idoc_v1.json", + "title": "JSON Data Plugin - Complete Example", + "description": "Demonstrates the new JSON plugin for inline JSON data in markdown fence blocks", + "dataLoaders": [ + { + "dataSourceName": "companiesFromDataLoader", + "type": "inline", + "format": "json", + "content": [ + {"company": "TechCorp", "industry": "Technology", "revenue": 5000000, "employees": 250}, + {"company": "HealthPlus", "industry": "Healthcare", "revenue": 3200000, "employees": 180}, + {"company": "EduLearn", "industry": "Education", "revenue": 1500000, "employees": 95} + ] + } + ], + "groups": [ + { + "groupId": "intro", + "elements": [ + "# JSON Data Plugin", + "", + "This document demonstrates the **JSON plugin** which allows you to embed JSON data directly in markdown fence blocks, similar to how CSV, TSV, and DSV plugins work.", + "", + "## Benefits", + "- **Structured Data**: JSON supports complex nested structures, booleans, numbers, and more", + "- **Type Safety**: Preserves data types (numbers stay as numbers, booleans as booleans)", + "- **Readability**: JSON format is widely understood and easy to read", + "- **Compatibility**: Works seamlessly with existing Chartifact components like tables and charts" + ] + }, + { + "groupId": "fence_example", + "elements": [ + "## Example 1: JSON Fence Block", + "", + "You can provide JSON data using a markdown fence block with the `json` keyword followed by a variable name:", + "", + "```json products\n[\n {\"name\": \"Laptop\", \"category\": \"Electronics\", \"price\": 1299.99, \"inStock\": true, \"rating\": 4.5},\n {\"name\": \"Desk Chair\", \"category\": \"Furniture\", \"price\": 349.99, \"inStock\": true, \"rating\": 4.8},\n {\"name\": \"Monitor\", \"category\": \"Electronics\", \"price\": 499.99, \"inStock\": false, \"rating\": 4.6},\n {\"name\": \"Keyboard\", \"category\": \"Electronics\", \"price\": 129.99, \"inStock\": true, \"rating\": 4.7},\n {\"name\": \"Standing Desk\", \"category\": \"Furniture\", \"price\": 799.99, \"inStock\": true, \"rating\": 4.9}\n]\n```", + "", + "### Products Table", + { + "type": "tabulator", + "dataSourceName": "products", + "variableId": "selectedProduct", + "tabulatorOptions": { + "autoColumns": true, + "layout": "fitColumns", + "maxHeight": "250px", + "selectable": 1 + } + }, + "", + "**Selected Product:** {{selectedProduct[0].name}} - ${{selectedProduct[0].price}} (Rating: {{selectedProduct[0].rating}}⭐)" + ] + }, + { + "groupId": "dataloader_example", + "elements": [ + "## Example 2: JSON in DataLoaders", + "", + "You can also use JSON format in the `dataLoaders` section with an object array:", + "", + "### Companies Table", + { + "type": "tabulator", + "dataSourceName": "companiesFromDataLoader", + "tabulatorOptions": { + "autoColumns": true, + "layout": "fitColumns", + "maxHeight": "200px" + } + } + ] + }, + { + "groupId": "comparison", + "elements": [ + "## Comparison: JSON vs CSV", + "", + "### JSON Format", + "```json jsonExample\n[\n {\"item\": \"Widget\", \"quantity\": 100, \"available\": true},\n {\"item\": \"Gadget\", \"quantity\": 50, \"available\": false}\n]\n```", + "", + "### CSV Format", + "```csv csvExample\nitem,quantity,available\nWidget,100,true\nGadget,50,false\n```", + "", + "### JSON Advantages:", + "- **Native data types**: Numbers and booleans don't need parsing", + "- **Complex structures**: Can handle nested objects and arrays", + "- **No escaping issues**: Special characters don't need escape sequences", + "", + "### CSV Advantages:", + "- **Compact**: More space-efficient for simple tabular data", + "- **Widely supported**: Easy to import/export from spreadsheets", + "- **Human-readable**: Simpler format for basic tables" + ] + } + ] +} From ab43fa5107bd57a30abb7075cfbfff0fe8f7d5fc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:20:48 +0000 Subject: [PATCH 05/47] Update JSON plugin to use 'json data variableId' syntax and script tag Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/compiler/src/loader.ts | 2 +- packages/markdown/src/plugins/json.ts | 35 +++++++++++++++---- packages/markdown/src/sanitize.ts | 30 +++++++++++++--- .../features/2.4.inline-csv-data.idoc.json | 2 +- .../features/json-complete-example.idoc.json | 6 ++-- .../json/features/json-plugin-test.idoc.json | 2 +- 6 files changed, 59 insertions(+), 18 deletions(-) diff --git a/packages/compiler/src/loader.ts b/packages/compiler/src/loader.ts index c9ab8ec1..a18287f6 100644 --- a/packages/compiler/src/loader.ts +++ b/packages/compiler/src/loader.ts @@ -66,7 +66,7 @@ export function addStaticDataLoaderToSpec(vegaScope: VegaScope, dataSource: Data break; } case 'json': { - inlineDataMd = tickWrap(`json ${ds_raw}`, content); + inlineDataMd = tickWrap(`json data ${ds_raw}`, content); break; } default: { diff --git a/packages/markdown/src/plugins/json.ts b/packages/markdown/src/plugins/json.ts index ebe04fda..10a00897 100644 --- a/packages/markdown/src/plugins/json.ts +++ b/packages/markdown/src/plugins/json.ts @@ -4,7 +4,7 @@ */ import { Batch, IInstance, Plugin, RawFlaggableSpec } from '../factory.js'; -import { sanitizedHTML, sanitizeHtmlComment } from '../sanitize.js'; +import { sanitizedScriptTag, sanitizeHtmlComment } from '../sanitize.js'; import { pluginClassName } from './util.js'; import { PluginNames } from './interfaces.js'; import { SpecReview } from 'common'; @@ -46,16 +46,37 @@ export const jsonPlugin: Plugin = { const content = token.content.trim(); const info = token.info.trim(); - // Use utility function to parse variable ID - const { variableId, wasDefaultId } = parseVariableId(info, 'json', index); + // Parse the fence info - expect "json data variableId" format + const parts = info.split(/\s+/); - return sanitizedHTML('pre', { - id: `${pluginName}-${index}`, + // Check if the second parameter is "data" + let variableId: string; + let wasDefaultId = false; + + if (parts.length >= 3 && parts[1] === 'data') { + // Format: json data variableId + variableId = parts[2]; + } else if (parts.length >= 2 && parts[1].startsWith('variableId:')) { + // Format: json data variableId:name (with explicit parameter) + variableId = parts[1].slice(11).trim(); + } else if (parts.length >= 3 && parts[2].startsWith('variableId:')) { + // Format: json data variableId:name (with data keyword) + variableId = parts[2].slice(11).trim(); + } else { + // Default variable ID if format is incorrect + variableId = `${pluginName}Data${index}`; + wasDefaultId = true; + } + + // Use script tag with application/json type instead of pre tag + const scriptElement = sanitizedScriptTag(content, { + id: `${pluginName}-${index}`, class: className, - style: 'display:none', 'data-variable-id': variableId, 'data-was-default-id': wasDefaultId.toString() - }, content, false); + }); + + return scriptElement.outerHTML; }, hydrateSpecs: (renderer, errorHandler) => { const flagged: SpecReview[] = []; diff --git a/packages/markdown/src/sanitize.ts b/packages/markdown/src/sanitize.ts index 526bf8f1..cabe8f84 100644 --- a/packages/markdown/src/sanitize.ts +++ b/packages/markdown/src/sanitize.ts @@ -26,11 +26,7 @@ export function sanitizedHTML(tagName: string, attributes: { [key: string]: stri if (precedeWithScriptTag) { // Create a script tag that precedes the main element - const scriptElement = domDocument.createElement('script'); - scriptElement.setAttribute('type', 'application/json'); - // Only escape the dangerous sequence that could break out of script tag - const safeContent = content.replace(/<\/script>/gi, '<\\/script>'); - scriptElement.innerHTML = safeContent; + const scriptElement = sanitizedScriptTag(content); // Return script tag followed by empty element return scriptElement.outerHTML + element.outerHTML; @@ -43,6 +39,30 @@ export function sanitizedHTML(tagName: string, attributes: { [key: string]: stri return element.outerHTML; } +export function sanitizedScriptTag(content: string, attributes?: { [key: string]: string }): HTMLScriptElement { + if (!domDocument) { + throw new Error('No DOM Document available. Please set domDocument using setDomDocument.'); + } + + const scriptElement = domDocument.createElement('script'); + + // Set default type to application/json + scriptElement.setAttribute('type', 'application/json'); + + // Set additional attributes if provided + if (attributes) { + Object.keys(attributes).forEach(key => { + scriptElement.setAttribute(key, attributes[key]); + }); + } + + // Only escape the dangerous sequence that could break out of script tag + const safeContent = content.replace(/<\/script>/gi, '<\\/script>'); + scriptElement.innerHTML = safeContent; + + return scriptElement; +} + export function sanitizeHtmlComment(content: string) { // First escape the content safely diff --git a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json index b73acdb5..dc759c2c 100644 --- a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json +++ b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json @@ -8,7 +8,7 @@ "## Inline JSON Data", "You can provide JSON data directly as a code block within the document using the **json plugin** with a `variableId` parameter. This is useful for structured data or when you want to include complex data objects.", "", - "```json inventory\n[\n {\"item\": \"Keyboard\", \"price\": 79.99, \"inStock\": true},\n {\"item\": \"Mouse\", \"price\": 29.99, \"inStock\": true},\n {\"item\": \"Monitor\", \"price\": 299.99, \"inStock\": false}\n]\n```", + "```json data inventory\n[\n {\"item\": \"Keyboard\", \"price\": 79.99, \"inStock\": true},\n {\"item\": \"Mouse\", \"price\": 29.99, \"inStock\": true},\n {\"item\": \"Monitor\", \"price\": 299.99, \"inStock\": false}\n]\n```", { "type": "tabulator", "dataSourceName": "inventory", diff --git a/packages/web-deploy/json/features/json-complete-example.idoc.json b/packages/web-deploy/json/features/json-complete-example.idoc.json index 17f05f10..b8d18566 100644 --- a/packages/web-deploy/json/features/json-complete-example.idoc.json +++ b/packages/web-deploy/json/features/json-complete-example.idoc.json @@ -34,9 +34,9 @@ "elements": [ "## Example 1: JSON Fence Block", "", - "You can provide JSON data using a markdown fence block with the `json` keyword followed by a variable name:", + "You can provide JSON data using a markdown fence block with the `json data` keywords followed by a variable name:", "", - "```json products\n[\n {\"name\": \"Laptop\", \"category\": \"Electronics\", \"price\": 1299.99, \"inStock\": true, \"rating\": 4.5},\n {\"name\": \"Desk Chair\", \"category\": \"Furniture\", \"price\": 349.99, \"inStock\": true, \"rating\": 4.8},\n {\"name\": \"Monitor\", \"category\": \"Electronics\", \"price\": 499.99, \"inStock\": false, \"rating\": 4.6},\n {\"name\": \"Keyboard\", \"category\": \"Electronics\", \"price\": 129.99, \"inStock\": true, \"rating\": 4.7},\n {\"name\": \"Standing Desk\", \"category\": \"Furniture\", \"price\": 799.99, \"inStock\": true, \"rating\": 4.9}\n]\n```", + "```json data products\n[\n {\"name\": \"Laptop\", \"category\": \"Electronics\", \"price\": 1299.99, \"inStock\": true, \"rating\": 4.5},\n {\"name\": \"Desk Chair\", \"category\": \"Furniture\", \"price\": 349.99, \"inStock\": true, \"rating\": 4.8},\n {\"name\": \"Monitor\", \"category\": \"Electronics\", \"price\": 499.99, \"inStock\": false, \"rating\": 4.6},\n {\"name\": \"Keyboard\", \"category\": \"Electronics\", \"price\": 129.99, \"inStock\": true, \"rating\": 4.7},\n {\"name\": \"Standing Desk\", \"category\": \"Furniture\", \"price\": 799.99, \"inStock\": true, \"rating\": 4.9}\n]\n```", "", "### Products Table", { @@ -79,7 +79,7 @@ "## Comparison: JSON vs CSV", "", "### JSON Format", - "```json jsonExample\n[\n {\"item\": \"Widget\", \"quantity\": 100, \"available\": true},\n {\"item\": \"Gadget\", \"quantity\": 50, \"available\": false}\n]\n```", + "```json data jsonExample\n[\n {\"item\": \"Widget\", \"quantity\": 100, \"available\": true},\n {\"item\": \"Gadget\", \"quantity\": 50, \"available\": false}\n]\n```", "", "### CSV Format", "```csv csvExample\nitem,quantity,available\nWidget,100,true\nGadget,50,false\n```", diff --git a/packages/web-deploy/json/features/json-plugin-test.idoc.json b/packages/web-deploy/json/features/json-plugin-test.idoc.json index 9c995498..18f8fbf9 100644 --- a/packages/web-deploy/json/features/json-plugin-test.idoc.json +++ b/packages/web-deploy/json/features/json-plugin-test.idoc.json @@ -8,7 +8,7 @@ "## Testing JSON Plugin", "This example tests the new JSON plugin with inline JSON data.", "", - "```json books\n[\n {\"title\": \"The Great Gatsby\", \"author\": \"F. Scott Fitzgerald\", \"year\": 1925, \"rating\": 4.5},\n {\"title\": \"To Kill a Mockingbird\", \"author\": \"Harper Lee\", \"year\": 1960, \"rating\": 4.8},\n {\"title\": \"1984\", \"author\": \"George Orwell\", \"year\": 1949, \"rating\": 4.7},\n {\"title\": \"Pride and Prejudice\", \"author\": \"Jane Austen\", \"year\": 1813, \"rating\": 4.6}\n]\n```", + "```json data books\n[\n {\"title\": \"The Great Gatsby\", \"author\": \"F. Scott Fitzgerald\", \"year\": 1925, \"rating\": 4.5},\n {\"title\": \"To Kill a Mockingbird\", \"author\": \"Harper Lee\", \"year\": 1960, \"rating\": 4.8},\n {\"title\": \"1984\", \"author\": \"George Orwell\", \"year\": 1949, \"rating\": 4.7},\n {\"title\": \"Pride and Prejudice\", \"author\": \"Jane Austen\", \"year\": 1813, \"rating\": 4.6}\n]\n```", { "type": "tabulator", "dataSourceName": "books", From e38425f5d9916a424c906a0d1cf2a294e3039859 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 19:51:39 +0000 Subject: [PATCH 06/47] Rename json plugin to json-data and fix loader.ts JSON handling Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/compiler/src/loader.ts | 47 ++++++++++++++----- packages/markdown/src/factory.ts | 8 ++++ packages/markdown/src/plugins/index.ts | 4 +- packages/markdown/src/plugins/interfaces.ts | 4 +- .../src/plugins/{json.ts => json-data.ts} | 24 +++++----- 5 files changed, 59 insertions(+), 28 deletions(-) rename packages/markdown/src/plugins/{json.ts => json-data.ts} (90%) diff --git a/packages/compiler/src/loader.ts b/packages/compiler/src/loader.ts index a18287f6..40ed1762 100644 --- a/packages/compiler/src/loader.ts +++ b/packages/compiler/src/loader.ts @@ -18,15 +18,42 @@ export function addStaticDataLoaderToSpec(vegaScope: VegaScope, dataSource: Data if (dataSource.type === 'inline' && dataSource.format === 'json') { - const newData: ValuesData = { - name: dataSourceName, - values: dataSource.content as object[], - transform: dataSource.dataFrameTransformations || [], - }; - spec.signals.push(dataAsSignal(dataSourceName)); + // Check if content is already an object array + if (Array.isArray(dataSource.content) && typeof dataSource.content[0] === 'object') { + const newData: ValuesData = { + name: dataSourceName, + values: dataSource.content as object[], + transform: dataSource.dataFrameTransformations || [], + }; + spec.signals.push(dataAsSignal(dataSourceName)); - //real data goes to the beginning of the data array - spec.data.unshift(newData); + //real data goes to the beginning of the data array + spec.data.unshift(newData); + } else if (typeof dataSource.content === 'string') { + // Content is JSON string - need to output as json data fence block + const content = dataSource.content; + let ds_raw = dataSourceName; + + if (dataSource.dataFrameTransformations) { + ds_raw += '_raw'; + + const newData: SourceData = { + name: dataSourceName, + source: ds_raw, + transform: dataSource.dataFrameTransformations || [], + }; + spec.signals.push(dataAsSignal(dataSourceName)); + + spec.data.unshift(newData); + + //add a placeholder data since the transform depends on it + spec.data.unshift({ + name: ds_raw + }); + } + + inlineDataMd = tickWrap(`json data ${ds_raw}`, content); + } } else if (typeof dataSource.content === 'string' || (Array.isArray(dataSource.content) && typeof dataSource.content[0] === 'string')) { @@ -65,10 +92,6 @@ export function addStaticDataLoaderToSpec(vegaScope: VegaScope, dataSource: Data inlineDataMd = tickWrap(`dsv delimiter:${delimiter} variableId:${ds_raw}`, content); break; } - case 'json': { - inlineDataMd = tickWrap(`json data ${ds_raw}`, content); - break; - } default: { console.warn(`Unsupported inline data format: ${dataSource.format}, type is ${typeof dataSource.content}`); break; diff --git a/packages/markdown/src/factory.ts b/packages/markdown/src/factory.ts index c435b73c..d584897e 100644 --- a/packages/markdown/src/factory.ts +++ b/packages/markdown/src/factory.ts @@ -156,6 +156,14 @@ export function create() { else { const infoWords = info.split(/\s+/); if (infoWords.length > 0) { + // Special case for "json data" - treat as single plugin name + if (infoWords[0] === 'json' && infoWords[1] === 'data') { + const jsonDataPlugin = findPlugin('json-data'); + if (jsonDataPlugin) { + return jsonDataPlugin; + } + } + const pluginPrefix = findPluginByPrefix(infoWords[0]); if (pluginPrefix) { return pluginPrefix; diff --git a/packages/markdown/src/plugins/index.ts b/packages/markdown/src/plugins/index.ts index 4c2ba8e3..240bf25d 100644 --- a/packages/markdown/src/plugins/index.ts +++ b/packages/markdown/src/plugins/index.ts @@ -13,7 +13,7 @@ import { dsvPlugin } from './dsv.js'; import { googleFontsPlugin } from './google-fonts.js'; import { dropdownPlugin } from './dropdown.js'; import { imagePlugin } from './image.js'; -import { jsonPlugin } from './json.js'; +import { jsonDataPlugin } from './json-data.js'; import { mermaidPlugin } from './mermaid.js'; import { numberPlugin } from './number.js'; import { placeholdersPlugin } from './placeholders.js'; @@ -35,7 +35,7 @@ export function registerNativePlugins() { registerMarkdownPlugin(googleFontsPlugin); registerMarkdownPlugin(dropdownPlugin); registerMarkdownPlugin(imagePlugin); - registerMarkdownPlugin(jsonPlugin); + registerMarkdownPlugin(jsonDataPlugin); registerMarkdownPlugin(mermaidPlugin); registerMarkdownPlugin(numberPlugin); registerMarkdownPlugin(placeholdersPlugin); diff --git a/packages/markdown/src/plugins/interfaces.ts b/packages/markdown/src/plugins/interfaces.ts index c175ec96..51bcb7fb 100644 --- a/packages/markdown/src/plugins/interfaces.ts +++ b/packages/markdown/src/plugins/interfaces.ts @@ -7,7 +7,7 @@ export { CsvSpec } from './csv.js'; export { DropdownSpec } from './dropdown.js'; export { DsvSpec } from './dsv.js'; export { ImageSpec } from './image.js'; -export { JsonSpec } from './json.js'; +export { JsonSpec } from './json-data.js'; export { MermaidSpec } from './mermaid.js'; export { NumberSpec } from './number.js'; export { PresetsSpec } from './presets.js'; @@ -26,7 +26,7 @@ export type PluginNames = 'dsv' | 'image' | 'google-fonts' | - 'json' | + 'json-data' | 'mermaid' | 'number' | 'placeholders' | diff --git a/packages/markdown/src/plugins/json.ts b/packages/markdown/src/plugins/json-data.ts similarity index 90% rename from packages/markdown/src/plugins/json.ts rename to packages/markdown/src/plugins/json-data.ts index 10a00897..a6de5fa8 100644 --- a/packages/markdown/src/plugins/json.ts +++ b/packages/markdown/src/plugins/json-data.ts @@ -37,10 +37,10 @@ function inspectJsonSpec(spec: JsonSpec): RawFlaggableSpec { return result; } -const pluginName: PluginNames = 'json'; +const pluginName: PluginNames = 'json-data'; const className = pluginClassName(pluginName); -export const jsonPlugin: Plugin = { +export const jsonDataPlugin: Plugin = { name: pluginName, fence: (token, index) => { const content = token.content.trim(); @@ -49,22 +49,22 @@ export const jsonPlugin: Plugin = { // Parse the fence info - expect "json data variableId" format const parts = info.split(/\s+/); - // Check if the second parameter is "data" + // Require "json data" prefix + if (parts.length < 2 || parts[0] !== 'json' || parts[1] !== 'data') { + // This fence is not for json-data plugin + return ''; + } + + // Check for variable ID let variableId: string; let wasDefaultId = false; - if (parts.length >= 3 && parts[1] === 'data') { + if (parts.length >= 3) { // Format: json data variableId variableId = parts[2]; - } else if (parts.length >= 2 && parts[1].startsWith('variableId:')) { - // Format: json data variableId:name (with explicit parameter) - variableId = parts[1].slice(11).trim(); - } else if (parts.length >= 3 && parts[2].startsWith('variableId:')) { - // Format: json data variableId:name (with data keyword) - variableId = parts[2].slice(11).trim(); } else { - // Default variable ID if format is incorrect - variableId = `${pluginName}Data${index}`; + // Default variable ID if not provided + variableId = `jsonData${index}`; wasDefaultId = true; } From a67e6f8bd40b4340eb6c8386fa4a1a6838790fb5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:06:47 +0000 Subject: [PATCH 07/47] Fix JSON dataLoaders to always output as fence blocks and rename types Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/compiler/src/loader.ts | 54 ++++++++++----------- packages/markdown/src/plugins/interfaces.ts | 2 +- packages/markdown/src/plugins/json-data.ts | 26 +++++----- 3 files changed, 40 insertions(+), 42 deletions(-) diff --git a/packages/compiler/src/loader.ts b/packages/compiler/src/loader.ts index 40ed1762..df382d42 100644 --- a/packages/compiler/src/loader.ts +++ b/packages/compiler/src/loader.ts @@ -18,42 +18,40 @@ export function addStaticDataLoaderToSpec(vegaScope: VegaScope, dataSource: Data if (dataSource.type === 'inline' && dataSource.format === 'json') { - // Check if content is already an object array + // For JSON format, always output as json data fence block + let content: string; + + // Stringify content if it's an object array if (Array.isArray(dataSource.content) && typeof dataSource.content[0] === 'object') { - const newData: ValuesData = { + content = JSON.stringify(dataSource.content, null, 2); + } else if (typeof dataSource.content === 'string') { + content = dataSource.content; + } else { + console.warn(`Unsupported JSON content type: ${typeof dataSource.content}`); + return inlineDataMd; + } + + let ds_raw = dataSourceName; + + if (dataSource.dataFrameTransformations) { + ds_raw += '_raw'; + + const newData: SourceData = { name: dataSourceName, - values: dataSource.content as object[], + source: ds_raw, transform: dataSource.dataFrameTransformations || [], }; spec.signals.push(dataAsSignal(dataSourceName)); - //real data goes to the beginning of the data array spec.data.unshift(newData); - } else if (typeof dataSource.content === 'string') { - // Content is JSON string - need to output as json data fence block - const content = dataSource.content; - let ds_raw = dataSourceName; - - if (dataSource.dataFrameTransformations) { - ds_raw += '_raw'; - - const newData: SourceData = { - name: dataSourceName, - source: ds_raw, - transform: dataSource.dataFrameTransformations || [], - }; - spec.signals.push(dataAsSignal(dataSourceName)); - - spec.data.unshift(newData); - - //add a placeholder data since the transform depends on it - spec.data.unshift({ - name: ds_raw - }); - } - - inlineDataMd = tickWrap(`json data ${ds_raw}`, content); + + //add a placeholder data since the transform depends on it + spec.data.unshift({ + name: ds_raw + }); } + + inlineDataMd = tickWrap(`json data ${ds_raw}`, content); } else if (typeof dataSource.content === 'string' || (Array.isArray(dataSource.content) && typeof dataSource.content[0] === 'string')) { diff --git a/packages/markdown/src/plugins/interfaces.ts b/packages/markdown/src/plugins/interfaces.ts index 51bcb7fb..e45ad04f 100644 --- a/packages/markdown/src/plugins/interfaces.ts +++ b/packages/markdown/src/plugins/interfaces.ts @@ -7,7 +7,7 @@ export { CsvSpec } from './csv.js'; export { DropdownSpec } from './dropdown.js'; export { DsvSpec } from './dsv.js'; export { ImageSpec } from './image.js'; -export { JsonSpec } from './json-data.js'; +export { JsonDataSpec } from './json-data.js'; export { MermaidSpec } from './mermaid.js'; export { NumberSpec } from './number.js'; export { PresetsSpec } from './presets.js'; diff --git a/packages/markdown/src/plugins/json-data.ts b/packages/markdown/src/plugins/json-data.ts index a6de5fa8..fcd7bf6d 100644 --- a/packages/markdown/src/plugins/json-data.ts +++ b/packages/markdown/src/plugins/json-data.ts @@ -10,19 +10,19 @@ import { PluginNames } from './interfaces.js'; import { SpecReview } from 'common'; import { parseVariableId } from './dsv.js'; -interface JsonInstance { +interface JsonDataInstance { id: string; - spec: JsonSpec; + spec: JsonDataSpec; data: object[]; } -export interface JsonSpec { +export interface JsonDataSpec { variableId: string; wasDefaultId?: boolean; } -function inspectJsonSpec(spec: JsonSpec): RawFlaggableSpec { - const result: RawFlaggableSpec = { +function inspectJsonDataSpec(spec: JsonDataSpec): RawFlaggableSpec { + const result: RawFlaggableSpec = { spec, hasFlags: false, reasons: [] @@ -40,7 +40,7 @@ function inspectJsonSpec(spec: JsonSpec): RawFlaggableSpec { const pluginName: PluginNames = 'json-data'; const className = pluginClassName(pluginName); -export const jsonDataPlugin: Plugin = { +export const jsonDataPlugin: Plugin = { name: pluginName, fence: (token, index) => { const content = token.content.trim(); @@ -79,7 +79,7 @@ export const jsonDataPlugin: Plugin = { return scriptElement.outerHTML; }, hydrateSpecs: (renderer, errorHandler) => { - const flagged: SpecReview[] = []; + const flagged: SpecReview[] = []; const containers = renderer.element.querySelectorAll(`.${className}`); for (const [index, container] of Array.from(containers).entries()) { @@ -92,10 +92,10 @@ export const jsonDataPlugin: Plugin = { continue; } - const spec: JsonSpec = { variableId, wasDefaultId }; - const flaggableSpec = inspectJsonSpec(spec); + const spec: JsonDataSpec = { variableId, wasDefaultId }; + const flaggableSpec = inspectJsonDataSpec(spec); - const f: SpecReview = { + const f: SpecReview = { approvedSpec: null, pluginName, containerId: container.id @@ -118,7 +118,7 @@ export const jsonDataPlugin: Plugin = { }, hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; - const jsonInstances: JsonInstance[] = []; + const jsonInstances: JsonDataInstance[] = []; for (let index = 0; index < specs.length; index++) { const specReview = specs[index]; @@ -139,7 +139,7 @@ export const jsonDataPlugin: Plugin = { continue; } - const spec: JsonSpec = specReview.approvedSpec; + const spec: JsonDataSpec = specReview.approvedSpec; // Parse JSON content let data: object[]; @@ -163,7 +163,7 @@ export const jsonDataPlugin: Plugin = { continue; } - const jsonInstance: JsonInstance = { + const jsonInstance: JsonDataInstance = { id: `${pluginName}-${index}`, spec, data From fb01c3a9e4139b68f44c348949b7a72732cf7264 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:12:31 +0000 Subject: [PATCH 08/47] Rename plugin from json-data to data, remove special case logic Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/markdown/src/factory.ts | 8 ------- .../src/plugins/{json-data.ts => data.ts} | 22 +++++++++---------- packages/markdown/src/plugins/index.ts | 4 ++-- packages/markdown/src/plugins/interfaces.ts | 4 ++-- 4 files changed, 14 insertions(+), 24 deletions(-) rename packages/markdown/src/plugins/{json-data.ts => data.ts} (93%) diff --git a/packages/markdown/src/factory.ts b/packages/markdown/src/factory.ts index d584897e..c435b73c 100644 --- a/packages/markdown/src/factory.ts +++ b/packages/markdown/src/factory.ts @@ -156,14 +156,6 @@ export function create() { else { const infoWords = info.split(/\s+/); if (infoWords.length > 0) { - // Special case for "json data" - treat as single plugin name - if (infoWords[0] === 'json' && infoWords[1] === 'data') { - const jsonDataPlugin = findPlugin('json-data'); - if (jsonDataPlugin) { - return jsonDataPlugin; - } - } - const pluginPrefix = findPluginByPrefix(infoWords[0]); if (pluginPrefix) { return pluginPrefix; diff --git a/packages/markdown/src/plugins/json-data.ts b/packages/markdown/src/plugins/data.ts similarity index 93% rename from packages/markdown/src/plugins/json-data.ts rename to packages/markdown/src/plugins/data.ts index fcd7bf6d..260b17f6 100644 --- a/packages/markdown/src/plugins/json-data.ts +++ b/packages/markdown/src/plugins/data.ts @@ -37,35 +37,33 @@ function inspectJsonDataSpec(spec: JsonDataSpec): RawFlaggableSpec return result; } -const pluginName: PluginNames = 'json-data'; +const pluginName: PluginNames = 'data'; const className = pluginClassName(pluginName); -export const jsonDataPlugin: Plugin = { +export const dataPlugin: Plugin = { name: pluginName, fence: (token, index) => { const content = token.content.trim(); const info = token.info.trim(); // Parse the fence info - expect "json data variableId" format + // The factory.ts will route "json data ..." to this plugin const parts = info.split(/\s+/); - // Require "json data" prefix - if (parts.length < 2 || parts[0] !== 'json' || parts[1] !== 'data') { - // This fence is not for json-data plugin - return ''; - } - - // Check for variable ID + // Check for variable ID (should be after "json data") let variableId: string; let wasDefaultId = false; - if (parts.length >= 3) { + if (parts.length >= 3 && parts[0] === 'json' && parts[1] === 'data') { // Format: json data variableId variableId = parts[2]; - } else { - // Default variable ID if not provided + } else if (parts.length >= 2 && parts[0] === 'json' && parts[1] === 'data') { + // Format: json data (no variable ID provided) variableId = `jsonData${index}`; wasDefaultId = true; + } else { + // Not the expected format + return ''; } // Use script tag with application/json type instead of pre tag diff --git a/packages/markdown/src/plugins/index.ts b/packages/markdown/src/plugins/index.ts index 240bf25d..689a5937 100644 --- a/packages/markdown/src/plugins/index.ts +++ b/packages/markdown/src/plugins/index.ts @@ -9,11 +9,11 @@ import { checkboxPlugin } from './checkbox.js'; import { commentPlugin } from './comment.js'; import { cssPlugin } from './css.js'; import { csvPlugin } from './csv.js'; +import { dataPlugin } from './data.js'; import { dsvPlugin } from './dsv.js'; import { googleFontsPlugin } from './google-fonts.js'; import { dropdownPlugin } from './dropdown.js'; import { imagePlugin } from './image.js'; -import { jsonDataPlugin } from './json-data.js'; import { mermaidPlugin } from './mermaid.js'; import { numberPlugin } from './number.js'; import { placeholdersPlugin } from './placeholders.js'; @@ -31,11 +31,11 @@ export function registerNativePlugins() { registerMarkdownPlugin(commentPlugin); registerMarkdownPlugin(cssPlugin); registerMarkdownPlugin(csvPlugin); + registerMarkdownPlugin(dataPlugin); registerMarkdownPlugin(dsvPlugin); registerMarkdownPlugin(googleFontsPlugin); registerMarkdownPlugin(dropdownPlugin); registerMarkdownPlugin(imagePlugin); - registerMarkdownPlugin(jsonDataPlugin); registerMarkdownPlugin(mermaidPlugin); registerMarkdownPlugin(numberPlugin); registerMarkdownPlugin(placeholdersPlugin); diff --git a/packages/markdown/src/plugins/interfaces.ts b/packages/markdown/src/plugins/interfaces.ts index e45ad04f..29281caf 100644 --- a/packages/markdown/src/plugins/interfaces.ts +++ b/packages/markdown/src/plugins/interfaces.ts @@ -4,10 +4,10 @@ */ export { CheckboxSpec } from './checkbox.js'; export { CsvSpec } from './csv.js'; +export { JsonDataSpec } from './data.js'; export { DropdownSpec } from './dropdown.js'; export { DsvSpec } from './dsv.js'; export { ImageSpec } from './image.js'; -export { JsonDataSpec } from './json-data.js'; export { MermaidSpec } from './mermaid.js'; export { NumberSpec } from './number.js'; export { PresetsSpec } from './presets.js'; @@ -22,11 +22,11 @@ export type PluginNames = 'css' | 'csv' | 'checkbox' | + 'data' | 'dropdown' | 'dsv' | 'image' | 'google-fonts' | - 'json-data' | 'mermaid' | 'number' | 'placeholders' | From 7f22663920317693d9f92f83011f9dff6ce8a3bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 22:36:04 +0000 Subject: [PATCH 09/47] Fix plugin name extraction to use infoWords instead of slice Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/markdown/src/factory.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/markdown/src/factory.ts b/packages/markdown/src/factory.ts index c435b73c..14ca1f4d 100644 --- a/packages/markdown/src/factory.ts +++ b/packages/markdown/src/factory.ts @@ -152,28 +152,28 @@ export function create() { if (directPlugin) { return directPlugin; } + + // Split info into words for further processing + const infoWords = info.split(/\s+/); + // Third priority: Check for plugin names with additional parameters (like "csv variableId") - else { - const infoWords = info.split(/\s+/); - if (infoWords.length > 0) { - const pluginPrefix = findPluginByPrefix(infoWords[0]); - if (pluginPrefix) { - return pluginPrefix; - } + if (infoWords.length > 0) { + const pluginPrefix = findPluginByPrefix(infoWords[0]); + if (pluginPrefix) { + return pluginPrefix; } } + // Fourth priority: Check if it starts with "json " and extract the plugin name - if (info.startsWith('json ')) { - const jsonPluginName = info.slice(5).trim(); - const jsonPlugin = findPlugin(jsonPluginName); + if (info.startsWith('json ') && infoWords.length > 1) { + const jsonPlugin = findPlugin(infoWords[1]); if (jsonPlugin) { return jsonPlugin; } } // Fifth priority: Check if it starts with "yaml " and extract the plugin name - else if (info.startsWith('yaml ')) { - const yamlPluginName = info.slice(5).trim(); - const yamlPlugin = findPlugin(yamlPluginName); + else if (info.startsWith('yaml ') && infoWords.length > 1) { + const yamlPlugin = findPlugin(infoWords[1]); if (yamlPlugin) { return yamlPlugin; } From 7bede40164b7ed0551b0e946f1c74941dc4609bc Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Fri, 24 Oct 2025 16:09:26 -0700 Subject: [PATCH 10/47] Preserve signal bus state during reset by carrying over logLevel and logWatchIds --- packages/markdown/src/renderer.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/markdown/src/renderer.ts b/packages/markdown/src/renderer.ts index 27d515bf..62866a02 100644 --- a/packages/markdown/src/renderer.ts +++ b/packages/markdown/src/renderer.ts @@ -181,12 +181,18 @@ export class Renderer { } reset() { + //carry old values over + const { logLevel, logWatchIds } = this.signalBus; //cancel the old signal bus, which may have active listeners this.signalBus.deactivate(); //create a new signal bus this.signalBus = new SignalBus(defaultCommonOptions.dataSignalPrefix!); + + //restore old values + this.signalBus.logLevel = logLevel; + this.signalBus.logWatchIds = logWatchIds; for (const pluginName of Object.keys(this.instances)) { const instances = this.instances[pluginName]; From 81560777792ba132ad3ce8e33cfabb9a17879ef5 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Fri, 24 Oct 2025 16:40:09 -0700 Subject: [PATCH 11/47] handle nullref --- packages/markdown/src/signalbus.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/markdown/src/signalbus.ts b/packages/markdown/src/signalbus.ts index ec7faf9a..fb43d9c4 100644 --- a/packages/markdown/src/signalbus.ts +++ b/packages/markdown/src/signalbus.ts @@ -91,7 +91,9 @@ export class SignalBus { //set current values for (const signalName in batch) { const signalDep = this.signalDeps[signalName]; - signalDep.value = batch[signalName].value; + if (signalDep) { + signalDep.value = batch[signalName].value; + } } if (this.broadcastingStack.length === 0) { From 194451d0509c9a987f921cdb12131a3c068ff0a6 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Fri, 24 Oct 2025 16:45:22 -0700 Subject: [PATCH 12/47] Fix plugin name extraction to check for 'json' keyword directly in infoWords --- packages/markdown/src/factory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markdown/src/factory.ts b/packages/markdown/src/factory.ts index 14ca1f4d..0567860d 100644 --- a/packages/markdown/src/factory.ts +++ b/packages/markdown/src/factory.ts @@ -165,7 +165,7 @@ export function create() { } // Fourth priority: Check if it starts with "json " and extract the plugin name - if (info.startsWith('json ') && infoWords.length > 1) { + if (infoWords[0] === 'json' && infoWords.length > 1) { const jsonPlugin = findPlugin(infoWords[1]); if (jsonPlugin) { return jsonPlugin; From 99e01cf86887baa01291ed306f6e9c231c79b37d Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Sun, 26 Oct 2025 22:56:46 -0700 Subject: [PATCH 13/47] receive initial batches without running --- packages/markdown/src/plugins/vega.ts | 31 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/markdown/src/plugins/vega.ts b/packages/markdown/src/plugins/vega.ts index e179832b..e7fc6884 100644 --- a/packages/markdown/src/plugins/vega.ts +++ b/packages/markdown/src/plugins/vega.ts @@ -28,6 +28,7 @@ interface VegaInstance extends SpecInit { batch?: Batch; dataSignals: string[]; needToRun?: boolean; + isListening?: boolean; } const pluginName: PluginNames = 'vega'; @@ -113,17 +114,23 @@ export const vegaPlugin: Plugin = { receiveBatch: async (batch, from) => { signalBus.log(vegaInstance.id, 'received batch', batch, from); return new Promise(resolve => { - view.runAfter(async () => { - if (receiveBatch(batch, signalBus, vegaInstance)) { - signalBus.log(vegaInstance.id, 'running after _pulse, changes from', from); - view.resize(); - vegaInstance.needToRun = true; - } else { - signalBus.log(vegaInstance.id, 'no changes'); - } - signalBus.log(vegaInstance.id, 'running view after _pulse finished'); + if (vegaInstance.isListening) { + view.runAfter(async () => { + if (receiveBatch(batch, signalBus, vegaInstance)) { + signalBus.log(vegaInstance.id, 'running after _pulse, changes from', from); + view.resize(); + vegaInstance.needToRun = true; + } else { + signalBus.log(vegaInstance.id, 'no changes'); + } + signalBus.log(vegaInstance.id, 'running view after _pulse finished'); + resolve(); + }); + } else { + //not yet listening, so just receive the batch without running + receiveBatch(batch, signalBus, vegaInstance); resolve(); - }); + } }); }, broadcastComplete: async () => { @@ -180,6 +187,8 @@ export const vegaPlugin: Plugin = { //signalBus.log(vegaInstance.id, 'not listening to signal, no match', signalName); } } + vegaInstance.isListening = true; + view.runAsync(); }, getCurrentSignalValue: (signalName: string) => { const matchSignal = spec.signals?.find(signal => signal.name === signalName); @@ -219,7 +228,7 @@ function receiveBatch(batch: Batch, signalBus: SignalBus, vegaInstance: VegaInst logReason = 'not updating data, no match'; } else { logReason = 'updating data'; - + // Use structuredClone to ensure deep copy // vega may efficiently have symbols on data to cache a datum's values // so this needs to appear to be new data From 687d3b4e410b88ecfe17103dcbf31f9b6f67ae65 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Sun, 26 Oct 2025 22:56:56 -0700 Subject: [PATCH 14/47] Optimize initial batch handling in SignalBus by reusing the constructed initialBatch object for all peers. --- packages/markdown/src/signalbus.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/markdown/src/signalbus.ts b/packages/markdown/src/signalbus.ts index fb43d9c4..9ec69334 100644 --- a/packages/markdown/src/signalbus.ts +++ b/packages/markdown/src/signalbus.ts @@ -145,14 +145,15 @@ export class SignalBus { //set the initial batch on each peer this.log('beginListening', 'begin initial batch', this.signalDeps); + const initialBatch: Batch = {}; + for (const signalName in this.signalDeps) { + const signalDep = this.signalDeps[signalName]; + const { value, isData } = signalDep; + initialBatch[signalName] = { value, isData }; + } + for (const peer of this.peers) { - const batch: Batch = {}; - for (const signalName in this.signalDeps) { - const signalDep = this.signalDeps[signalName]; - const { value, isData } = signalDep; - batch[signalName] = { value, isData }; - } - peer.receiveBatch && peer.receiveBatch(batch, 'initial'); + peer.receiveBatch && peer.receiveBatch(initialBatch, 'initial'); } //need to call broadcast complete to ensure that all peers have the initial values From f93a424abfd41a0ed25ced019cb5dcf7785d250a Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Sun, 26 Oct 2025 22:58:42 -0700 Subject: [PATCH 15/47] remove json data examples --- .../features/json-complete-example.idoc.json | 99 ------------------- .../features/json-dataloader-test.idoc.json | 30 ------ .../json/features/json-plugin-test.idoc.json | 29 ------ 3 files changed, 158 deletions(-) delete mode 100644 packages/web-deploy/json/features/json-complete-example.idoc.json delete mode 100644 packages/web-deploy/json/features/json-dataloader-test.idoc.json delete mode 100644 packages/web-deploy/json/features/json-plugin-test.idoc.json diff --git a/packages/web-deploy/json/features/json-complete-example.idoc.json b/packages/web-deploy/json/features/json-complete-example.idoc.json deleted file mode 100644 index b8d18566..00000000 --- a/packages/web-deploy/json/features/json-complete-example.idoc.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "$schema": "../../../../docs/schema/idoc_v1.json", - "title": "JSON Data Plugin - Complete Example", - "description": "Demonstrates the new JSON plugin for inline JSON data in markdown fence blocks", - "dataLoaders": [ - { - "dataSourceName": "companiesFromDataLoader", - "type": "inline", - "format": "json", - "content": [ - {"company": "TechCorp", "industry": "Technology", "revenue": 5000000, "employees": 250}, - {"company": "HealthPlus", "industry": "Healthcare", "revenue": 3200000, "employees": 180}, - {"company": "EduLearn", "industry": "Education", "revenue": 1500000, "employees": 95} - ] - } - ], - "groups": [ - { - "groupId": "intro", - "elements": [ - "# JSON Data Plugin", - "", - "This document demonstrates the **JSON plugin** which allows you to embed JSON data directly in markdown fence blocks, similar to how CSV, TSV, and DSV plugins work.", - "", - "## Benefits", - "- **Structured Data**: JSON supports complex nested structures, booleans, numbers, and more", - "- **Type Safety**: Preserves data types (numbers stay as numbers, booleans as booleans)", - "- **Readability**: JSON format is widely understood and easy to read", - "- **Compatibility**: Works seamlessly with existing Chartifact components like tables and charts" - ] - }, - { - "groupId": "fence_example", - "elements": [ - "## Example 1: JSON Fence Block", - "", - "You can provide JSON data using a markdown fence block with the `json data` keywords followed by a variable name:", - "", - "```json data products\n[\n {\"name\": \"Laptop\", \"category\": \"Electronics\", \"price\": 1299.99, \"inStock\": true, \"rating\": 4.5},\n {\"name\": \"Desk Chair\", \"category\": \"Furniture\", \"price\": 349.99, \"inStock\": true, \"rating\": 4.8},\n {\"name\": \"Monitor\", \"category\": \"Electronics\", \"price\": 499.99, \"inStock\": false, \"rating\": 4.6},\n {\"name\": \"Keyboard\", \"category\": \"Electronics\", \"price\": 129.99, \"inStock\": true, \"rating\": 4.7},\n {\"name\": \"Standing Desk\", \"category\": \"Furniture\", \"price\": 799.99, \"inStock\": true, \"rating\": 4.9}\n]\n```", - "", - "### Products Table", - { - "type": "tabulator", - "dataSourceName": "products", - "variableId": "selectedProduct", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "250px", - "selectable": 1 - } - }, - "", - "**Selected Product:** {{selectedProduct[0].name}} - ${{selectedProduct[0].price}} (Rating: {{selectedProduct[0].rating}}⭐)" - ] - }, - { - "groupId": "dataloader_example", - "elements": [ - "## Example 2: JSON in DataLoaders", - "", - "You can also use JSON format in the `dataLoaders` section with an object array:", - "", - "### Companies Table", - { - "type": "tabulator", - "dataSourceName": "companiesFromDataLoader", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } - } - ] - }, - { - "groupId": "comparison", - "elements": [ - "## Comparison: JSON vs CSV", - "", - "### JSON Format", - "```json data jsonExample\n[\n {\"item\": \"Widget\", \"quantity\": 100, \"available\": true},\n {\"item\": \"Gadget\", \"quantity\": 50, \"available\": false}\n]\n```", - "", - "### CSV Format", - "```csv csvExample\nitem,quantity,available\nWidget,100,true\nGadget,50,false\n```", - "", - "### JSON Advantages:", - "- **Native data types**: Numbers and booleans don't need parsing", - "- **Complex structures**: Can handle nested objects and arrays", - "- **No escaping issues**: Special characters don't need escape sequences", - "", - "### CSV Advantages:", - "- **Compact**: More space-efficient for simple tabular data", - "- **Widely supported**: Easy to import/export from spreadsheets", - "- **Human-readable**: Simpler format for basic tables" - ] - } - ] -} diff --git a/packages/web-deploy/json/features/json-dataloader-test.idoc.json b/packages/web-deploy/json/features/json-dataloader-test.idoc.json deleted file mode 100644 index a4940ad2..00000000 --- a/packages/web-deploy/json/features/json-dataloader-test.idoc.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "../../../../docs/schema/idoc_v1.json", - "title": "JSON Plugin - DataLoader String Format", - "dataLoaders": [ - { - "dataSourceName": "employees", - "type": "inline", - "format": "json", - "content": "[\n {\"name\": \"Alice Johnson\", \"department\": \"Engineering\", \"salary\": 95000},\n {\"name\": \"Bob Smith\", \"department\": \"Sales\", \"salary\": 75000},\n {\"name\": \"Carol White\", \"department\": \"Engineering\", \"salary\": 105000},\n {\"name\": \"David Brown\", \"department\": \"Marketing\", \"salary\": 68000}\n]" - } - ], - "groups": [ - { - "groupId": "dataloader_test", - "elements": [ - "## JSON DataLoader (String Format)", - "This example tests using JSON format in dataLoaders with string content.", - { - "type": "tabulator", - "dataSourceName": "employees", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } - } - ] - } - ] -} diff --git a/packages/web-deploy/json/features/json-plugin-test.idoc.json b/packages/web-deploy/json/features/json-plugin-test.idoc.json deleted file mode 100644 index 18f8fbf9..00000000 --- a/packages/web-deploy/json/features/json-plugin-test.idoc.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "../../../../docs/schema/idoc_v1.json", - "title": "JSON Plugin Test", - "groups": [ - { - "groupId": "json_test", - "elements": [ - "## Testing JSON Plugin", - "This example tests the new JSON plugin with inline JSON data.", - "", - "```json data books\n[\n {\"title\": \"The Great Gatsby\", \"author\": \"F. Scott Fitzgerald\", \"year\": 1925, \"rating\": 4.5},\n {\"title\": \"To Kill a Mockingbird\", \"author\": \"Harper Lee\", \"year\": 1960, \"rating\": 4.8},\n {\"title\": \"1984\", \"author\": \"George Orwell\", \"year\": 1949, \"rating\": 4.7},\n {\"title\": \"Pride and Prejudice\", \"author\": \"Jane Austen\", \"year\": 1813, \"rating\": 4.6}\n]\n```", - { - "type": "tabulator", - "dataSourceName": "books", - "variableId": "selectedBooks", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "300px", - "selectable": 1 - } - }, - "", - "### Selected Book", - "Selected: **{{selectedBooks[0].title}}** by {{selectedBooks[0].author}} ({{selectedBooks[0].year}})" - ] - } - ] -} From 69ab583154274265bcc6d3624f904b352b4f4ebb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 06:24:53 +0000 Subject: [PATCH 16/47] Add YAML support to data plugin Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/markdown/src/plugins/data.ts | 48 ++++++++++++------- .../json/features/yaml-data-test.idoc.json | 29 +++++++++++ 2 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 packages/web-deploy/json/features/yaml-data-test.idoc.json diff --git a/packages/markdown/src/plugins/data.ts b/packages/markdown/src/plugins/data.ts index 260b17f6..b5cadc81 100644 --- a/packages/markdown/src/plugins/data.ts +++ b/packages/markdown/src/plugins/data.ts @@ -9,6 +9,7 @@ import { pluginClassName } from './util.js'; import { PluginNames } from './interfaces.js'; import { SpecReview } from 'common'; import { parseVariableId } from './dsv.js'; +import * as yaml from 'js-yaml'; interface JsonDataInstance { id: string; @@ -46,32 +47,37 @@ export const dataPlugin: Plugin = { const content = token.content.trim(); const info = token.info.trim(); - // Parse the fence info - expect "json data variableId" format - // The factory.ts will route "json data ..." to this plugin + // Parse the fence info - expect "json data variableId" or "yaml data variableId" format + // The factory.ts will route "json data ..." or "yaml data ..." to this plugin const parts = info.split(/\s+/); - // Check for variable ID (should be after "json data") + // Check for variable ID (should be after "json data" or "yaml data") let variableId: string; let wasDefaultId = false; + let isYaml = false; - if (parts.length >= 3 && parts[0] === 'json' && parts[1] === 'data') { - // Format: json data variableId + if (parts.length >= 3 && (parts[0] === 'json' || parts[0] === 'yaml') && parts[1] === 'data') { + // Format: json data variableId or yaml data variableId variableId = parts[2]; - } else if (parts.length >= 2 && parts[0] === 'json' && parts[1] === 'data') { - // Format: json data (no variable ID provided) - variableId = `jsonData${index}`; + isYaml = parts[0] === 'yaml'; + } else if (parts.length >= 2 && (parts[0] === 'json' || parts[0] === 'yaml') && parts[1] === 'data') { + // Format: json data or yaml data (no variable ID provided) + variableId = `${parts[0]}Data${index}`; wasDefaultId = true; + isYaml = parts[0] === 'yaml'; } else { // Not the expected format return ''; } - // Use script tag with application/json type instead of pre tag + // Use script tag with application/json type for storage + // Note: We store both JSON and YAML data as JSON in the script tag const scriptElement = sanitizedScriptTag(content, { id: `${pluginName}-${index}`, class: className, 'data-variable-id': variableId, - 'data-was-default-id': wasDefaultId.toString() + 'data-was-default-id': wasDefaultId.toString(), + 'data-format': isYaml ? 'yaml' : 'json' }); return scriptElement.outerHTML; @@ -133,16 +139,26 @@ export const dataPlugin: Plugin = { try { const content = container.textContent?.trim(); if (!content) { - errorHandler(new Error('No JSON content found'), pluginName, index, 'parse', container); + errorHandler(new Error('No data content found'), pluginName, index, 'parse', container); continue; } const spec: JsonDataSpec = specReview.approvedSpec; + const format = container.getAttribute('data-format') || 'json'; - // Parse JSON content + // Parse JSON or YAML content let data: object[]; try { - const parsed = JSON.parse(content); + let parsed: any; + + if (format === 'yaml') { + // Parse YAML + parsed = yaml.load(content); + } else { + // Parse JSON + parsed = JSON.parse(content); + } + // Ensure data is an array if (Array.isArray(parsed)) { data = parsed; @@ -150,9 +166,9 @@ export const dataPlugin: Plugin = { // If it's a single object, wrap it in an array data = [parsed]; } - } catch (jsonError) { + } catch (parseError) { errorHandler( - new Error(`Invalid JSON: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`), + new Error(`Invalid ${format.toUpperCase()}: ${parseError instanceof Error ? parseError.message : String(parseError)}`), pluginName, index, 'parse', @@ -169,7 +185,7 @@ export const dataPlugin: Plugin = { jsonInstances.push(jsonInstance); // Add a safe comment before the container to show that data was loaded - const comment = sanitizeHtmlComment(`JSON data loaded: ${data.length} rows for variable '${spec.variableId}'`); + const comment = sanitizeHtmlComment(`${format.toUpperCase()} data loaded: ${data.length} rows for variable '${spec.variableId}'`); container.insertAdjacentHTML('beforebegin', comment); } catch (e) { diff --git a/packages/web-deploy/json/features/yaml-data-test.idoc.json b/packages/web-deploy/json/features/yaml-data-test.idoc.json new file mode 100644 index 00000000..dbc68671 --- /dev/null +++ b/packages/web-deploy/json/features/yaml-data-test.idoc.json @@ -0,0 +1,29 @@ +{ + "$schema": "../../../../docs/schema/idoc_v1.json", + "title": "YAML Data Plugin Test", + "groups": [ + { + "groupId": "yaml_test", + "elements": [ + "## Testing YAML Data Plugin", + "This example tests the data plugin with YAML format.", + "", + "```yaml data people\n- name: Alice\n age: 25\n city: New York\n\n- name: Bob\n age: 30\n city: Chicago\n\n- name: Carol\n age: 35\n city: Los Angeles\n\n- name: David\n age: 28\n city: Seattle\n```", + { + "type": "tabulator", + "dataSourceName": "people", + "variableId": "selectedPerson", + "tabulatorOptions": { + "autoColumns": true, + "layout": "fitColumns", + "maxHeight": "300px", + "selectable": 1 + } + }, + "", + "### Selected Person", + "Selected: **{{selectedPerson[0].name}}** from {{selectedPerson[0].city}} ({{selectedPerson[0].age}} years old)" + ] + } + ] +} From 58fbd35ffa1464d98607f4bd5d927dc22210e254 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Mon, 27 Oct 2025 08:59:37 -0700 Subject: [PATCH 17/47] Refactor Mermaid diagram rendering to use MermaidInstance for improved state management and eliminate redundant parameters --- packages/markdown/src/plugins/mermaid.ts | 35 ++++++++++++++++-------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/markdown/src/plugins/mermaid.ts b/packages/markdown/src/plugins/mermaid.ts index c8e566e7..98ef923c 100644 --- a/packages/markdown/src/plugins/mermaid.ts +++ b/packages/markdown/src/plugins/mermaid.ts @@ -61,6 +61,7 @@ interface MermaidInstance { id: string; spec: MermaidElementProps; container: Element; + renderingDiagram: string; lastRenderedDiagram: string; signals: Record; tokens: TemplateToken[]; @@ -176,7 +177,6 @@ export const mermaidPlugin: Plugin = { // Determine format from token info (like flaggablePlugin does) const info = token.info.trim(); const isYaml = info.startsWith('yaml '); - const formatName = isYaml ? 'YAML' : 'JSON'; // Try to parse as YAML or JSON based on format try { @@ -186,7 +186,7 @@ export const mermaidPlugin: Plugin = { } else { parsed = JSON.parse(content); } - + if (parsed && typeof parsed === 'object') { spec = parsed as MermaidSpec; } else { @@ -231,14 +231,13 @@ export const mermaidPlugin: Plugin = { container, signals: {}, tokens, + renderingDiagram: null, lastRenderedDiagram: null, }; mermaidInstances.push(mermaidInstance); // For raw text mode, render immediately - if (spec.diagramText && typeof spec.diagramText === 'string') { - await renderRawDiagram(mermaidInstance.id, mermaidInstance.container, spec.diagramText, errorHandler, pluginName, index); - } + await renderRawDiagram(mermaidInstance, spec.diagramText, errorHandler, pluginName, index); } const instances = mermaidInstances.map((mermaidInstance, index): IInstance => { @@ -300,10 +299,7 @@ export const mermaidPlugin: Plugin = { } } - if (diagramText && mermaidInstance.lastRenderedDiagram !== diagramText) { - await renderRawDiagram(mermaidInstance.id, mermaidInstance.container, diagramText, errorHandler, pluginName, index); - mermaidInstance.lastRenderedDiagram = diagramText; - } + await renderRawDiagram(mermaidInstance, diagramText, errorHandler, pluginName, index); } else { mermaidInstance.container.innerHTML = '
No data available to render diagram
'; } @@ -313,8 +309,7 @@ export const mermaidPlugin: Plugin = { if (typeof value === 'string' && value.trim().length > 0) { // Render raw Mermaid text from variable - await renderRawDiagram(mermaidInstance.id, mermaidInstance.container, value, errorHandler, pluginName, index); - mermaidInstance.lastRenderedDiagram = value; + await renderRawDiagram(mermaidInstance, value, errorHandler, pluginName, index); } else { // Clear container if variable is empty mermaidInstance.container.innerHTML = '
No diagram to display
'; @@ -336,11 +331,25 @@ function isValidMermaid(diagramText: string) { return lines.length > 1 && lines[1].trim().length > 0; } -async function renderRawDiagram(id: string, container: Element, diagramText: string, errorHandler: ErrorHandler, pluginName: string, index: number) { +async function renderRawDiagram(mermaidInstance: MermaidInstance, diagramText: string, errorHandler: ErrorHandler, pluginName: string, index: number) { + + if (!diagramText || typeof diagramText !== 'string' || diagramText.trim().length === 0) { + return; + } + + if (mermaidInstance.renderingDiagram === diagramText) { + // Already rendering this diagram + return; + } + + mermaidInstance.renderingDiagram = diagramText; + if (typeof mermaid === 'undefined') { await loadMermaidFromCDN(); } + const { id, container } = mermaidInstance; + if (typeof mermaid === 'undefined') { container.innerHTML = '
Mermaid library not loaded dynamically
'; return; @@ -354,6 +363,8 @@ async function renderRawDiagram(id: string, container: Element, diagramText: str try { const { svg } = await mermaid.render(id, diagramText); container.innerHTML = svg; + mermaidInstance.lastRenderedDiagram = diagramText; + mermaidInstance.renderingDiagram = null; } catch (error) { container.innerHTML = `
Failed to render diagram ${id}
${diagramText}
`; errorHandler(error instanceof Error ? error : new Error(String(error)), pluginName, index, 'render', container); From 78ba3d55caf9cdefe6640245a5e4979ee8d678d9 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Mon, 27 Oct 2025 09:01:08 -0700 Subject: [PATCH 18/47] Improve batch handling in Vega plugin by setting needToRun flag based on receiveBatch result --- packages/markdown/src/plugins/vega.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/markdown/src/plugins/vega.ts b/packages/markdown/src/plugins/vega.ts index e7fc6884..37901e37 100644 --- a/packages/markdown/src/plugins/vega.ts +++ b/packages/markdown/src/plugins/vega.ts @@ -128,7 +128,9 @@ export const vegaPlugin: Plugin = { }); } else { //not yet listening, so just receive the batch without running - receiveBatch(batch, signalBus, vegaInstance); + if (receiveBatch(batch, signalBus, vegaInstance)) { + vegaInstance.needToRun = true; + } resolve(); } }); @@ -188,7 +190,10 @@ export const vegaPlugin: Plugin = { } } vegaInstance.isListening = true; - view.runAsync(); + if (vegaInstance.needToRun) { + view.runAsync(); + vegaInstance.needToRun = false; + } }, getCurrentSignalValue: (signalName: string) => { const matchSignal = spec.signals?.find(signal => signal.name === signalName); From 0a92c75c2970c4c170fcdfe836cdb111ed9e8d21 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 02:02:47 +0000 Subject: [PATCH 19/47] Revert UMD build artifacts to exclude from PR Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- docs/dist/v1/chartifact.compiler.umd.js | 4 - docs/dist/v1/chartifact.editor.umd.js | 4 - docs/dist/v1/chartifact.host.umd.js | 4 - docs/dist/v1/chartifact.markdown.umd.js | 234 +++++------------------- 4 files changed, 46 insertions(+), 200 deletions(-) diff --git a/docs/dist/v1/chartifact.compiler.umd.js b/docs/dist/v1/chartifact.compiler.umd.js index 333b7c24..6c956f2f 100644 --- a/docs/dist/v1/chartifact.compiler.umd.js +++ b/docs/dist/v1/chartifact.compiler.umd.js @@ -1057,10 +1057,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy inlineDataMd = tickWrap(`dsv delimiter:${delimiter} variableId:${ds_raw}`, content); break; } - case "json": { - inlineDataMd = tickWrap(`json ${ds_raw}`, content); - break; - } default: { console.warn(`Unsupported inline data format: ${dataSource.format}, type is ${typeof dataSource.content}`); break; diff --git a/docs/dist/v1/chartifact.editor.umd.js b/docs/dist/v1/chartifact.editor.umd.js index 0d50b9b7..adb27c31 100644 --- a/docs/dist/v1/chartifact.editor.umd.js +++ b/docs/dist/v1/chartifact.editor.umd.js @@ -1041,10 +1041,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy inlineDataMd = tickWrap(`dsv delimiter:${delimiter} variableId:${ds_raw}`, content); break; } - case "json": { - inlineDataMd = tickWrap(`json ${ds_raw}`, content); - break; - } default: { console.warn(`Unsupported inline data format: ${dataSource.format}, type is ${typeof dataSource.content}`); break; diff --git a/docs/dist/v1/chartifact.host.umd.js b/docs/dist/v1/chartifact.host.umd.js index ea39762a..681d27c9 100644 --- a/docs/dist/v1/chartifact.host.umd.js +++ b/docs/dist/v1/chartifact.host.umd.js @@ -1041,10 +1041,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy inlineDataMd = tickWrap(`dsv delimiter:${delimiter} variableId:${ds_raw}`, content); break; } - case "json": { - inlineDataMd = tickWrap(`json ${ds_raw}`, content); - break; - } default: { console.warn(`Unsupported inline data format: ${dataSource.format}, type is ${typeof dataSource.content}`); break; diff --git a/docs/dist/v1/chartifact.markdown.umd.js b/docs/dist/v1/chartifact.markdown.umd.js index c28f3d0b..ef28a70b 100644 --- a/docs/dist/v1/chartifact.markdown.umd.js +++ b/docs/dist/v1/chartifact.markdown.umd.js @@ -628,10 +628,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy loading: "0.1", error: "0.5" }; - const pluginName$h = "image"; - const className$f = pluginClassName(pluginName$h); + const pluginName$g = "image"; + const className$e = pluginClassName(pluginName$g); const imagePlugin = { - ...flaggablePlugin(pluginName$h, className$f), + ...flaggablePlugin(pluginName$g, className$e), hydrateComponent: async (renderer, errorHandler, specs) => { const imageInstances = []; for (let index2 = 0; index2 < specs.length; index2++) { @@ -646,11 +646,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy container, null, (error) => { - errorHandler(error, pluginName$h, index2, "load", container, img.src); + errorHandler(error, pluginName$g, index2, "load", container, img.src); } ); const imageInstance = { - id: `${pluginName$h}-${index2}`, + id: `${pluginName$g}-${index2}`, spec, img: null, // Will be set below @@ -708,7 +708,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy if (isSafeImageUrl(src)) { tempImg.setAttribute("src", src); } else { - errorHandler(new Error(`Unsafe image URL: ${src}`), pluginName$h, instanceIndex, "load", null, src); + errorHandler(new Error(`Unsafe image URL: ${src}`), pluginName$g, instanceIndex, "load", null, src); } } tempImg.setAttribute("alt", alt); @@ -827,10 +827,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy } return null; } - const pluginName$g = "placeholders"; - const imageClassName = pluginClassName(pluginName$g + "_image"); + const pluginName$f = "placeholders"; + const imageClassName = pluginClassName(pluginName$f + "_image"); const placeholdersPlugin = { - name: pluginName$g, + name: pluginName$f, initializePlugin: async (md) => { md.use(function(md2) { md2.inline.ruler.after("emphasis", "dynamic_placeholder", function(state, silent) { @@ -951,7 +951,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy for (const element of Array.from(dynamicImages)) { const { dynamicUrl, img } = createImageLoadingLogic(element, null, (error) => { const index2 = -1; - errorHandler(error, pluginName$g, index2, "load", element, img.src); + errorHandler(error, pluginName$f, index2, "load", element, img.src); }); if (!dynamicUrl) { continue; @@ -976,7 +976,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy }); const instances = [ { - id: pluginName$g, + id: pluginName$f, initialSignals, receiveBatch: async (batch) => { var _a, _b; @@ -1099,9 +1099,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy } if (info.startsWith("json ")) { const jsonPluginName = info.slice(5).trim(); - const jsonPlugin2 = findPlugin(jsonPluginName); - if (jsonPlugin2) { - return jsonPlugin2; + const jsonPlugin = findPlugin(jsonPluginName); + if (jsonPlugin) { + return jsonPlugin; } } else if (info.startsWith("yaml ")) { const yamlPluginName = info.slice(5).trim(); @@ -1123,10 +1123,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy }; return md; } - const pluginName$f = "checkbox"; - const className$e = pluginClassName(pluginName$f); + const pluginName$e = "checkbox"; + const className$d = pluginClassName(pluginName$e); const checkboxPlugin = { - ...flaggablePlugin(pluginName$f, className$e), + ...flaggablePlugin(pluginName$e, className$d), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const checkboxInstances = []; @@ -1147,7 +1147,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy `; container.innerHTML = html; const element = container.querySelector('input[type="checkbox"]'); - const checkboxInstance = { id: `${pluginName$f}-${index2}`, spec, element }; + const checkboxInstance = { id: `${pluginName$e}-${index2}`, spec, element }; checkboxInstances.push(checkboxInstance); } const instances = checkboxInstances.map((checkboxInstance) => { @@ -1190,9 +1190,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy return instances; } }; - const pluginName$e = "#"; + const pluginName$d = "#"; const commentPlugin = { - name: pluginName$e, + name: pluginName$d, fence: (token) => { const content = token.content.trim(); return sanitizeHtmlComment(content); @@ -1421,14 +1421,14 @@ ${reconstitutedRules.join("\n\n")} } return result; } - const pluginName$d = "css"; - const className$d = pluginClassName(pluginName$d); + const pluginName$c = "css"; + const className$c = pluginClassName(pluginName$c); const cssPlugin = { - ...flaggablePlugin(pluginName$d, className$d), + ...flaggablePlugin(pluginName$c, className$c), fence: (token, index2) => { const cssContent = token.content.trim(); const categorizedCss = categorizeCss(cssContent); - return sanitizedHTML("div", { id: `${pluginName$d}-${index2}`, class: className$d }, JSON.stringify(categorizedCss), true); + return sanitizedHTML("div", { id: `${pluginName$c}-${index2}`, class: className$c }, JSON.stringify(categorizedCss), true); }, hydrateComponent: async (renderer, errorHandler, specs) => { const cssInstances = []; @@ -1451,7 +1451,7 @@ ${reconstitutedRules.join("\n\n")} target.appendChild(styleElement); comments.push(``); cssInstances.push({ - id: `${pluginName$d}-${index2}`, + id: `${pluginName$c}-${index2}`, element: styleElement }); } else { @@ -1528,18 +1528,18 @@ ${reconstitutedRules.join("\n\n")} } return result; } - const pluginName$c = "dsv"; - const className$c = pluginClassName(pluginName$c); + const pluginName$b = "dsv"; + const className$b = pluginClassName(pluginName$b); const dsvPlugin = { - name: pluginName$c, + name: pluginName$b, fence: (token, index2) => { const content = token.content.trim(); const info = token.info.trim(); const { delimiter, wasDefaultDelimiter } = parseDelimiter(info); const { variableId, wasDefaultId } = parseVariableId(info, "dsv", index2); return sanitizedHTML("pre", { - id: `${pluginName$c}-${index2}`, - class: className$c, + id: `${pluginName$b}-${index2}`, + class: className$b, style: "display:none", "data-variable-id": variableId, "data-delimiter": delimiter, @@ -1550,7 +1550,7 @@ ${reconstitutedRules.join("\n\n")} hydrateSpecs: (renderer, errorHandler) => { var _a; const flagged = []; - const containers = renderer.element.querySelectorAll(`.${className$c}`); + const containers = renderer.element.querySelectorAll(`.${className$b}`); for (const [index2, container] of Array.from(containers).entries()) { try { const variableId = container.getAttribute("data-variable-id"); @@ -1558,18 +1558,18 @@ ${reconstitutedRules.join("\n\n")} const wasDefaultId = container.getAttribute("data-was-default-id") === "true"; const wasDefaultDelimiter = container.getAttribute("data-was-default-delimiter") === "true"; if (!variableId) { - errorHandler(new Error("No variable ID found"), pluginName$c, index2, "parse", container); + errorHandler(new Error("No variable ID found"), pluginName$b, index2, "parse", container); continue; } if (!delimiter) { - errorHandler(new Error("No delimiter found"), pluginName$c, index2, "parse", container); + errorHandler(new Error("No delimiter found"), pluginName$b, index2, "parse", container); continue; } const spec = { variableId, delimiter, wasDefaultId, wasDefaultDelimiter }; const flaggableSpec = inspectDsvSpec(spec); const f = { approvedSpec: null, - pluginName: pluginName$c, + pluginName: pluginName$b, containerId: container.id }; if (flaggableSpec.hasFlags) { @@ -1580,7 +1580,7 @@ ${reconstitutedRules.join("\n\n")} } flagged.push(f); } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$c, index2, "parse", container); + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); } } return flagged; @@ -1596,19 +1596,19 @@ ${reconstitutedRules.join("\n\n")} } const container = renderer.element.querySelector(`#${specReview.containerId}`); if (!container) { - errorHandler(new Error("Container not found"), pluginName$c, index2, "init", null); + errorHandler(new Error("Container not found"), pluginName$b, index2, "init", null); continue; } try { const content = (_a = container.textContent) == null ? void 0 : _a.trim(); if (!content) { - errorHandler(new Error("No DSV content found"), pluginName$c, index2, "parse", container); + errorHandler(new Error("No DSV content found"), pluginName$b, index2, "parse", container); continue; } const spec = specReview.approvedSpec; const data = vega.read(content, { type: "dsv", delimiter: spec.delimiter }); const dsvInstance = { - id: `${pluginName$c}-${index2}`, + id: `${pluginName$b}-${index2}`, spec, data }; @@ -1617,7 +1617,7 @@ ${reconstitutedRules.join("\n\n")} const comment = sanitizeHtmlComment(`${delimiterName} data loaded: ${data.length} rows for variable '${spec.variableId}'`); container.insertAdjacentHTML("beforebegin", comment); } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$c, index2, "parse", container); + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); } } const instances = dsvInstances.map((dsvInstance) => { @@ -1735,8 +1735,8 @@ ${reconstitutedRules.join("\n\n")} generateRule("hero", "h1"); return cssRules.join("\n\n"); } - const pluginName$b = "google-fonts"; - const className$b = pluginClassName(pluginName$b); + const pluginName$a = "google-fonts"; + const className$a = pluginClassName(pluginName$a); function inspectGoogleFontsSpec(spec) { var _a, _b; const reasons = []; @@ -1765,7 +1765,7 @@ ${reconstitutedRules.join("\n\n")} }; } const googleFontsPlugin = { - ...flaggablePlugin(pluginName$b, className$b, inspectGoogleFontsSpec), + ...flaggablePlugin(pluginName$a, className$a, inspectGoogleFontsSpec), hydrateComponent: async (renderer, errorHandler, specs) => { const googleFontsInstances = []; let emitted = false; @@ -1836,10 +1836,10 @@ ${reconstitutedRules.join("\n\n")} return instances; } }; - const pluginName$a = "dropdown"; - const className$a = pluginClassName(pluginName$a); + const pluginName$9 = "dropdown"; + const className$9 = pluginClassName(pluginName$9); const dropdownPlugin = { - ...flaggablePlugin(pluginName$a, className$a), + ...flaggablePlugin(pluginName$9, className$9), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const dropdownInstances = []; @@ -1862,7 +1862,7 @@ ${reconstitutedRules.join("\n\n")} container.innerHTML = html; const element = container.querySelector("select"); setSelectOptions(element, spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.multiple ? [] : "")); - const dropdownInstance = { id: `${pluginName$a}-${index2}`, spec, element }; + const dropdownInstance = { id: `${pluginName$9}-${index2}`, spec, element }; dropdownInstances.push(dropdownInstance); } const instances = dropdownInstances.map((dropdownInstance, index2) => { @@ -1986,147 +1986,6 @@ ${reconstitutedRules.join("\n\n")} selectElement.appendChild(optionElement); }); } - function inspectJsonSpec(spec) { - const result = { - spec, - hasFlags: false, - reasons: [] - }; - if (spec.wasDefaultId) { - result.hasFlags = true; - result.reasons.push("No variable ID specified - using default"); - } - return result; - } - const pluginName$9 = "json"; - const className$9 = pluginClassName(pluginName$9); - const jsonPlugin = { - name: pluginName$9, - fence: (token, index2) => { - const content = token.content.trim(); - const info = token.info.trim(); - const { variableId, wasDefaultId } = parseVariableId(info, "json", index2); - return sanitizedHTML("pre", { - id: `${pluginName$9}-${index2}`, - class: className$9, - style: "display:none", - "data-variable-id": variableId, - "data-was-default-id": wasDefaultId.toString() - }, content, false); - }, - hydrateSpecs: (renderer, errorHandler) => { - var _a; - const flagged = []; - const containers = renderer.element.querySelectorAll(`.${className$9}`); - for (const [index2, container] of Array.from(containers).entries()) { - try { - const variableId = container.getAttribute("data-variable-id"); - const wasDefaultId = container.getAttribute("data-was-default-id") === "true"; - if (!variableId) { - errorHandler(new Error("No variable ID found"), pluginName$9, index2, "parse", container); - continue; - } - const spec = { variableId, wasDefaultId }; - const flaggableSpec = inspectJsonSpec(spec); - const f = { - approvedSpec: null, - pluginName: pluginName$9, - containerId: container.id - }; - if (flaggableSpec.hasFlags) { - f.blockedSpec = flaggableSpec.spec; - f.reason = ((_a = flaggableSpec.reasons) == null ? void 0 : _a.join(", ")) || "Unknown reason"; - } else { - f.approvedSpec = flaggableSpec.spec; - } - flagged.push(f); - } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$9, index2, "parse", container); - } - } - return flagged; - }, - hydrateComponent: async (renderer, errorHandler, specs) => { - var _a; - const { signalBus } = renderer; - const jsonInstances = []; - for (let index2 = 0; index2 < specs.length; index2++) { - const specReview = specs[index2]; - if (!specReview.approvedSpec) { - continue; - } - const container = renderer.element.querySelector(`#${specReview.containerId}`); - if (!container) { - errorHandler(new Error("Container not found"), pluginName$9, index2, "init", null); - continue; - } - try { - const content = (_a = container.textContent) == null ? void 0 : _a.trim(); - if (!content) { - errorHandler(new Error("No JSON content found"), pluginName$9, index2, "parse", container); - continue; - } - const spec = specReview.approvedSpec; - let data; - try { - const parsed = JSON.parse(content); - if (Array.isArray(parsed)) { - data = parsed; - } else { - data = [parsed]; - } - } catch (jsonError) { - errorHandler( - new Error(`Invalid JSON: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`), - pluginName$9, - index2, - "parse", - container - ); - continue; - } - const jsonInstance = { - id: `${pluginName$9}-${index2}`, - spec, - data - }; - jsonInstances.push(jsonInstance); - const comment = sanitizeHtmlComment(`JSON data loaded: ${data.length} rows for variable '${spec.variableId}'`); - container.insertAdjacentHTML("beforebegin", comment); - } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$9, index2, "parse", container); - } - } - const instances = jsonInstances.map((jsonInstance) => { - const { spec, data } = jsonInstance; - const initialSignals = [{ - name: spec.variableId, - value: data, - priority: 1, - isData: true - }]; - return { - ...jsonInstance, - initialSignals, - beginListening() { - const batch = { - [spec.variableId]: { - value: data, - isData: true - } - }; - signalBus.broadcast(jsonInstance.id, batch); - }, - getCurrentSignalValue: () => { - return data; - }, - destroy: () => { - } - }; - }); - return instances; - } - }; const pluginName$8 = "mermaid"; const className$8 = pluginClassName(pluginName$8); function inspectMermaidSpec(spec) { @@ -4059,7 +3918,6 @@ ${reconstitutedRules.join("\n\n")} registerMarkdownPlugin(googleFontsPlugin); registerMarkdownPlugin(dropdownPlugin); registerMarkdownPlugin(imagePlugin); - registerMarkdownPlugin(jsonPlugin); registerMarkdownPlugin(mermaidPlugin); registerMarkdownPlugin(numberPlugin); registerMarkdownPlugin(placeholdersPlugin); From 06f6174a2a452394ab70cb8d39d4d1b176a397f6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:13:06 +0000 Subject: [PATCH 20/47] Revert loader.ts changes and add UMD file guidance to copilot instructions Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .github/copilot-instructions.md | 4 +++ packages/compiler/src/loader.ts | 43 +++++++-------------------------- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3d5f6e58..4940ea2b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -29,6 +29,10 @@ Chartifact consists of several interoperating modules: ### Testing - Currently we don't have much test coverage. We can add this later. +### Version Control +- **Do NOT check in UMD build artifacts**: Files matching `*.umd.js` in `docs/dist/v1/` are build outputs and should not be committed in PRs +- These files are generated during the build process and committing them creates unnecessary merge conflicts + ## Project-Specific Conventions 1. **File Formats**: diff --git a/packages/compiler/src/loader.ts b/packages/compiler/src/loader.ts index df382d42..0cb02d07 100644 --- a/packages/compiler/src/loader.ts +++ b/packages/compiler/src/loader.ts @@ -18,40 +18,15 @@ export function addStaticDataLoaderToSpec(vegaScope: VegaScope, dataSource: Data if (dataSource.type === 'inline' && dataSource.format === 'json') { - // For JSON format, always output as json data fence block - let content: string; - - // Stringify content if it's an object array - if (Array.isArray(dataSource.content) && typeof dataSource.content[0] === 'object') { - content = JSON.stringify(dataSource.content, null, 2); - } else if (typeof dataSource.content === 'string') { - content = dataSource.content; - } else { - console.warn(`Unsupported JSON content type: ${typeof dataSource.content}`); - return inlineDataMd; - } - - let ds_raw = dataSourceName; - - if (dataSource.dataFrameTransformations) { - ds_raw += '_raw'; - - const newData: SourceData = { - name: dataSourceName, - source: ds_raw, - transform: dataSource.dataFrameTransformations || [], - }; - spec.signals.push(dataAsSignal(dataSourceName)); - - spec.data.unshift(newData); - - //add a placeholder data since the transform depends on it - spec.data.unshift({ - name: ds_raw - }); - } - - inlineDataMd = tickWrap(`json data ${ds_raw}`, content); + const newData: ValuesData = { + name: dataSourceName, + values: dataSource.content as object[], + transform: dataSource.dataFrameTransformations || [], + }; + spec.signals.push(dataAsSignal(dataSourceName)); + + //real data goes to the beginning of the data array + spec.data.unshift(newData); } else if (typeof dataSource.content === 'string' || (Array.isArray(dataSource.content) && typeof dataSource.content[0] === 'string')) { From 065dd50b7354f502c540c7988856478e5d5b2365 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:48:18 +0000 Subject: [PATCH 21/47] Rename data plugin to value plugin Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/markdown/src/plugins/index.ts | 4 +- packages/markdown/src/plugins/interfaces.ts | 4 +- .../src/plugins/{data.ts => value.ts} | 62 +++++++++---------- .../features/2.4.inline-csv-data.idoc.json | 4 +- ...st.idoc.json => yaml-value-test.idoc.json} | 8 +-- 5 files changed, 41 insertions(+), 41 deletions(-) rename packages/markdown/src/plugins/{data.ts => value.ts} (78%) rename packages/web-deploy/json/features/{yaml-data-test.idoc.json => yaml-value-test.idoc.json} (67%) diff --git a/packages/markdown/src/plugins/index.ts b/packages/markdown/src/plugins/index.ts index 689a5937..82b4b611 100644 --- a/packages/markdown/src/plugins/index.ts +++ b/packages/markdown/src/plugins/index.ts @@ -9,7 +9,7 @@ import { checkboxPlugin } from './checkbox.js'; import { commentPlugin } from './comment.js'; import { cssPlugin } from './css.js'; import { csvPlugin } from './csv.js'; -import { dataPlugin } from './data.js'; +import { valuePlugin } from './value.js'; import { dsvPlugin } from './dsv.js'; import { googleFontsPlugin } from './google-fonts.js'; import { dropdownPlugin } from './dropdown.js'; @@ -31,7 +31,7 @@ export function registerNativePlugins() { registerMarkdownPlugin(commentPlugin); registerMarkdownPlugin(cssPlugin); registerMarkdownPlugin(csvPlugin); - registerMarkdownPlugin(dataPlugin); + registerMarkdownPlugin(valuePlugin); registerMarkdownPlugin(dsvPlugin); registerMarkdownPlugin(googleFontsPlugin); registerMarkdownPlugin(dropdownPlugin); diff --git a/packages/markdown/src/plugins/interfaces.ts b/packages/markdown/src/plugins/interfaces.ts index 29281caf..613bd877 100644 --- a/packages/markdown/src/plugins/interfaces.ts +++ b/packages/markdown/src/plugins/interfaces.ts @@ -4,7 +4,7 @@ */ export { CheckboxSpec } from './checkbox.js'; export { CsvSpec } from './csv.js'; -export { JsonDataSpec } from './data.js'; +export { ValueSpec } from './value.js'; export { DropdownSpec } from './dropdown.js'; export { DsvSpec } from './dsv.js'; export { ImageSpec } from './image.js'; @@ -22,7 +22,7 @@ export type PluginNames = 'css' | 'csv' | 'checkbox' | - 'data' | + 'value' | 'dropdown' | 'dsv' | 'image' | diff --git a/packages/markdown/src/plugins/data.ts b/packages/markdown/src/plugins/value.ts similarity index 78% rename from packages/markdown/src/plugins/data.ts rename to packages/markdown/src/plugins/value.ts index b5cadc81..ca1fa9a5 100644 --- a/packages/markdown/src/plugins/data.ts +++ b/packages/markdown/src/plugins/value.ts @@ -11,19 +11,19 @@ import { SpecReview } from 'common'; import { parseVariableId } from './dsv.js'; import * as yaml from 'js-yaml'; -interface JsonDataInstance { +interface ValueInstance { id: string; - spec: JsonDataSpec; + spec: ValueSpec; data: object[]; } -export interface JsonDataSpec { +export interface ValueSpec { variableId: string; wasDefaultId?: boolean; } -function inspectJsonDataSpec(spec: JsonDataSpec): RawFlaggableSpec { - const result: RawFlaggableSpec = { +function inspectValueSpec(spec: ValueSpec): RawFlaggableSpec { + const result: RawFlaggableSpec = { spec, hasFlags: false, reasons: [] @@ -38,31 +38,31 @@ function inspectJsonDataSpec(spec: JsonDataSpec): RawFlaggableSpec return result; } -const pluginName: PluginNames = 'data'; +const pluginName: PluginNames = 'value'; const className = pluginClassName(pluginName); -export const dataPlugin: Plugin = { +export const valuePlugin: Plugin = { name: pluginName, fence: (token, index) => { const content = token.content.trim(); const info = token.info.trim(); - // Parse the fence info - expect "json data variableId" or "yaml data variableId" format - // The factory.ts will route "json data ..." or "yaml data ..." to this plugin + // Parse the fence info - expect "json value variableId" or "yaml value variableId" format + // The factory.ts will route "json value ..." or "yaml value ..." to this plugin const parts = info.split(/\s+/); - // Check for variable ID (should be after "json data" or "yaml data") + // Check for variable ID (should be after "json value" or "yaml value") let variableId: string; let wasDefaultId = false; let isYaml = false; - if (parts.length >= 3 && (parts[0] === 'json' || parts[0] === 'yaml') && parts[1] === 'data') { - // Format: json data variableId or yaml data variableId + if (parts.length >= 3 && (parts[0] === 'json' || parts[0] === 'yaml') && parts[1] === 'value') { + // Format: json value variableId or yaml value variableId variableId = parts[2]; isYaml = parts[0] === 'yaml'; - } else if (parts.length >= 2 && (parts[0] === 'json' || parts[0] === 'yaml') && parts[1] === 'data') { - // Format: json data or yaml data (no variable ID provided) - variableId = `${parts[0]}Data${index}`; + } else if (parts.length >= 2 && (parts[0] === 'json' || parts[0] === 'yaml') && parts[1] === 'value') { + // Format: json value or yaml value (no variable ID provided) + variableId = `${parts[0]}Value${index}`; wasDefaultId = true; isYaml = parts[0] === 'yaml'; } else { @@ -83,7 +83,7 @@ export const dataPlugin: Plugin = { return scriptElement.outerHTML; }, hydrateSpecs: (renderer, errorHandler) => { - const flagged: SpecReview[] = []; + const flagged: SpecReview[] = []; const containers = renderer.element.querySelectorAll(`.${className}`); for (const [index, container] of Array.from(containers).entries()) { @@ -96,10 +96,10 @@ export const dataPlugin: Plugin = { continue; } - const spec: JsonDataSpec = { variableId, wasDefaultId }; - const flaggableSpec = inspectJsonDataSpec(spec); + const spec: ValueSpec = { variableId, wasDefaultId }; + const flaggableSpec = inspectValueSpec(spec); - const f: SpecReview = { + const f: SpecReview = { approvedSpec: null, pluginName, containerId: container.id @@ -122,7 +122,7 @@ export const dataPlugin: Plugin = { }, hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; - const jsonInstances: JsonDataInstance[] = []; + const valueInstances: ValueInstance[] = []; for (let index = 0; index < specs.length; index++) { const specReview = specs[index]; @@ -139,11 +139,11 @@ export const dataPlugin: Plugin = { try { const content = container.textContent?.trim(); if (!content) { - errorHandler(new Error('No data content found'), pluginName, index, 'parse', container); + errorHandler(new Error('No value content found'), pluginName, index, 'parse', container); continue; } - const spec: JsonDataSpec = specReview.approvedSpec; + const spec: ValueSpec = specReview.approvedSpec; const format = container.getAttribute('data-format') || 'json'; // Parse JSON or YAML content @@ -177,15 +177,15 @@ export const dataPlugin: Plugin = { continue; } - const jsonInstance: JsonDataInstance = { + const valueInstance: ValueInstance = { id: `${pluginName}-${index}`, spec, data }; - jsonInstances.push(jsonInstance); + valueInstances.push(valueInstance); - // Add a safe comment before the container to show that data was loaded - const comment = sanitizeHtmlComment(`${format.toUpperCase()} data loaded: ${data.length} rows for variable '${spec.variableId}'`); + // Add a safe comment before the container to show that value was loaded + const comment = sanitizeHtmlComment(`${format.toUpperCase()} value loaded: ${data.length} rows for variable '${spec.variableId}'`); container.insertAdjacentHTML('beforebegin', comment); } catch (e) { @@ -193,8 +193,8 @@ export const dataPlugin: Plugin = { } } - const instances = jsonInstances.map((jsonInstance): IInstance => { - const { spec, data } = jsonInstance; + const instances = valueInstances.map((valueInstance): IInstance => { + const { spec, data } = valueInstance; const initialSignals = [{ name: spec.variableId, @@ -204,17 +204,17 @@ export const dataPlugin: Plugin = { }]; return { - ...jsonInstance, + ...valueInstance, initialSignals, beginListening() { - // JSON data is static, but we broadcast it when listening begins + // Value data is static, but we broadcast it when listening begins const batch: Batch = { [spec.variableId]: { value: data, isData: true, }, }; - signalBus.broadcast(jsonInstance.id, batch); + signalBus.broadcast(valueInstance.id, batch); }, getCurrentSignalValue: () => { return data; diff --git a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json index dc759c2c..2bcf5ee4 100644 --- a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json +++ b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json @@ -5,10 +5,10 @@ { "groupId": "inline_json", "elements": [ - "## Inline JSON Data", + "## Inline JSON Value", "You can provide JSON data directly as a code block within the document using the **json plugin** with a `variableId` parameter. This is useful for structured data or when you want to include complex data objects.", "", - "```json data inventory\n[\n {\"item\": \"Keyboard\", \"price\": 79.99, \"inStock\": true},\n {\"item\": \"Mouse\", \"price\": 29.99, \"inStock\": true},\n {\"item\": \"Monitor\", \"price\": 299.99, \"inStock\": false}\n]\n```", + "```json value inventory\n[\n {\"item\": \"Keyboard\", \"price\": 79.99, \"inStock\": true},\n {\"item\": \"Mouse\", \"price\": 29.99, \"inStock\": true},\n {\"item\": \"Monitor\", \"price\": 299.99, \"inStock\": false}\n]\n```", { "type": "tabulator", "dataSourceName": "inventory", diff --git a/packages/web-deploy/json/features/yaml-data-test.idoc.json b/packages/web-deploy/json/features/yaml-value-test.idoc.json similarity index 67% rename from packages/web-deploy/json/features/yaml-data-test.idoc.json rename to packages/web-deploy/json/features/yaml-value-test.idoc.json index dbc68671..24d87d5a 100644 --- a/packages/web-deploy/json/features/yaml-data-test.idoc.json +++ b/packages/web-deploy/json/features/yaml-value-test.idoc.json @@ -1,14 +1,14 @@ { "$schema": "../../../../docs/schema/idoc_v1.json", - "title": "YAML Data Plugin Test", + "title": "YAML Value Plugin Test", "groups": [ { "groupId": "yaml_test", "elements": [ - "## Testing YAML Data Plugin", - "This example tests the data plugin with YAML format.", + "## Testing YAML Value Plugin", + "This example tests the value plugin with YAML format.", "", - "```yaml data people\n- name: Alice\n age: 25\n city: New York\n\n- name: Bob\n age: 30\n city: Chicago\n\n- name: Carol\n age: 35\n city: Los Angeles\n\n- name: David\n age: 28\n city: Seattle\n```", + "```yaml value people\n- name: Alice\n age: 25\n city: New York\n\n- name: Bob\n age: 30\n city: Chicago\n\n- name: Carol\n age: 35\n city: Los Angeles\n\n- name: David\n age: 28\n city: Seattle\n```", { "type": "tabulator", "dataSourceName": "people", From 80501c12f0b3c4e4742d467c5c50dd1479c17042 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:50:04 +0000 Subject: [PATCH 22/47] Add value plugin documentation to schema Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- docs/schema/idoc_v1.d.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/schema/idoc_v1.d.ts b/docs/schema/idoc_v1.d.ts index 7fb3df38..54a5cdf7 100644 --- a/docs/schema/idoc_v1.d.ts +++ b/docs/schema/idoc_v1.d.ts @@ -286,6 +286,16 @@ interface InteractiveDocument { /** * Use markdown elements to be verbose and descriptive. Do not use as labels for interactive elements. * Embed dynamic variables in markdown using double curly braces {{variableId}} as a placeholder for their values. + * + * Markdown Fence Blocks for Inline Data: + * - CSV: ```csv variableId (content is CSV rows) + * - TSV: ```tsv variableId (content is TSV rows) + * - DSV: ```dsv delimiter:| variableId (content is delimiter-separated rows) + * - JSON Value: ```json value variableId (content is JSON array or object) + * - YAML Value: ```yaml value variableId (content is YAML array or object) + * + * The value plugin (JSON/YAML) is useful for inline structured data with type preservation (numbers, booleans, nested objects). + * CSV/TSV/DSV store everything as strings and are better for tabular data. */ type MarkdownElement = string; /** Union type for all possible elements */ From fe002f94d689eebaaaff68616326f5dd09feec12 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:59:12 +0000 Subject: [PATCH 23/47] Revert built schema file and add value plugin docs to source schema Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .github/copilot-instructions.md | 1 + docs/schema/idoc_v1.d.ts | 10 ---------- packages/schema-doc/src/page.ts | 10 ++++++++++ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4940ea2b..352f2dc1 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -31,6 +31,7 @@ Chartifact consists of several interoperating modules: ### Version Control - **Do NOT check in UMD build artifacts**: Files matching `*.umd.js` in `docs/dist/v1/` are build outputs and should not be committed in PRs +- **Do NOT edit `docs/schema/idoc_v1.d.ts` directly**: This file is generated from `packages/schema-doc/src/`. Edit the source files in the schema-doc package instead - These files are generated during the build process and committing them creates unnecessary merge conflicts ## Project-Specific Conventions diff --git a/docs/schema/idoc_v1.d.ts b/docs/schema/idoc_v1.d.ts index 54a5cdf7..7fb3df38 100644 --- a/docs/schema/idoc_v1.d.ts +++ b/docs/schema/idoc_v1.d.ts @@ -286,16 +286,6 @@ interface InteractiveDocument { /** * Use markdown elements to be verbose and descriptive. Do not use as labels for interactive elements. * Embed dynamic variables in markdown using double curly braces {{variableId}} as a placeholder for their values. - * - * Markdown Fence Blocks for Inline Data: - * - CSV: ```csv variableId (content is CSV rows) - * - TSV: ```tsv variableId (content is TSV rows) - * - DSV: ```dsv delimiter:| variableId (content is delimiter-separated rows) - * - JSON Value: ```json value variableId (content is JSON array or object) - * - YAML Value: ```yaml value variableId (content is YAML array or object) - * - * The value plugin (JSON/YAML) is useful for inline structured data with type preservation (numbers, booleans, nested objects). - * CSV/TSV/DSV store everything as strings and are better for tabular data. */ type MarkdownElement = string; /** Union type for all possible elements */ diff --git a/packages/schema-doc/src/page.ts b/packages/schema-doc/src/page.ts index 49ca5d87..1ad6fc55 100644 --- a/packages/schema-doc/src/page.ts +++ b/packages/schema-doc/src/page.ts @@ -41,6 +41,16 @@ export interface InteractiveDocument { /** * Use markdown elements to be verbose and descriptive. Do not use as labels for interactive elements. * Embed dynamic variables in markdown using double curly braces {{variableId}} as a placeholder for their values. + * + * Markdown Fence Blocks for Inline Data: + * - CSV: ```csv variableId (content is CSV rows) + * - TSV: ```tsv variableId (content is TSV rows) + * - DSV: ```dsv delimiter:| variableId (content is delimiter-separated rows) + * - JSON Value: ```json value variableId (content is JSON array or object) + * - YAML Value: ```yaml value variableId (content is YAML array or object) + * + * The value plugin (JSON/YAML) is useful for inline structured data with type preservation (numbers, booleans, nested objects). + * CSV/TSV/DSV store everything as strings and are better for tabular data. */ export type MarkdownElement = string; From dc5700e791a3ced07b425cbb8dad8ccf6b6987e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 21:15:43 +0000 Subject: [PATCH 24/47] Add ValueElement, CsvElement, TsvElement, DsvElement to schema Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- docs/schema/idoc_v1.d.ts | 80 +++++++++++- docs/schema/idoc_v1.json | 163 +++++++++++++++++++++++++ packages/schema-doc/src/index.ts | 1 + packages/schema-doc/src/inline-data.ts | 89 ++++++++++++++ packages/schema-doc/src/interactive.ts | 2 + packages/schema-doc/src/page.ts | 10 -- 6 files changed, 333 insertions(+), 12 deletions(-) create mode 100644 packages/schema-doc/src/inline-data.ts diff --git a/docs/schema/idoc_v1.d.ts b/docs/schema/idoc_v1.d.ts index 7fb3df38..3954fc69 100644 --- a/docs/schema/idoc_v1.d.ts +++ b/docs/schema/idoc_v1.d.ts @@ -95,6 +95,82 @@ interface DataLoaderBySpec { spec: object; } type DataLoader = DataSource | DataLoaderBySpec; +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ +/** + * Inline Data Elements + * These elements are defined using markdown fence blocks to embed data directly in the document. + */ +/** + * CSV Element + * Embed CSV data inline using markdown fence block: ```csv variableId + * Content should be CSV rows (comma-separated values). + * All values are stored as strings. + */ +interface CsvElement extends ElementBase { + type: 'csv'; + variableId: VariableID; + /** CSV content as string */ + content: string; +} +/** + * TSV Element + * Embed TSV data inline using markdown fence block: ```tsv variableId + * Content should be TSV rows (tab-separated values). + * All values are stored as strings. + */ +interface TsvElement extends ElementBase { + type: 'tsv'; + variableId: VariableID; + /** TSV content as string */ + content: string; +} +/** + * DSV Element + * Embed delimiter-separated data inline using markdown fence block: ```dsv delimiter:| variableId + * Content should be delimiter-separated rows. + * All values are stored as strings. + */ +interface DsvElement extends ElementBase { + type: 'dsv'; + variableId: VariableID; + /** Custom delimiter character (e.g., '|', ';', etc.) */ + delimiter: string; + /** DSV content as string */ + content: string; +} +/** + * Value Element (JSON) + * Embed JSON data inline using markdown fence block: ```json value variableId + * Content should be a JSON array or object. + * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. + * Use this for structured data with type preservation. + */ +interface JsonValueElement extends ElementBase { + type: 'json-value'; + variableId: VariableID; + /** JSON content as array or object */ + content: object | object[]; +} +/** + * Value Element (YAML) + * Embed YAML data inline using markdown fence block: ```yaml value variableId + * Content should be YAML array or object. + * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. + * Use this for structured data with type preservation. + */ +interface YamlValueElement extends ElementBase { + type: 'yaml-value'; + variableId: VariableID; + /** YAML content as array or object */ + content: object | object[]; +} +/** + * Union type for all inline data elements + */ +type InlineDataElement = CsvElement | TsvElement | DsvElement | JsonValueElement | YamlValueElement; /** * Interactive Elements */ @@ -256,7 +332,7 @@ interface TabulatorElementProps extends OptionalVariableControl { /** * Union type for all possible interactive elements */ -type InteractiveElement = ChartElement | CheckboxElement | DropdownElement | ImageElement | MermaidElement | NumberElement | PresetsElement | SliderElement | TabulatorElement | TextboxElement | TreebarkElement; +type InteractiveElement = ChartElement | CheckboxElement | DropdownElement | ImageElement | MermaidElement | NumberElement | PresetsElement | SliderElement | TabulatorElement | TextboxElement | TreebarkElement | InlineDataElement; interface ElementGroup { groupId: string; elements: PageElement[]; @@ -316,4 +392,4 @@ interface GoogleFontsSpec { type InteractiveDocumentWithSchema = InteractiveDocument & { $schema?: string; }; -export type { Calculation, ChartElement, CheckboxElement, CheckboxProps, DataFrameCalculation, DataLoader, DataLoaderBySpec, DataSource, DataSourceBase, DataSourceBaseFormat, DataSourceByDynamicURL, DataSourceByFile, DataSourceInline, DropdownElement, DropdownElementProps, DynamicDropdownOptions, ElementBase, ElementGroup, GoogleFontsSpec, ImageElement, ImageElementProps, InteractiveDocument, InteractiveDocumentWithSchema, InteractiveElement, MarkdownElement, MermaidElement, MermaidElementProps, MermaidTemplate, NumberElement, NumberElementProps, OptionalVariableControl, PageElement, PageStyle, Preset, PresetsElement, PresetsElementProps, ReturnType, ScalarCalculation, SliderElement, SliderElementProps, TabulatorElement, TabulatorElementProps, TemplatedUrl, TextboxElement, TextboxElementProps, TreebarkElement, TreebarkElementProps, Variable, VariableControl, VariableID, VariableType, VariableValue, VariableValueArray, VariableValuePrimitive, Vega_or_VegaLite_spec }; \ No newline at end of file +export type { Calculation, ChartElement, CheckboxElement, CheckboxProps, CsvElement, DataFrameCalculation, DataLoader, DataLoaderBySpec, DataSource, DataSourceBase, DataSourceBaseFormat, DataSourceByDynamicURL, DataSourceByFile, DataSourceInline, DropdownElement, DropdownElementProps, DsvElement, DynamicDropdownOptions, ElementBase, ElementGroup, GoogleFontsSpec, ImageElement, ImageElementProps, InlineDataElement, InteractiveDocument, InteractiveDocumentWithSchema, InteractiveElement, JsonValueElement, MarkdownElement, MermaidElement, MermaidElementProps, MermaidTemplate, NumberElement, NumberElementProps, OptionalVariableControl, PageElement, PageStyle, Preset, PresetsElement, PresetsElementProps, ReturnType, ScalarCalculation, SliderElement, SliderElementProps, TabulatorElement, TabulatorElementProps, TemplatedUrl, TextboxElement, TextboxElementProps, TreebarkElement, TreebarkElementProps, TsvElement, Variable, VariableControl, VariableID, VariableType, VariableValue, VariableValueArray, VariableValuePrimitive, Vega_or_VegaLite_spec, YamlValueElement }; \ No newline at end of file diff --git a/docs/schema/idoc_v1.json b/docs/schema/idoc_v1.json index c93a9361..7a00ba2c 100644 --- a/docs/schema/idoc_v1.json +++ b/docs/schema/idoc_v1.json @@ -1083,6 +1083,29 @@ ], "type": "object" }, + "CsvElement": { + "additionalProperties": false, + "description": "CSV Element Embed CSV data inline using markdown fence block: ```csv variableId Content should be CSV rows (comma-separated values). All values are stored as strings.", + "properties": { + "content": { + "description": "CSV content as string", + "type": "string" + }, + "type": { + "const": "csv", + "type": "string" + }, + "variableId": { + "$ref": "#/definitions/VariableID" + } + }, + "required": [ + "type", + "variableId", + "content" + ], + "type": "object" + }, "DataFrameCalculation": { "additionalProperties": false, "description": "DataFrame calculation for object arrays. Not for primitive/scalar values.", @@ -1614,6 +1637,34 @@ ], "type": "object" }, + "DsvElement": { + "additionalProperties": false, + "description": "DSV Element Embed delimiter-separated data inline using markdown fence block: ```dsv delimiter:| variableId Content should be delimiter-separated rows. All values are stored as strings.", + "properties": { + "content": { + "description": "DSV content as string", + "type": "string" + }, + "delimiter": { + "description": "Custom delimiter character (e.g., '|', ';', etc.)", + "type": "string" + }, + "type": { + "const": "dsv", + "type": "string" + }, + "variableId": { + "$ref": "#/definitions/VariableID" + } + }, + "required": [ + "type", + "variableId", + "delimiter", + "content" + ], + "type": "object" + }, "DynamicDropdownOptions": { "additionalProperties": false, "description": "Dropdown use for selecting from a list of options", @@ -3143,6 +3194,26 @@ ], "type": "object" }, + "InlineDataElement": { + "anyOf": [ + { + "$ref": "#/definitions/CsvElement" + }, + { + "$ref": "#/definitions/TsvElement" + }, + { + "$ref": "#/definitions/DsvElement" + }, + { + "$ref": "#/definitions/JsonValueElement" + }, + { + "$ref": "#/definitions/YamlValueElement" + } + ], + "description": "Union type for all inline data elements" + }, "InteractiveDocumentWithSchema": { "additionalProperties": false, "description": "JSON Schema version with $schema property for validation", @@ -3236,6 +3307,9 @@ }, { "$ref": "#/definitions/TreebarkElement" + }, + { + "$ref": "#/definitions/InlineDataElement" } ], "description": "Union type for all possible interactive elements" @@ -3364,6 +3438,39 @@ ], "type": "object" }, + "JsonValueElement": { + "additionalProperties": false, + "description": "Value Element (JSON) Embed JSON data inline using markdown fence block: ```json value variableId Content should be a JSON array or object. Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. Use this for structured data with type preservation.", + "properties": { + "content": { + "anyOf": [ + { + "type": "object" + }, + { + "items": { + "type": "object" + }, + "type": "array" + } + ], + "description": "JSON content as array or object" + }, + "type": { + "const": "json-value", + "type": "string" + }, + "variableId": { + "$ref": "#/definitions/VariableID" + } + }, + "required": [ + "type", + "variableId", + "content" + ], + "type": "object" + }, "KDE2DTransform": { "additionalProperties": false, "properties": { @@ -5893,6 +6000,29 @@ ], "type": "object" }, + "TsvElement": { + "additionalProperties": false, + "description": "TSV Element Embed TSV data inline using markdown fence block: ```tsv variableId Content should be TSV rows (tab-separated values). All values are stored as strings.", + "properties": { + "content": { + "description": "TSV content as string", + "type": "string" + }, + "type": { + "const": "tsv", + "type": "string" + }, + "variableId": { + "$ref": "#/definitions/VariableID" + } + }, + "required": [ + "type", + "variableId", + "content" + ], + "type": "object" + }, "UlTag": { "additionalProperties": false, "properties": { @@ -6359,6 +6489,39 @@ "type" ], "type": "object" + }, + "YamlValueElement": { + "additionalProperties": false, + "description": "Value Element (YAML) Embed YAML data inline using markdown fence block: ```yaml value variableId Content should be YAML array or object. Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. Use this for structured data with type preservation.", + "properties": { + "content": { + "anyOf": [ + { + "type": "object" + }, + { + "items": { + "type": "object" + }, + "type": "array" + } + ], + "description": "YAML content as array or object" + }, + "type": { + "const": "yaml-value", + "type": "string" + }, + "variableId": { + "$ref": "#/definitions/VariableID" + } + }, + "required": [ + "type", + "variableId", + "content" + ], + "type": "object" } } } \ No newline at end of file diff --git a/packages/schema-doc/src/index.ts b/packages/schema-doc/src/index.ts index cfb2a6af..5c19bab9 100644 --- a/packages/schema-doc/src/index.ts +++ b/packages/schema-doc/src/index.ts @@ -4,6 +4,7 @@ */ export * from './common.js'; export * from './datasource.js'; +export * from './inline-data.js'; export * from './interactive.js'; export * from './page.js'; export * from './schema.js'; diff --git a/packages/schema-doc/src/inline-data.ts b/packages/schema-doc/src/inline-data.ts new file mode 100644 index 00000000..44ff61eb --- /dev/null +++ b/packages/schema-doc/src/inline-data.ts @@ -0,0 +1,89 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ +import { VariableID, ElementBase } from './common.js'; + +/** + * Inline Data Elements + * These elements are defined using markdown fence blocks to embed data directly in the document. + */ + +/** + * CSV Element + * Embed CSV data inline using markdown fence block: ```csv variableId + * Content should be CSV rows (comma-separated values). + * All values are stored as strings. + */ +export interface CsvElement extends ElementBase { + type: 'csv'; + variableId: VariableID; + /** CSV content as string */ + content: string; +} + +/** + * TSV Element + * Embed TSV data inline using markdown fence block: ```tsv variableId + * Content should be TSV rows (tab-separated values). + * All values are stored as strings. + */ +export interface TsvElement extends ElementBase { + type: 'tsv'; + variableId: VariableID; + /** TSV content as string */ + content: string; +} + +/** + * DSV Element + * Embed delimiter-separated data inline using markdown fence block: ```dsv delimiter:| variableId + * Content should be delimiter-separated rows. + * All values are stored as strings. + */ +export interface DsvElement extends ElementBase { + type: 'dsv'; + variableId: VariableID; + /** Custom delimiter character (e.g., '|', ';', etc.) */ + delimiter: string; + /** DSV content as string */ + content: string; +} + +/** + * Value Element (JSON) + * Embed JSON data inline using markdown fence block: ```json value variableId + * Content should be a JSON array or object. + * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. + * Use this for structured data with type preservation. + */ +export interface JsonValueElement extends ElementBase { + type: 'json-value'; + variableId: VariableID; + /** JSON content as array or object */ + content: object | object[]; +} + +/** + * Value Element (YAML) + * Embed YAML data inline using markdown fence block: ```yaml value variableId + * Content should be YAML array or object. + * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. + * Use this for structured data with type preservation. + */ +export interface YamlValueElement extends ElementBase { + type: 'yaml-value'; + variableId: VariableID; + /** YAML content as array or object */ + content: object | object[]; +} + +/** + * Union type for all inline data elements + */ +export type InlineDataElement = + | CsvElement + | TsvElement + | DsvElement + | JsonValueElement + | YamlValueElement; diff --git a/packages/schema-doc/src/interactive.ts b/packages/schema-doc/src/interactive.ts index 70753307..374feafc 100644 --- a/packages/schema-doc/src/interactive.ts +++ b/packages/schema-doc/src/interactive.ts @@ -4,6 +4,7 @@ */ import { VariableID, VariableControl, ElementBase, TemplatedUrl, OptionalVariableControl } from './common.js'; import { TemplateElement } from 'treebark'; +import { InlineDataElement } from './inline-data.js'; /** * Interactive Elements @@ -250,4 +251,5 @@ export type InteractiveElement = | TabulatorElement | TextboxElement | TreebarkElement + | InlineDataElement ; diff --git a/packages/schema-doc/src/page.ts b/packages/schema-doc/src/page.ts index 1ad6fc55..49ca5d87 100644 --- a/packages/schema-doc/src/page.ts +++ b/packages/schema-doc/src/page.ts @@ -41,16 +41,6 @@ export interface InteractiveDocument { /** * Use markdown elements to be verbose and descriptive. Do not use as labels for interactive elements. * Embed dynamic variables in markdown using double curly braces {{variableId}} as a placeholder for their values. - * - * Markdown Fence Blocks for Inline Data: - * - CSV: ```csv variableId (content is CSV rows) - * - TSV: ```tsv variableId (content is TSV rows) - * - DSV: ```dsv delimiter:| variableId (content is delimiter-separated rows) - * - JSON Value: ```json value variableId (content is JSON array or object) - * - YAML Value: ```yaml value variableId (content is YAML array or object) - * - * The value plugin (JSON/YAML) is useful for inline structured data with type preservation (numbers, booleans, nested objects). - * CSV/TSV/DSV store everything as strings and are better for tabular data. */ export type MarkdownElement = string; From 3e831134dc751293d2d587b171d8b1d7e705a6c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 22:36:48 +0000 Subject: [PATCH 25/47] Fix schema: revert built files, update types, move inline data to page, use element types in examples Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .github/copilot-instructions.md | 1 + docs/schema/idoc_v1.d.ts | 80 +-------- docs/schema/idoc_v1.json | 163 ------------------ packages/schema-doc/src/inline-data.ts | 16 +- packages/schema-doc/src/interactive.ts | 8 +- packages/schema-doc/src/page.ts | 3 +- .../features/2.4.inline-csv-data.idoc.json | 29 +++- 7 files changed, 41 insertions(+), 259 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 352f2dc1..954695bb 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -32,6 +32,7 @@ Chartifact consists of several interoperating modules: ### Version Control - **Do NOT check in UMD build artifacts**: Files matching `*.umd.js` in `docs/dist/v1/` are build outputs and should not be committed in PRs - **Do NOT edit `docs/schema/idoc_v1.d.ts` directly**: This file is generated from `packages/schema-doc/src/`. Edit the source files in the schema-doc package instead +- **Do NOT check in built schema files**: Files `docs/schema/idoc_v1.d.ts` and `docs/schema/idoc_v1.json` are generated from `packages/schema-doc/src/` during the build process and should not be committed in PRs - These files are generated during the build process and committing them creates unnecessary merge conflicts ## Project-Specific Conventions diff --git a/docs/schema/idoc_v1.d.ts b/docs/schema/idoc_v1.d.ts index 3954fc69..7fb3df38 100644 --- a/docs/schema/idoc_v1.d.ts +++ b/docs/schema/idoc_v1.d.ts @@ -95,82 +95,6 @@ interface DataLoaderBySpec { spec: object; } type DataLoader = DataSource | DataLoaderBySpec; -/** - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ -/** - * Inline Data Elements - * These elements are defined using markdown fence blocks to embed data directly in the document. - */ -/** - * CSV Element - * Embed CSV data inline using markdown fence block: ```csv variableId - * Content should be CSV rows (comma-separated values). - * All values are stored as strings. - */ -interface CsvElement extends ElementBase { - type: 'csv'; - variableId: VariableID; - /** CSV content as string */ - content: string; -} -/** - * TSV Element - * Embed TSV data inline using markdown fence block: ```tsv variableId - * Content should be TSV rows (tab-separated values). - * All values are stored as strings. - */ -interface TsvElement extends ElementBase { - type: 'tsv'; - variableId: VariableID; - /** TSV content as string */ - content: string; -} -/** - * DSV Element - * Embed delimiter-separated data inline using markdown fence block: ```dsv delimiter:| variableId - * Content should be delimiter-separated rows. - * All values are stored as strings. - */ -interface DsvElement extends ElementBase { - type: 'dsv'; - variableId: VariableID; - /** Custom delimiter character (e.g., '|', ';', etc.) */ - delimiter: string; - /** DSV content as string */ - content: string; -} -/** - * Value Element (JSON) - * Embed JSON data inline using markdown fence block: ```json value variableId - * Content should be a JSON array or object. - * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. - * Use this for structured data with type preservation. - */ -interface JsonValueElement extends ElementBase { - type: 'json-value'; - variableId: VariableID; - /** JSON content as array or object */ - content: object | object[]; -} -/** - * Value Element (YAML) - * Embed YAML data inline using markdown fence block: ```yaml value variableId - * Content should be YAML array or object. - * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. - * Use this for structured data with type preservation. - */ -interface YamlValueElement extends ElementBase { - type: 'yaml-value'; - variableId: VariableID; - /** YAML content as array or object */ - content: object | object[]; -} -/** - * Union type for all inline data elements - */ -type InlineDataElement = CsvElement | TsvElement | DsvElement | JsonValueElement | YamlValueElement; /** * Interactive Elements */ @@ -332,7 +256,7 @@ interface TabulatorElementProps extends OptionalVariableControl { /** * Union type for all possible interactive elements */ -type InteractiveElement = ChartElement | CheckboxElement | DropdownElement | ImageElement | MermaidElement | NumberElement | PresetsElement | SliderElement | TabulatorElement | TextboxElement | TreebarkElement | InlineDataElement; +type InteractiveElement = ChartElement | CheckboxElement | DropdownElement | ImageElement | MermaidElement | NumberElement | PresetsElement | SliderElement | TabulatorElement | TextboxElement | TreebarkElement; interface ElementGroup { groupId: string; elements: PageElement[]; @@ -392,4 +316,4 @@ interface GoogleFontsSpec { type InteractiveDocumentWithSchema = InteractiveDocument & { $schema?: string; }; -export type { Calculation, ChartElement, CheckboxElement, CheckboxProps, CsvElement, DataFrameCalculation, DataLoader, DataLoaderBySpec, DataSource, DataSourceBase, DataSourceBaseFormat, DataSourceByDynamicURL, DataSourceByFile, DataSourceInline, DropdownElement, DropdownElementProps, DsvElement, DynamicDropdownOptions, ElementBase, ElementGroup, GoogleFontsSpec, ImageElement, ImageElementProps, InlineDataElement, InteractiveDocument, InteractiveDocumentWithSchema, InteractiveElement, JsonValueElement, MarkdownElement, MermaidElement, MermaidElementProps, MermaidTemplate, NumberElement, NumberElementProps, OptionalVariableControl, PageElement, PageStyle, Preset, PresetsElement, PresetsElementProps, ReturnType, ScalarCalculation, SliderElement, SliderElementProps, TabulatorElement, TabulatorElementProps, TemplatedUrl, TextboxElement, TextboxElementProps, TreebarkElement, TreebarkElementProps, TsvElement, Variable, VariableControl, VariableID, VariableType, VariableValue, VariableValueArray, VariableValuePrimitive, Vega_or_VegaLite_spec, YamlValueElement }; \ No newline at end of file +export type { Calculation, ChartElement, CheckboxElement, CheckboxProps, DataFrameCalculation, DataLoader, DataLoaderBySpec, DataSource, DataSourceBase, DataSourceBaseFormat, DataSourceByDynamicURL, DataSourceByFile, DataSourceInline, DropdownElement, DropdownElementProps, DynamicDropdownOptions, ElementBase, ElementGroup, GoogleFontsSpec, ImageElement, ImageElementProps, InteractiveDocument, InteractiveDocumentWithSchema, InteractiveElement, MarkdownElement, MermaidElement, MermaidElementProps, MermaidTemplate, NumberElement, NumberElementProps, OptionalVariableControl, PageElement, PageStyle, Preset, PresetsElement, PresetsElementProps, ReturnType, ScalarCalculation, SliderElement, SliderElementProps, TabulatorElement, TabulatorElementProps, TemplatedUrl, TextboxElement, TextboxElementProps, TreebarkElement, TreebarkElementProps, Variable, VariableControl, VariableID, VariableType, VariableValue, VariableValueArray, VariableValuePrimitive, Vega_or_VegaLite_spec }; \ No newline at end of file diff --git a/docs/schema/idoc_v1.json b/docs/schema/idoc_v1.json index 7a00ba2c..c93a9361 100644 --- a/docs/schema/idoc_v1.json +++ b/docs/schema/idoc_v1.json @@ -1083,29 +1083,6 @@ ], "type": "object" }, - "CsvElement": { - "additionalProperties": false, - "description": "CSV Element Embed CSV data inline using markdown fence block: ```csv variableId Content should be CSV rows (comma-separated values). All values are stored as strings.", - "properties": { - "content": { - "description": "CSV content as string", - "type": "string" - }, - "type": { - "const": "csv", - "type": "string" - }, - "variableId": { - "$ref": "#/definitions/VariableID" - } - }, - "required": [ - "type", - "variableId", - "content" - ], - "type": "object" - }, "DataFrameCalculation": { "additionalProperties": false, "description": "DataFrame calculation for object arrays. Not for primitive/scalar values.", @@ -1637,34 +1614,6 @@ ], "type": "object" }, - "DsvElement": { - "additionalProperties": false, - "description": "DSV Element Embed delimiter-separated data inline using markdown fence block: ```dsv delimiter:| variableId Content should be delimiter-separated rows. All values are stored as strings.", - "properties": { - "content": { - "description": "DSV content as string", - "type": "string" - }, - "delimiter": { - "description": "Custom delimiter character (e.g., '|', ';', etc.)", - "type": "string" - }, - "type": { - "const": "dsv", - "type": "string" - }, - "variableId": { - "$ref": "#/definitions/VariableID" - } - }, - "required": [ - "type", - "variableId", - "delimiter", - "content" - ], - "type": "object" - }, "DynamicDropdownOptions": { "additionalProperties": false, "description": "Dropdown use for selecting from a list of options", @@ -3194,26 +3143,6 @@ ], "type": "object" }, - "InlineDataElement": { - "anyOf": [ - { - "$ref": "#/definitions/CsvElement" - }, - { - "$ref": "#/definitions/TsvElement" - }, - { - "$ref": "#/definitions/DsvElement" - }, - { - "$ref": "#/definitions/JsonValueElement" - }, - { - "$ref": "#/definitions/YamlValueElement" - } - ], - "description": "Union type for all inline data elements" - }, "InteractiveDocumentWithSchema": { "additionalProperties": false, "description": "JSON Schema version with $schema property for validation", @@ -3307,9 +3236,6 @@ }, { "$ref": "#/definitions/TreebarkElement" - }, - { - "$ref": "#/definitions/InlineDataElement" } ], "description": "Union type for all possible interactive elements" @@ -3438,39 +3364,6 @@ ], "type": "object" }, - "JsonValueElement": { - "additionalProperties": false, - "description": "Value Element (JSON) Embed JSON data inline using markdown fence block: ```json value variableId Content should be a JSON array or object. Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. Use this for structured data with type preservation.", - "properties": { - "content": { - "anyOf": [ - { - "type": "object" - }, - { - "items": { - "type": "object" - }, - "type": "array" - } - ], - "description": "JSON content as array or object" - }, - "type": { - "const": "json-value", - "type": "string" - }, - "variableId": { - "$ref": "#/definitions/VariableID" - } - }, - "required": [ - "type", - "variableId", - "content" - ], - "type": "object" - }, "KDE2DTransform": { "additionalProperties": false, "properties": { @@ -6000,29 +5893,6 @@ ], "type": "object" }, - "TsvElement": { - "additionalProperties": false, - "description": "TSV Element Embed TSV data inline using markdown fence block: ```tsv variableId Content should be TSV rows (tab-separated values). All values are stored as strings.", - "properties": { - "content": { - "description": "TSV content as string", - "type": "string" - }, - "type": { - "const": "tsv", - "type": "string" - }, - "variableId": { - "$ref": "#/definitions/VariableID" - } - }, - "required": [ - "type", - "variableId", - "content" - ], - "type": "object" - }, "UlTag": { "additionalProperties": false, "properties": { @@ -6489,39 +6359,6 @@ "type" ], "type": "object" - }, - "YamlValueElement": { - "additionalProperties": false, - "description": "Value Element (YAML) Embed YAML data inline using markdown fence block: ```yaml value variableId Content should be YAML array or object. Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. Use this for structured data with type preservation.", - "properties": { - "content": { - "anyOf": [ - { - "type": "object" - }, - { - "items": { - "type": "object" - }, - "type": "array" - } - ], - "description": "YAML content as array or object" - }, - "type": { - "const": "yaml-value", - "type": "string" - }, - "variableId": { - "$ref": "#/definitions/VariableID" - } - }, - "required": [ - "type", - "variableId", - "content" - ], - "type": "object" } } } \ No newline at end of file diff --git a/packages/schema-doc/src/inline-data.ts b/packages/schema-doc/src/inline-data.ts index 44ff61eb..80fa8e2b 100644 --- a/packages/schema-doc/src/inline-data.ts +++ b/packages/schema-doc/src/inline-data.ts @@ -18,8 +18,8 @@ import { VariableID, ElementBase } from './common.js'; export interface CsvElement extends ElementBase { type: 'csv'; variableId: VariableID; - /** CSV content as string */ - content: string; + /** CSV content as string, array of strings, or array of string arrays */ + content: string | string[] | string[][]; } /** @@ -31,8 +31,8 @@ export interface CsvElement extends ElementBase { export interface TsvElement extends ElementBase { type: 'tsv'; variableId: VariableID; - /** TSV content as string */ - content: string; + /** TSV content as string, array of strings, or array of string arrays */ + content: string | string[] | string[][]; } /** @@ -46,8 +46,8 @@ export interface DsvElement extends ElementBase { variableId: VariableID; /** Custom delimiter character (e.g., '|', ';', etc.) */ delimiter: string; - /** DSV content as string */ - content: string; + /** DSV content as string, array of strings, or array of string arrays */ + content: string | string[] | string[][]; } /** @@ -74,8 +74,8 @@ export interface JsonValueElement extends ElementBase { export interface YamlValueElement extends ElementBase { type: 'yaml-value'; variableId: VariableID; - /** YAML content as array or object */ - content: object | object[]; + /** YAML content as array, object, string, or string array */ + content: object | object[] | string | string[]; } /** diff --git a/packages/schema-doc/src/interactive.ts b/packages/schema-doc/src/interactive.ts index 374feafc..1dbc2bc1 100644 --- a/packages/schema-doc/src/interactive.ts +++ b/packages/schema-doc/src/interactive.ts @@ -1,10 +1,9 @@ /** -* Copyright (c) Microsoft Corporation. -* Licensed under the MIT License. -*/ + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ import { VariableID, VariableControl, ElementBase, TemplatedUrl, OptionalVariableControl } from './common.js'; import { TemplateElement } from 'treebark'; -import { InlineDataElement } from './inline-data.js'; /** * Interactive Elements @@ -251,5 +250,4 @@ export type InteractiveElement = | TabulatorElement | TextboxElement | TreebarkElement - | InlineDataElement ; diff --git a/packages/schema-doc/src/page.ts b/packages/schema-doc/src/page.ts index 49ca5d87..afdea4e9 100644 --- a/packages/schema-doc/src/page.ts +++ b/packages/schema-doc/src/page.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. */ import { InteractiveElement } from './interactive.js'; +import { InlineDataElement } from './inline-data.js'; import { DataLoader } from './datasource.js'; import { Variable } from './common.js'; @@ -45,7 +46,7 @@ export interface InteractiveDocument { export type MarkdownElement = string; /** Union type for all possible elements */ -export type PageElement = MarkdownElement | InteractiveElement; +export type PageElement = MarkdownElement | InteractiveElement | InlineDataElement; export interface PageStyle { /** CSS styles, either a string, or array of strings which will be concatenated. The array is for developer ergonomics for authoring and merging. */ diff --git a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json index 2bcf5ee4..f3c82093 100644 --- a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json +++ b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json @@ -8,7 +8,15 @@ "## Inline JSON Value", "You can provide JSON data directly as a code block within the document using the **json plugin** with a `variableId` parameter. This is useful for structured data or when you want to include complex data objects.", "", - "```json value inventory\n[\n {\"item\": \"Keyboard\", \"price\": 79.99, \"inStock\": true},\n {\"item\": \"Mouse\", \"price\": 29.99, \"inStock\": true},\n {\"item\": \"Monitor\", \"price\": 299.99, \"inStock\": false}\n]\n```", + { + "type": "json-value", + "variableId": "inventory", + "content": [ + {"item": "Keyboard", "price": 79.99, "inStock": true}, + {"item": "Mouse", "price": 29.99, "inStock": true}, + {"item": "Monitor", "price": 299.99, "inStock": false} + ] + }, { "type": "tabulator", "dataSourceName": "inventory", @@ -26,7 +34,11 @@ "## Inline CSV Data", "You can provide CSV data directly as a code block within the document using the **csv plugin** with a `variableId` parameter. This is useful for small datasets or when you want to include sample data without external dependencies.", "", - "```csv officeSupplies\nitem,price\nStapler,12.99\nPen,1.25\nLamp,29.99\n```", + { + "type": "csv", + "variableId": "officeSupplies", + "content": "item,price\nStapler,12.99\nPen,1.25\nLamp,29.99" + }, { "type": "tabulator", "dataSourceName": "officeSupplies", @@ -44,7 +56,11 @@ "## Inline TSV Data", "You can also use tab-separated values (TSV) with the **tsv plugin** and a `variableId` parameter:", "", - "```tsv people\nname\tage\tcity\nAlice\t30\tNew York\nBob\t25\tLos Angeles\nCharlie\t35\tChicago\n```", + { + "type": "tsv", + "variableId": "people", + "content": "name\tage\tcity\nAlice\t30\tNew York\nBob\t25\tLos Angeles\nCharlie\t35\tChicago" + }, { "type": "tabulator", "dataSourceName": "people", @@ -62,7 +78,12 @@ "## Inline DSV Data (Custom Delimiter)", "For custom delimiters, use the **dsv plugin** with `delimiter:` and `variableId:` parameters. Here's an example with pipe-separated values:", "", - "```dsv delimiter:| variableId:products\nproduct|category|rating\nLaptop|Electronics|4.5\nChair|Furniture|4.2\nBook|Education|4.8\n```", + { + "type": "dsv", + "variableId": "products", + "delimiter": "|", + "content": "product|category|rating\nLaptop|Electronics|4.5\nChair|Furniture|4.2\nBook|Education|4.8" + }, { "type": "tabulator", "dataSourceName": "products", From e70d7593cba90fa28d0454a3711e37adab630b6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 22:55:37 +0000 Subject: [PATCH 26/47] Polish schema and compiler: minimal comments, arrays for content, validator support Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- docs/dist/v1/chartifact.compiler.umd.js | 37 ++ docs/dist/v1/chartifact.editor.umd.js | 37 ++ docs/dist/v1/chartifact.host.umd.js | 37 ++ docs/dist/v1/chartifact.markdown.umd.js | 349 ++++++++++++++---- docs/schema/idoc_v1.d.ts | 52 ++- docs/schema/idoc_v1.json | 225 +++++++++++ packages/compiler/src/md.ts | 37 ++ packages/compiler/src/validate/element.ts | 17 + packages/schema-doc/src/inline-data.ts | 41 +- packages/schema-doc/src/interactive.ts | 8 +- .../features/2.4.inline-csv-data.idoc.json | 21 +- .../json/features/yaml-value-test.idoc.json | 11 +- 12 files changed, 748 insertions(+), 124 deletions(-) diff --git a/docs/dist/v1/chartifact.compiler.umd.js b/docs/dist/v1/chartifact.compiler.umd.js index 6c956f2f..23441b2f 100644 --- a/docs/dist/v1/chartifact.compiler.umd.js +++ b/docs/dist/v1/chartifact.compiler.umd.js @@ -1430,6 +1430,43 @@ ${content} addSpec("number", numberSpec, false); break; } + case "csv": { + const { variableId, content } = element; + const csvContent = Array.isArray(content) ? content.join("\n") : content; + mdElements.push(tickWrap(`csv ${variableId}`, csvContent)); + break; + } + case "tsv": { + const { variableId, content } = element; + const tsvContent = Array.isArray(content) ? content.join("\n") : content; + mdElements.push(tickWrap(`tsv ${variableId}`, tsvContent)); + break; + } + case "dsv": { + const { variableId, delimiter, content } = element; + const dsvContent2 = Array.isArray(content) ? content.join("\n") : content; + mdElements.push(tickWrap(`dsv delimiter:${delimiter} ${variableId}`, dsvContent2)); + break; + } + case "json-value": { + const { variableId, content } = element; + const jsonContent = JSON.stringify(content, null, defaultJsonIndent); + mdElements.push(tickWrap(`json value ${variableId}`, jsonContent)); + break; + } + case "yaml-value": { + const { variableId, content } = element; + let yamlContent; + if (typeof content === "string") { + yamlContent = content; + } else if (Array.isArray(content) && content.every((item) => typeof item === "string")) { + yamlContent = content.join("\n"); + } else { + yamlContent = yaml__namespace.dump(content, { indent: defaultJsonIndent }); + } + mdElements.push(tickWrap(`yaml value ${variableId}`, trimTrailingNewline(yamlContent))); + break; + } default: { mdElements.push(tickWrap("#", JSON.stringify(element))); } diff --git a/docs/dist/v1/chartifact.editor.umd.js b/docs/dist/v1/chartifact.editor.umd.js index adb27c31..7a32dbe7 100644 --- a/docs/dist/v1/chartifact.editor.umd.js +++ b/docs/dist/v1/chartifact.editor.umd.js @@ -2761,6 +2761,43 @@ ${content} addSpec("number", numberSpec, false); break; } + case "csv": { + const { variableId, content } = element; + const csvContent = Array.isArray(content) ? content.join("\n") : content; + mdElements.push(tickWrap(`csv ${variableId}`, csvContent)); + break; + } + case "tsv": { + const { variableId, content } = element; + const tsvContent = Array.isArray(content) ? content.join("\n") : content; + mdElements.push(tickWrap(`tsv ${variableId}`, tsvContent)); + break; + } + case "dsv": { + const { variableId, delimiter, content } = element; + const dsvContent2 = Array.isArray(content) ? content.join("\n") : content; + mdElements.push(tickWrap(`dsv delimiter:${delimiter} ${variableId}`, dsvContent2)); + break; + } + case "json-value": { + const { variableId, content } = element; + const jsonContent = JSON.stringify(content, null, defaultJsonIndent); + mdElements.push(tickWrap(`json value ${variableId}`, jsonContent)); + break; + } + case "yaml-value": { + const { variableId, content } = element; + let yamlContent; + if (typeof content === "string") { + yamlContent = content; + } else if (Array.isArray(content) && content.every((item) => typeof item === "string")) { + yamlContent = content.join("\n"); + } else { + yamlContent = dump(content, { indent: defaultJsonIndent }); + } + mdElements.push(tickWrap(`yaml value ${variableId}`, trimTrailingNewline(yamlContent))); + break; + } default: { mdElements.push(tickWrap("#", JSON.stringify(element))); } diff --git a/docs/dist/v1/chartifact.host.umd.js b/docs/dist/v1/chartifact.host.umd.js index 681d27c9..cb42e292 100644 --- a/docs/dist/v1/chartifact.host.umd.js +++ b/docs/dist/v1/chartifact.host.umd.js @@ -2761,6 +2761,43 @@ ${content} addSpec("number", numberSpec, false); break; } + case "csv": { + const { variableId, content } = element; + const csvContent = Array.isArray(content) ? content.join("\n") : content; + mdElements.push(tickWrap(`csv ${variableId}`, csvContent)); + break; + } + case "tsv": { + const { variableId, content } = element; + const tsvContent = Array.isArray(content) ? content.join("\n") : content; + mdElements.push(tickWrap(`tsv ${variableId}`, tsvContent)); + break; + } + case "dsv": { + const { variableId, delimiter, content } = element; + const dsvContent2 = Array.isArray(content) ? content.join("\n") : content; + mdElements.push(tickWrap(`dsv delimiter:${delimiter} ${variableId}`, dsvContent2)); + break; + } + case "json-value": { + const { variableId, content } = element; + const jsonContent = JSON.stringify(content, null, defaultJsonIndent); + mdElements.push(tickWrap(`json value ${variableId}`, jsonContent)); + break; + } + case "yaml-value": { + const { variableId, content } = element; + let yamlContent; + if (typeof content === "string") { + yamlContent = content; + } else if (Array.isArray(content) && content.every((item) => typeof item === "string")) { + yamlContent = content.join("\n"); + } else { + yamlContent = dump(content, { indent: defaultJsonIndent }); + } + mdElements.push(tickWrap(`yaml value ${variableId}`, trimTrailingNewline(yamlContent))); + break; + } default: { mdElements.push(tickWrap("#", JSON.stringify(element))); } diff --git a/docs/dist/v1/chartifact.markdown.umd.js b/docs/dist/v1/chartifact.markdown.umd.js index ef28a70b..62719582 100644 --- a/docs/dist/v1/chartifact.markdown.umd.js +++ b/docs/dist/v1/chartifact.markdown.umd.js @@ -548,16 +548,28 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy element.setAttribute(key, attributes[key]); }); if (precedeWithScriptTag) { - const scriptElement = domDocument.createElement("script"); - scriptElement.setAttribute("type", "application/json"); - const safeContent = content.replace(/<\/script>/gi, "<\\/script>"); - scriptElement.innerHTML = safeContent; + const scriptElement = sanitizedScriptTag(content); return scriptElement.outerHTML + element.outerHTML; } else { element.textContent = content; } return element.outerHTML; } + function sanitizedScriptTag(content, attributes) { + if (!domDocument) { + throw new Error("No DOM Document available. Please set domDocument using setDomDocument."); + } + const scriptElement = domDocument.createElement("script"); + scriptElement.setAttribute("type", "application/json"); + if (attributes) { + Object.keys(attributes).forEach((key) => { + scriptElement.setAttribute(key, attributes[key]); + }); + } + const safeContent = content.replace(/<\/script>/gi, "<\\/script>"); + scriptElement.innerHTML = safeContent; + return scriptElement; + } function sanitizeHtmlComment(content) { const tempElement = document.createElement("div"); tempElement.textContent = content; @@ -628,10 +640,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy loading: "0.1", error: "0.5" }; - const pluginName$g = "image"; - const className$e = pluginClassName(pluginName$g); + const pluginName$h = "image"; + const className$f = pluginClassName(pluginName$h); const imagePlugin = { - ...flaggablePlugin(pluginName$g, className$e), + ...flaggablePlugin(pluginName$h, className$f), hydrateComponent: async (renderer, errorHandler, specs) => { const imageInstances = []; for (let index2 = 0; index2 < specs.length; index2++) { @@ -646,11 +658,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy container, null, (error) => { - errorHandler(error, pluginName$g, index2, "load", container, img.src); + errorHandler(error, pluginName$h, index2, "load", container, img.src); } ); const imageInstance = { - id: `${pluginName$g}-${index2}`, + id: `${pluginName$h}-${index2}`, spec, img: null, // Will be set below @@ -708,7 +720,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy if (isSafeImageUrl(src)) { tempImg.setAttribute("src", src); } else { - errorHandler(new Error(`Unsafe image URL: ${src}`), pluginName$g, instanceIndex, "load", null, src); + errorHandler(new Error(`Unsafe image URL: ${src}`), pluginName$h, instanceIndex, "load", null, src); } } tempImg.setAttribute("alt", alt); @@ -827,10 +839,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy } return null; } - const pluginName$f = "placeholders"; - const imageClassName = pluginClassName(pluginName$f + "_image"); + const pluginName$g = "placeholders"; + const imageClassName = pluginClassName(pluginName$g + "_image"); const placeholdersPlugin = { - name: pluginName$f, + name: pluginName$g, initializePlugin: async (md) => { md.use(function(md2) { md2.inline.ruler.after("emphasis", "dynamic_placeholder", function(state, silent) { @@ -951,7 +963,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy for (const element of Array.from(dynamicImages)) { const { dynamicUrl, img } = createImageLoadingLogic(element, null, (error) => { const index2 = -1; - errorHandler(error, pluginName$f, index2, "load", element, img.src); + errorHandler(error, pluginName$g, index2, "load", element, img.src); }); if (!dynamicUrl) { continue; @@ -976,7 +988,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy }); const instances = [ { - id: pluginName$f, + id: pluginName$g, initialSignals, receiveBatch: async (batch) => { var _a, _b; @@ -1088,24 +1100,21 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy const directPlugin = findPlugin(info); if (directPlugin) { return directPlugin; - } else { - const infoWords = info.split(/\s+/); - if (infoWords.length > 0) { - const pluginPrefix = findPluginByPrefix(infoWords[0]); - if (pluginPrefix) { - return pluginPrefix; - } + } + const infoWords = info.split(/\s+/); + if (infoWords.length > 0) { + const pluginPrefix = findPluginByPrefix(infoWords[0]); + if (pluginPrefix) { + return pluginPrefix; } } - if (info.startsWith("json ")) { - const jsonPluginName = info.slice(5).trim(); - const jsonPlugin = findPlugin(jsonPluginName); + if (infoWords[0] === "json" && infoWords.length > 1) { + const jsonPlugin = findPlugin(infoWords[1]); if (jsonPlugin) { return jsonPlugin; } - } else if (info.startsWith("yaml ")) { - const yamlPluginName = info.slice(5).trim(); - const yamlPlugin = findPlugin(yamlPluginName); + } else if (info.startsWith("yaml ") && infoWords.length > 1) { + const yamlPlugin = findPlugin(infoWords[1]); if (yamlPlugin) { return yamlPlugin; } @@ -1123,10 +1132,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy }; return md; } - const pluginName$e = "checkbox"; - const className$d = pluginClassName(pluginName$e); + const pluginName$f = "checkbox"; + const className$e = pluginClassName(pluginName$f); const checkboxPlugin = { - ...flaggablePlugin(pluginName$e, className$d), + ...flaggablePlugin(pluginName$f, className$e), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const checkboxInstances = []; @@ -1147,7 +1156,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy `; container.innerHTML = html; const element = container.querySelector('input[type="checkbox"]'); - const checkboxInstance = { id: `${pluginName$e}-${index2}`, spec, element }; + const checkboxInstance = { id: `${pluginName$f}-${index2}`, spec, element }; checkboxInstances.push(checkboxInstance); } const instances = checkboxInstances.map((checkboxInstance) => { @@ -1190,9 +1199,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy return instances; } }; - const pluginName$d = "#"; + const pluginName$e = "#"; const commentPlugin = { - name: pluginName$d, + name: pluginName$e, fence: (token) => { const content = token.content.trim(); return sanitizeHtmlComment(content); @@ -1421,14 +1430,14 @@ ${reconstitutedRules.join("\n\n")} } return result; } - const pluginName$c = "css"; - const className$c = pluginClassName(pluginName$c); + const pluginName$d = "css"; + const className$d = pluginClassName(pluginName$d); const cssPlugin = { - ...flaggablePlugin(pluginName$c, className$c), + ...flaggablePlugin(pluginName$d, className$d), fence: (token, index2) => { const cssContent = token.content.trim(); const categorizedCss = categorizeCss(cssContent); - return sanitizedHTML("div", { id: `${pluginName$c}-${index2}`, class: className$c }, JSON.stringify(categorizedCss), true); + return sanitizedHTML("div", { id: `${pluginName$d}-${index2}`, class: className$d }, JSON.stringify(categorizedCss), true); }, hydrateComponent: async (renderer, errorHandler, specs) => { const cssInstances = []; @@ -1451,7 +1460,7 @@ ${reconstitutedRules.join("\n\n")} target.appendChild(styleElement); comments.push(``); cssInstances.push({ - id: `${pluginName$c}-${index2}`, + id: `${pluginName$d}-${index2}`, element: styleElement }); } else { @@ -1528,18 +1537,18 @@ ${reconstitutedRules.join("\n\n")} } return result; } - const pluginName$b = "dsv"; - const className$b = pluginClassName(pluginName$b); + const pluginName$c = "dsv"; + const className$c = pluginClassName(pluginName$c); const dsvPlugin = { - name: pluginName$b, + name: pluginName$c, fence: (token, index2) => { const content = token.content.trim(); const info = token.info.trim(); const { delimiter, wasDefaultDelimiter } = parseDelimiter(info); const { variableId, wasDefaultId } = parseVariableId(info, "dsv", index2); return sanitizedHTML("pre", { - id: `${pluginName$b}-${index2}`, - class: className$b, + id: `${pluginName$c}-${index2}`, + class: className$c, style: "display:none", "data-variable-id": variableId, "data-delimiter": delimiter, @@ -1550,7 +1559,7 @@ ${reconstitutedRules.join("\n\n")} hydrateSpecs: (renderer, errorHandler) => { var _a; const flagged = []; - const containers = renderer.element.querySelectorAll(`.${className$b}`); + const containers = renderer.element.querySelectorAll(`.${className$c}`); for (const [index2, container] of Array.from(containers).entries()) { try { const variableId = container.getAttribute("data-variable-id"); @@ -1558,18 +1567,18 @@ ${reconstitutedRules.join("\n\n")} const wasDefaultId = container.getAttribute("data-was-default-id") === "true"; const wasDefaultDelimiter = container.getAttribute("data-was-default-delimiter") === "true"; if (!variableId) { - errorHandler(new Error("No variable ID found"), pluginName$b, index2, "parse", container); + errorHandler(new Error("No variable ID found"), pluginName$c, index2, "parse", container); continue; } if (!delimiter) { - errorHandler(new Error("No delimiter found"), pluginName$b, index2, "parse", container); + errorHandler(new Error("No delimiter found"), pluginName$c, index2, "parse", container); continue; } const spec = { variableId, delimiter, wasDefaultId, wasDefaultDelimiter }; const flaggableSpec = inspectDsvSpec(spec); const f = { approvedSpec: null, - pluginName: pluginName$b, + pluginName: pluginName$c, containerId: container.id }; if (flaggableSpec.hasFlags) { @@ -1580,7 +1589,7 @@ ${reconstitutedRules.join("\n\n")} } flagged.push(f); } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$c, index2, "parse", container); } } return flagged; @@ -1596,19 +1605,19 @@ ${reconstitutedRules.join("\n\n")} } const container = renderer.element.querySelector(`#${specReview.containerId}`); if (!container) { - errorHandler(new Error("Container not found"), pluginName$b, index2, "init", null); + errorHandler(new Error("Container not found"), pluginName$c, index2, "init", null); continue; } try { const content = (_a = container.textContent) == null ? void 0 : _a.trim(); if (!content) { - errorHandler(new Error("No DSV content found"), pluginName$b, index2, "parse", container); + errorHandler(new Error("No DSV content found"), pluginName$c, index2, "parse", container); continue; } const spec = specReview.approvedSpec; const data = vega.read(content, { type: "dsv", delimiter: spec.delimiter }); const dsvInstance = { - id: `${pluginName$b}-${index2}`, + id: `${pluginName$c}-${index2}`, spec, data }; @@ -1617,7 +1626,7 @@ ${reconstitutedRules.join("\n\n")} const comment = sanitizeHtmlComment(`${delimiterName} data loaded: ${data.length} rows for variable '${spec.variableId}'`); container.insertAdjacentHTML("beforebegin", comment); } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$c, index2, "parse", container); } } const instances = dsvInstances.map((dsvInstance) => { @@ -1660,6 +1669,167 @@ ${reconstitutedRules.join("\n\n")} return dsvPlugin.fence(dsvToken, index2); } }; + function inspectValueSpec(spec) { + const result = { + spec, + hasFlags: false, + reasons: [] + }; + if (spec.wasDefaultId) { + result.hasFlags = true; + result.reasons.push("No variable ID specified - using default"); + } + return result; + } + const pluginName$b = "value"; + const className$b = pluginClassName(pluginName$b); + const valuePlugin = { + name: pluginName$b, + fence: (token, index2) => { + const content = token.content.trim(); + const info = token.info.trim(); + const parts = info.split(/\s+/); + let variableId; + let wasDefaultId = false; + let isYaml = false; + if (parts.length >= 3 && (parts[0] === "json" || parts[0] === "yaml") && parts[1] === "value") { + variableId = parts[2]; + isYaml = parts[0] === "yaml"; + } else if (parts.length >= 2 && (parts[0] === "json" || parts[0] === "yaml") && parts[1] === "value") { + variableId = `${parts[0]}Value${index2}`; + wasDefaultId = true; + isYaml = parts[0] === "yaml"; + } else { + return ""; + } + const scriptElement = sanitizedScriptTag(content, { + id: `${pluginName$b}-${index2}`, + class: className$b, + "data-variable-id": variableId, + "data-was-default-id": wasDefaultId.toString(), + "data-format": isYaml ? "yaml" : "json" + }); + return scriptElement.outerHTML; + }, + hydrateSpecs: (renderer, errorHandler) => { + var _a; + const flagged = []; + const containers = renderer.element.querySelectorAll(`.${className$b}`); + for (const [index2, container] of Array.from(containers).entries()) { + try { + const variableId = container.getAttribute("data-variable-id"); + const wasDefaultId = container.getAttribute("data-was-default-id") === "true"; + if (!variableId) { + errorHandler(new Error("No variable ID found"), pluginName$b, index2, "parse", container); + continue; + } + const spec = { variableId, wasDefaultId }; + const flaggableSpec = inspectValueSpec(spec); + const f = { + approvedSpec: null, + pluginName: pluginName$b, + containerId: container.id + }; + if (flaggableSpec.hasFlags) { + f.blockedSpec = flaggableSpec.spec; + f.reason = ((_a = flaggableSpec.reasons) == null ? void 0 : _a.join(", ")) || "Unknown reason"; + } else { + f.approvedSpec = flaggableSpec.spec; + } + flagged.push(f); + } catch (e) { + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); + } + } + return flagged; + }, + hydrateComponent: async (renderer, errorHandler, specs) => { + var _a; + const { signalBus } = renderer; + const valueInstances = []; + for (let index2 = 0; index2 < specs.length; index2++) { + const specReview = specs[index2]; + if (!specReview.approvedSpec) { + continue; + } + const container = renderer.element.querySelector(`#${specReview.containerId}`); + if (!container) { + errorHandler(new Error("Container not found"), pluginName$b, index2, "init", null); + continue; + } + try { + const content = (_a = container.textContent) == null ? void 0 : _a.trim(); + if (!content) { + errorHandler(new Error("No value content found"), pluginName$b, index2, "parse", container); + continue; + } + const spec = specReview.approvedSpec; + const format = container.getAttribute("data-format") || "json"; + let data; + try { + let parsed; + if (format === "yaml") { + parsed = yaml__namespace.load(content); + } else { + parsed = JSON.parse(content); + } + if (Array.isArray(parsed)) { + data = parsed; + } else { + data = [parsed]; + } + } catch (parseError) { + errorHandler( + new Error(`Invalid ${format.toUpperCase()}: ${parseError instanceof Error ? parseError.message : String(parseError)}`), + pluginName$b, + index2, + "parse", + container + ); + continue; + } + const valueInstance = { + id: `${pluginName$b}-${index2}`, + spec, + data + }; + valueInstances.push(valueInstance); + const comment = sanitizeHtmlComment(`${format.toUpperCase()} value loaded: ${data.length} rows for variable '${spec.variableId}'`); + container.insertAdjacentHTML("beforebegin", comment); + } catch (e) { + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); + } + } + const instances = valueInstances.map((valueInstance) => { + const { spec, data } = valueInstance; + const initialSignals = [{ + name: spec.variableId, + value: data, + priority: 1, + isData: true + }]; + return { + ...valueInstance, + initialSignals, + beginListening() { + const batch = { + [spec.variableId]: { + value: data, + isData: true + } + }; + signalBus.broadcast(valueInstance.id, batch); + }, + getCurrentSignalValue: () => { + return data; + }, + destroy: () => { + } + }; + }); + return instances; + } + }; function isValidGoogleFontsUrl(url) { try { const parsed = new URL(url); @@ -2113,12 +2283,11 @@ ${reconstitutedRules.join("\n\n")} container, signals: {}, tokens, + renderingDiagram: null, lastRenderedDiagram: null }; mermaidInstances.push(mermaidInstance); - if (spec.diagramText && typeof spec.diagramText === "string") { - await renderRawDiagram(mermaidInstance.id, mermaidInstance.container, spec.diagramText, errorHandler, pluginName$8, index2); - } + await renderRawDiagram(mermaidInstance, spec.diagramText, errorHandler, pluginName$8, index2); } const instances = mermaidInstances.map((mermaidInstance, index2) => { const { spec, signals, tokens } = mermaidInstance; @@ -2165,18 +2334,14 @@ ${reconstitutedRules.join("\n\n")} }); } } - if (diagramText && mermaidInstance.lastRenderedDiagram !== diagramText) { - await renderRawDiagram(mermaidInstance.id, mermaidInstance.container, diagramText, errorHandler, pluginName$8, index2); - mermaidInstance.lastRenderedDiagram = diagramText; - } + await renderRawDiagram(mermaidInstance, diagramText, errorHandler, pluginName$8, index2); } else { mermaidInstance.container.innerHTML = '
No data available to render diagram
'; } } else if (variableId && batch[variableId]) { const value = batch[variableId].value; if (typeof value === "string" && value.trim().length > 0) { - await renderRawDiagram(mermaidInstance.id, mermaidInstance.container, value, errorHandler, pluginName$8, index2); - mermaidInstance.lastRenderedDiagram = value; + await renderRawDiagram(mermaidInstance, value, errorHandler, pluginName$8, index2); } else { mermaidInstance.container.innerHTML = '
No diagram to display
'; } @@ -2191,10 +2356,18 @@ ${reconstitutedRules.join("\n\n")} const lines = diagramText.split("\n"); return lines.length > 1 && lines[1].trim().length > 0; } - async function renderRawDiagram(id, container, diagramText, errorHandler, pluginName2, index2) { + async function renderRawDiagram(mermaidInstance, diagramText, errorHandler, pluginName2, index2) { + if (!diagramText || typeof diagramText !== "string" || diagramText.trim().length === 0) { + return; + } + if (mermaidInstance.renderingDiagram === diagramText) { + return; + } + mermaidInstance.renderingDiagram = diagramText; if (typeof mermaid === "undefined") { await loadMermaidFromCDN(); } + const { id, container } = mermaidInstance; if (typeof mermaid === "undefined") { container.innerHTML = '
Mermaid library not loaded dynamically
'; return; @@ -2206,6 +2379,8 @@ ${reconstitutedRules.join("\n\n")} try { const { svg } = await mermaid.render(id, diagramText); container.innerHTML = svg; + mermaidInstance.lastRenderedDiagram = diagramText; + mermaidInstance.renderingDiagram = null; } catch (error) { container.innerHTML = `
Failed to render diagram ${id}
${diagramText}
`; errorHandler(error instanceof Error ? error : new Error(String(error)), pluginName2, index2, "render", container); @@ -3448,7 +3623,9 @@ ${reconstitutedRules.join("\n\n")} this.broadcastingStack.pop(); for (const signalName in batch) { const signalDep = this.signalDeps[signalName]; - signalDep.value = batch[signalName].value; + if (signalDep) { + signalDep.value = batch[signalName].value; + } } if (this.broadcastingStack.length === 0) { for (const peer of this.peers) { @@ -3490,14 +3667,14 @@ ${reconstitutedRules.join("\n\n")} async beginListening() { this.active = true; this.log("beginListening", "begin initial batch", this.signalDeps); + const initialBatch = {}; + for (const signalName in this.signalDeps) { + const signalDep = this.signalDeps[signalName]; + const { value, isData } = signalDep; + initialBatch[signalName] = { value, isData }; + } for (const peer of this.peers) { - const batch = {}; - for (const signalName in this.signalDeps) { - const signalDep = this.signalDeps[signalName]; - const { value, isData } = signalDep; - batch[signalName] = { value, isData }; - } - peer.receiveBatch && peer.receiveBatch(batch, "initial"); + peer.receiveBatch && peer.receiveBatch(initialBatch, "initial"); } for (const peer of this.peers) { peer.broadcastComplete && await peer.broadcastComplete(); @@ -3615,17 +3792,24 @@ ${reconstitutedRules.join("\n\n")} receiveBatch: async (batch, from) => { signalBus.log(vegaInstance.id, "received batch", batch, from); return new Promise((resolve) => { - view.runAfter(async () => { + if (vegaInstance.isListening) { + view.runAfter(async () => { + if (receiveBatch(batch, signalBus, vegaInstance)) { + signalBus.log(vegaInstance.id, "running after _pulse, changes from", from); + view.resize(); + vegaInstance.needToRun = true; + } else { + signalBus.log(vegaInstance.id, "no changes"); + } + signalBus.log(vegaInstance.id, "running view after _pulse finished"); + resolve(); + }); + } else { if (receiveBatch(batch, signalBus, vegaInstance)) { - signalBus.log(vegaInstance.id, "running after _pulse, changes from", from); - view.resize(); vegaInstance.needToRun = true; - } else { - signalBus.log(vegaInstance.id, "no changes"); } - signalBus.log(vegaInstance.id, "running view after _pulse finished"); resolve(); - }); + } }); }, broadcastComplete: async () => { @@ -3672,6 +3856,11 @@ ${reconstitutedRules.join("\n\n")} } } } + vegaInstance.isListening = true; + if (vegaInstance.needToRun) { + view.runAsync(); + vegaInstance.needToRun = false; + } }, getCurrentSignalValue: (signalName) => { var _a; @@ -3914,6 +4103,7 @@ ${reconstitutedRules.join("\n\n")} registerMarkdownPlugin(commentPlugin); registerMarkdownPlugin(cssPlugin); registerMarkdownPlugin(csvPlugin); + registerMarkdownPlugin(valuePlugin); registerMarkdownPlugin(dsvPlugin); registerMarkdownPlugin(googleFontsPlugin); registerMarkdownPlugin(dropdownPlugin); @@ -4045,8 +4235,11 @@ ${reconstitutedRules.join("\n\n")} } } reset() { + const { logLevel, logWatchIds } = this.signalBus; this.signalBus.deactivate(); this.signalBus = new SignalBus(defaultCommonOptions.dataSignalPrefix); + this.signalBus.logLevel = logLevel; + this.signalBus.logWatchIds = logWatchIds; for (const pluginName2 of Object.keys(this.instances)) { const instances = this.instances[pluginName2]; for (const instance of instances) { diff --git a/docs/schema/idoc_v1.d.ts b/docs/schema/idoc_v1.d.ts index 7fb3df38..7a83f9ab 100644 --- a/docs/schema/idoc_v1.d.ts +++ b/docs/schema/idoc_v1.d.ts @@ -95,6 +95,52 @@ interface DataLoaderBySpec { spec: object; } type DataLoader = DataSource | DataLoaderBySpec; +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ +/** + * CSV data in markdown fence: ```csv variableId + */ +interface CsvElement extends ElementBase { + type: 'csv'; + variableId: VariableID; + content: string | string[] | string[][]; +} +/** + * TSV data in markdown fence: ```tsv variableId + */ +interface TsvElement extends ElementBase { + type: 'tsv'; + variableId: VariableID; + content: string | string[] | string[][]; +} +/** + * Custom delimiter-separated data in markdown fence: ```dsv delimiter:| variableId + */ +interface DsvElement extends ElementBase { + type: 'dsv'; + variableId: VariableID; + delimiter: string; + content: string | string[] | string[][]; +} +/** + * JSON data with type preservation in markdown fence: ```json value variableId + */ +interface JsonValueElement extends ElementBase { + type: 'json-value'; + variableId: VariableID; + content: object | object[]; +} +/** + * YAML data with type preservation in markdown fence: ```yaml value variableId + */ +interface YamlValueElement extends ElementBase { + type: 'yaml-value'; + variableId: VariableID; + content: object | object[] | string | string[]; +} +type InlineDataElement = CsvElement | TsvElement | DsvElement | JsonValueElement | YamlValueElement; /** * Interactive Elements */ @@ -256,7 +302,7 @@ interface TabulatorElementProps extends OptionalVariableControl { /** * Union type for all possible interactive elements */ -type InteractiveElement = ChartElement | CheckboxElement | DropdownElement | ImageElement | MermaidElement | NumberElement | PresetsElement | SliderElement | TabulatorElement | TextboxElement | TreebarkElement; +type InteractiveElement = ChartElement | CheckboxElement | DropdownElement | ImageElement | MermaidElement | NumberElement | PresetsElement | SliderElement | TabulatorElement | TextboxElement | TreebarkElement | InlineDataElement; interface ElementGroup { groupId: string; elements: PageElement[]; @@ -289,7 +335,7 @@ interface InteractiveDocument { */ type MarkdownElement = string; /** Union type for all possible elements */ -type PageElement = MarkdownElement | InteractiveElement; +type PageElement = MarkdownElement | InteractiveElement | InlineDataElement; interface PageStyle { /** CSS styles, either a string, or array of strings which will be concatenated. The array is for developer ergonomics for authoring and merging. */ css: string | string[]; @@ -316,4 +362,4 @@ interface GoogleFontsSpec { type InteractiveDocumentWithSchema = InteractiveDocument & { $schema?: string; }; -export type { Calculation, ChartElement, CheckboxElement, CheckboxProps, DataFrameCalculation, DataLoader, DataLoaderBySpec, DataSource, DataSourceBase, DataSourceBaseFormat, DataSourceByDynamicURL, DataSourceByFile, DataSourceInline, DropdownElement, DropdownElementProps, DynamicDropdownOptions, ElementBase, ElementGroup, GoogleFontsSpec, ImageElement, ImageElementProps, InteractiveDocument, InteractiveDocumentWithSchema, InteractiveElement, MarkdownElement, MermaidElement, MermaidElementProps, MermaidTemplate, NumberElement, NumberElementProps, OptionalVariableControl, PageElement, PageStyle, Preset, PresetsElement, PresetsElementProps, ReturnType, ScalarCalculation, SliderElement, SliderElementProps, TabulatorElement, TabulatorElementProps, TemplatedUrl, TextboxElement, TextboxElementProps, TreebarkElement, TreebarkElementProps, Variable, VariableControl, VariableID, VariableType, VariableValue, VariableValueArray, VariableValuePrimitive, Vega_or_VegaLite_spec }; \ No newline at end of file +export type { Calculation, ChartElement, CheckboxElement, CheckboxProps, CsvElement, DataFrameCalculation, DataLoader, DataLoaderBySpec, DataSource, DataSourceBase, DataSourceBaseFormat, DataSourceByDynamicURL, DataSourceByFile, DataSourceInline, DropdownElement, DropdownElementProps, DsvElement, DynamicDropdownOptions, ElementBase, ElementGroup, GoogleFontsSpec, ImageElement, ImageElementProps, InlineDataElement, InteractiveDocument, InteractiveDocumentWithSchema, InteractiveElement, JsonValueElement, MarkdownElement, MermaidElement, MermaidElementProps, MermaidTemplate, NumberElement, NumberElementProps, OptionalVariableControl, PageElement, PageStyle, Preset, PresetsElement, PresetsElementProps, ReturnType, ScalarCalculation, SliderElement, SliderElementProps, TabulatorElement, TabulatorElementProps, TemplatedUrl, TextboxElement, TextboxElementProps, TreebarkElement, TreebarkElementProps, TsvElement, Variable, VariableControl, VariableID, VariableType, VariableValue, VariableValueArray, VariableValuePrimitive, Vega_or_VegaLite_spec, YamlValueElement }; \ No newline at end of file diff --git a/docs/schema/idoc_v1.json b/docs/schema/idoc_v1.json index c93a9361..9897fa6c 100644 --- a/docs/schema/idoc_v1.json +++ b/docs/schema/idoc_v1.json @@ -1083,6 +1083,47 @@ ], "type": "object" }, + "CsvElement": { + "additionalProperties": false, + "description": "CSV data in markdown fence: ```csv variableId", + "properties": { + "content": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + } + ] + }, + "type": { + "const": "csv", + "type": "string" + }, + "variableId": { + "$ref": "#/definitions/VariableID" + } + }, + "required": [ + "type", + "variableId", + "content" + ], + "type": "object" + }, "DataFrameCalculation": { "additionalProperties": false, "description": "DataFrame calculation for object arrays. Not for primitive/scalar values.", @@ -1614,6 +1655,51 @@ ], "type": "object" }, + "DsvElement": { + "additionalProperties": false, + "description": "Custom delimiter-separated data in markdown fence: ```dsv delimiter:| variableId", + "properties": { + "content": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + } + ] + }, + "delimiter": { + "type": "string" + }, + "type": { + "const": "dsv", + "type": "string" + }, + "variableId": { + "$ref": "#/definitions/VariableID" + } + }, + "required": [ + "type", + "variableId", + "delimiter", + "content" + ], + "type": "object" + }, "DynamicDropdownOptions": { "additionalProperties": false, "description": "Dropdown use for selecting from a list of options", @@ -3143,6 +3229,25 @@ ], "type": "object" }, + "InlineDataElement": { + "anyOf": [ + { + "$ref": "#/definitions/CsvElement" + }, + { + "$ref": "#/definitions/TsvElement" + }, + { + "$ref": "#/definitions/DsvElement" + }, + { + "$ref": "#/definitions/JsonValueElement" + }, + { + "$ref": "#/definitions/YamlValueElement" + } + ] + }, "InteractiveDocumentWithSchema": { "additionalProperties": false, "description": "JSON Schema version with $schema property for validation", @@ -3236,6 +3341,9 @@ }, { "$ref": "#/definitions/TreebarkElement" + }, + { + "$ref": "#/definitions/InlineDataElement" } ], "description": "Union type for all possible interactive elements" @@ -3364,6 +3472,38 @@ ], "type": "object" }, + "JsonValueElement": { + "additionalProperties": false, + "description": "JSON data with type preservation in markdown fence: ```json value variableId", + "properties": { + "content": { + "anyOf": [ + { + "type": "object" + }, + { + "items": { + "type": "object" + }, + "type": "array" + } + ] + }, + "type": { + "const": "json-value", + "type": "string" + }, + "variableId": { + "$ref": "#/definitions/VariableID" + } + }, + "required": [ + "type", + "variableId", + "content" + ], + "type": "object" + }, "KDE2DTransform": { "additionalProperties": false, "properties": { @@ -4138,6 +4278,9 @@ }, { "$ref": "#/definitions/InteractiveElement" + }, + { + "$ref": "#/definitions/InlineDataElement" } ], "description": "Union type for all possible elements" @@ -5893,6 +6036,47 @@ ], "type": "object" }, + "TsvElement": { + "additionalProperties": false, + "description": "TSV data in markdown fence: ```tsv variableId", + "properties": { + "content": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + } + ] + }, + "type": { + "const": "tsv", + "type": "string" + }, + "variableId": { + "$ref": "#/definitions/VariableID" + } + }, + "required": [ + "type", + "variableId", + "content" + ], + "type": "object" + }, "UlTag": { "additionalProperties": false, "properties": { @@ -6359,6 +6543,47 @@ "type" ], "type": "object" + }, + "YamlValueElement": { + "additionalProperties": false, + "description": "YAML data with type preservation in markdown fence: ```yaml value variableId", + "properties": { + "content": { + "anyOf": [ + { + "type": "object" + }, + { + "items": { + "type": "object" + }, + "type": "array" + }, + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "type": { + "const": "yaml-value", + "type": "string" + }, + "variableId": { + "$ref": "#/definitions/VariableID" + } + }, + "required": [ + "type", + "variableId", + "content" + ], + "type": "object" } } } \ No newline at end of file diff --git a/packages/compiler/src/md.ts b/packages/compiler/src/md.ts index 1c49d9fd..e3d8a20d 100644 --- a/packages/compiler/src/md.ts +++ b/packages/compiler/src/md.ts @@ -373,6 +373,43 @@ function groupMarkdown(group: ElementGroup, variables: Variable[], vegaScope: Ve addSpec('number', numberSpec, false); break; } + case 'csv': { + const { variableId, content } = element; + const csvContent = Array.isArray(content) ? content.join('\n') : content; + mdElements.push(tickWrap(`csv ${variableId}`, csvContent)); + break; + } + case 'tsv': { + const { variableId, content } = element; + const tsvContent = Array.isArray(content) ? content.join('\n') : content; + mdElements.push(tickWrap(`tsv ${variableId}`, tsvContent)); + break; + } + case 'dsv': { + const { variableId, delimiter, content } = element; + const dsvContent = Array.isArray(content) ? content.join('\n') : content; + mdElements.push(tickWrap(`dsv delimiter:${delimiter} ${variableId}`, dsvContent)); + break; + } + case 'json-value': { + const { variableId, content } = element; + const jsonContent = JSON.stringify(content, null, defaultJsonIndent); + mdElements.push(tickWrap(`json value ${variableId}`, jsonContent)); + break; + } + case 'yaml-value': { + const { variableId, content } = element; + let yamlContent: string; + if (typeof content === 'string') { + yamlContent = content; + } else if (Array.isArray(content) && content.every(item => typeof item === 'string')) { + yamlContent = content.join('\n'); + } else { + yamlContent = yaml.dump(content, { indent: defaultJsonIndent }); + } + mdElements.push(tickWrap(`yaml value ${variableId}`, trimTrailingNewline(yamlContent))); + break; + } default: { //output as a comment mdElements.push(tickWrap('#', JSON.stringify(element))); diff --git a/packages/compiler/src/validate/element.ts b/packages/compiler/src/validate/element.ts index a8b3307b..fd34fa73 100644 --- a/packages/compiler/src/validate/element.ts +++ b/packages/compiler/src/validate/element.ts @@ -190,6 +190,23 @@ export async function validateElement(element: PageElement, groupIndex: number, errors.push(...validateInputElementWithVariableId(element)); break; } + case 'csv': + case 'tsv': + case 'dsv': { + errors.push(...validateVariableID(element.variableId)); + if (element.type === 'dsv' && !element.delimiter) { + errors.push('DSV element must have a delimiter property'); + } + break; + } + case 'json-value': + case 'yaml-value': { + errors.push(...validateVariableID(element.variableId)); + if (!element.content) { + errors.push(`${element.type} element must have content property`); + } + break; + } default: { errors.push(`Unknown element type ${(element as any).type} at group ${groupIndex}, element index ${elementIndex}: ${JSON.stringify(element)}`); break; diff --git a/packages/schema-doc/src/inline-data.ts b/packages/schema-doc/src/inline-data.ts index 80fa8e2b..911ff1aa 100644 --- a/packages/schema-doc/src/inline-data.ts +++ b/packages/schema-doc/src/inline-data.ts @@ -5,82 +5,51 @@ import { VariableID, ElementBase } from './common.js'; /** - * Inline Data Elements - * These elements are defined using markdown fence blocks to embed data directly in the document. - */ - -/** - * CSV Element - * Embed CSV data inline using markdown fence block: ```csv variableId - * Content should be CSV rows (comma-separated values). - * All values are stored as strings. + * CSV data in markdown fence: ```csv variableId */ export interface CsvElement extends ElementBase { type: 'csv'; variableId: VariableID; - /** CSV content as string, array of strings, or array of string arrays */ content: string | string[] | string[][]; } /** - * TSV Element - * Embed TSV data inline using markdown fence block: ```tsv variableId - * Content should be TSV rows (tab-separated values). - * All values are stored as strings. + * TSV data in markdown fence: ```tsv variableId */ export interface TsvElement extends ElementBase { type: 'tsv'; variableId: VariableID; - /** TSV content as string, array of strings, or array of string arrays */ content: string | string[] | string[][]; } /** - * DSV Element - * Embed delimiter-separated data inline using markdown fence block: ```dsv delimiter:| variableId - * Content should be delimiter-separated rows. - * All values are stored as strings. + * Custom delimiter-separated data in markdown fence: ```dsv delimiter:| variableId */ export interface DsvElement extends ElementBase { type: 'dsv'; variableId: VariableID; - /** Custom delimiter character (e.g., '|', ';', etc.) */ delimiter: string; - /** DSV content as string, array of strings, or array of string arrays */ content: string | string[] | string[][]; } /** - * Value Element (JSON) - * Embed JSON data inline using markdown fence block: ```json value variableId - * Content should be a JSON array or object. - * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. - * Use this for structured data with type preservation. + * JSON data with type preservation in markdown fence: ```json value variableId */ export interface JsonValueElement extends ElementBase { type: 'json-value'; variableId: VariableID; - /** JSON content as array or object */ content: object | object[]; } /** - * Value Element (YAML) - * Embed YAML data inline using markdown fence block: ```yaml value variableId - * Content should be YAML array or object. - * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. - * Use this for structured data with type preservation. + * YAML data with type preservation in markdown fence: ```yaml value variableId */ export interface YamlValueElement extends ElementBase { type: 'yaml-value'; variableId: VariableID; - /** YAML content as array, object, string, or string array */ content: object | object[] | string | string[]; } -/** - * Union type for all inline data elements - */ export type InlineDataElement = | CsvElement | TsvElement diff --git a/packages/schema-doc/src/interactive.ts b/packages/schema-doc/src/interactive.ts index 1dbc2bc1..374feafc 100644 --- a/packages/schema-doc/src/interactive.ts +++ b/packages/schema-doc/src/interactive.ts @@ -1,9 +1,10 @@ /** - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ +* Copyright (c) Microsoft Corporation. +* Licensed under the MIT License. +*/ import { VariableID, VariableControl, ElementBase, TemplatedUrl, OptionalVariableControl } from './common.js'; import { TemplateElement } from 'treebark'; +import { InlineDataElement } from './inline-data.js'; /** * Interactive Elements @@ -250,4 +251,5 @@ export type InteractiveElement = | TabulatorElement | TextboxElement | TreebarkElement + | InlineDataElement ; diff --git a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json index f3c82093..3decaebb 100644 --- a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json +++ b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json @@ -37,7 +37,12 @@ { "type": "csv", "variableId": "officeSupplies", - "content": "item,price\nStapler,12.99\nPen,1.25\nLamp,29.99" + "content": [ + "item,price", + "Stapler,12.99", + "Pen,1.25", + "Lamp,29.99" + ] }, { "type": "tabulator", @@ -59,7 +64,12 @@ { "type": "tsv", "variableId": "people", - "content": "name\tage\tcity\nAlice\t30\tNew York\nBob\t25\tLos Angeles\nCharlie\t35\tChicago" + "content": [ + "name\tage\tcity", + "Alice\t30\tNew York", + "Bob\t25\tLos Angeles", + "Charlie\t35\tChicago" + ] }, { "type": "tabulator", @@ -82,7 +92,12 @@ "type": "dsv", "variableId": "products", "delimiter": "|", - "content": "product|category|rating\nLaptop|Electronics|4.5\nChair|Furniture|4.2\nBook|Education|4.8" + "content": [ + "product|category|rating", + "Laptop|Electronics|4.5", + "Chair|Furniture|4.2", + "Book|Education|4.8" + ] }, { "type": "tabulator", diff --git a/packages/web-deploy/json/features/yaml-value-test.idoc.json b/packages/web-deploy/json/features/yaml-value-test.idoc.json index 24d87d5a..b3da3e9c 100644 --- a/packages/web-deploy/json/features/yaml-value-test.idoc.json +++ b/packages/web-deploy/json/features/yaml-value-test.idoc.json @@ -8,7 +8,16 @@ "## Testing YAML Value Plugin", "This example tests the value plugin with YAML format.", "", - "```yaml value people\n- name: Alice\n age: 25\n city: New York\n\n- name: Bob\n age: 30\n city: Chicago\n\n- name: Carol\n age: 35\n city: Los Angeles\n\n- name: David\n age: 28\n city: Seattle\n```", + { + "type": "yaml-value", + "variableId": "people", + "content": [ + {"name": "Alice", "age": 25, "city": "New York"}, + {"name": "Bob", "age": 30, "city": "Chicago"}, + {"name": "Carol", "age": 35, "city": "Los Angeles"}, + {"name": "David", "age": 28, "city": "Seattle"} + ] + }, { "type": "tabulator", "dataSourceName": "people", From 8ae7b85cd3b381a970320f6c4ff587c0cf7e4fd5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 23:04:28 +0000 Subject: [PATCH 27/47] Revert built files (UMD bundles and schema files) from PR Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- docs/dist/v1/chartifact.compiler.umd.js | 37 --- docs/dist/v1/chartifact.editor.umd.js | 37 --- docs/dist/v1/chartifact.host.umd.js | 37 --- docs/dist/v1/chartifact.markdown.umd.js | 349 ++++++------------------ docs/schema/idoc_v1.d.ts | 50 +++- docs/schema/idoc_v1.json | 98 ++----- 6 files changed, 136 insertions(+), 472 deletions(-) diff --git a/docs/dist/v1/chartifact.compiler.umd.js b/docs/dist/v1/chartifact.compiler.umd.js index 23441b2f..6c956f2f 100644 --- a/docs/dist/v1/chartifact.compiler.umd.js +++ b/docs/dist/v1/chartifact.compiler.umd.js @@ -1430,43 +1430,6 @@ ${content} addSpec("number", numberSpec, false); break; } - case "csv": { - const { variableId, content } = element; - const csvContent = Array.isArray(content) ? content.join("\n") : content; - mdElements.push(tickWrap(`csv ${variableId}`, csvContent)); - break; - } - case "tsv": { - const { variableId, content } = element; - const tsvContent = Array.isArray(content) ? content.join("\n") : content; - mdElements.push(tickWrap(`tsv ${variableId}`, tsvContent)); - break; - } - case "dsv": { - const { variableId, delimiter, content } = element; - const dsvContent2 = Array.isArray(content) ? content.join("\n") : content; - mdElements.push(tickWrap(`dsv delimiter:${delimiter} ${variableId}`, dsvContent2)); - break; - } - case "json-value": { - const { variableId, content } = element; - const jsonContent = JSON.stringify(content, null, defaultJsonIndent); - mdElements.push(tickWrap(`json value ${variableId}`, jsonContent)); - break; - } - case "yaml-value": { - const { variableId, content } = element; - let yamlContent; - if (typeof content === "string") { - yamlContent = content; - } else if (Array.isArray(content) && content.every((item) => typeof item === "string")) { - yamlContent = content.join("\n"); - } else { - yamlContent = yaml__namespace.dump(content, { indent: defaultJsonIndent }); - } - mdElements.push(tickWrap(`yaml value ${variableId}`, trimTrailingNewline(yamlContent))); - break; - } default: { mdElements.push(tickWrap("#", JSON.stringify(element))); } diff --git a/docs/dist/v1/chartifact.editor.umd.js b/docs/dist/v1/chartifact.editor.umd.js index 7a32dbe7..adb27c31 100644 --- a/docs/dist/v1/chartifact.editor.umd.js +++ b/docs/dist/v1/chartifact.editor.umd.js @@ -2761,43 +2761,6 @@ ${content} addSpec("number", numberSpec, false); break; } - case "csv": { - const { variableId, content } = element; - const csvContent = Array.isArray(content) ? content.join("\n") : content; - mdElements.push(tickWrap(`csv ${variableId}`, csvContent)); - break; - } - case "tsv": { - const { variableId, content } = element; - const tsvContent = Array.isArray(content) ? content.join("\n") : content; - mdElements.push(tickWrap(`tsv ${variableId}`, tsvContent)); - break; - } - case "dsv": { - const { variableId, delimiter, content } = element; - const dsvContent2 = Array.isArray(content) ? content.join("\n") : content; - mdElements.push(tickWrap(`dsv delimiter:${delimiter} ${variableId}`, dsvContent2)); - break; - } - case "json-value": { - const { variableId, content } = element; - const jsonContent = JSON.stringify(content, null, defaultJsonIndent); - mdElements.push(tickWrap(`json value ${variableId}`, jsonContent)); - break; - } - case "yaml-value": { - const { variableId, content } = element; - let yamlContent; - if (typeof content === "string") { - yamlContent = content; - } else if (Array.isArray(content) && content.every((item) => typeof item === "string")) { - yamlContent = content.join("\n"); - } else { - yamlContent = dump(content, { indent: defaultJsonIndent }); - } - mdElements.push(tickWrap(`yaml value ${variableId}`, trimTrailingNewline(yamlContent))); - break; - } default: { mdElements.push(tickWrap("#", JSON.stringify(element))); } diff --git a/docs/dist/v1/chartifact.host.umd.js b/docs/dist/v1/chartifact.host.umd.js index cb42e292..681d27c9 100644 --- a/docs/dist/v1/chartifact.host.umd.js +++ b/docs/dist/v1/chartifact.host.umd.js @@ -2761,43 +2761,6 @@ ${content} addSpec("number", numberSpec, false); break; } - case "csv": { - const { variableId, content } = element; - const csvContent = Array.isArray(content) ? content.join("\n") : content; - mdElements.push(tickWrap(`csv ${variableId}`, csvContent)); - break; - } - case "tsv": { - const { variableId, content } = element; - const tsvContent = Array.isArray(content) ? content.join("\n") : content; - mdElements.push(tickWrap(`tsv ${variableId}`, tsvContent)); - break; - } - case "dsv": { - const { variableId, delimiter, content } = element; - const dsvContent2 = Array.isArray(content) ? content.join("\n") : content; - mdElements.push(tickWrap(`dsv delimiter:${delimiter} ${variableId}`, dsvContent2)); - break; - } - case "json-value": { - const { variableId, content } = element; - const jsonContent = JSON.stringify(content, null, defaultJsonIndent); - mdElements.push(tickWrap(`json value ${variableId}`, jsonContent)); - break; - } - case "yaml-value": { - const { variableId, content } = element; - let yamlContent; - if (typeof content === "string") { - yamlContent = content; - } else if (Array.isArray(content) && content.every((item) => typeof item === "string")) { - yamlContent = content.join("\n"); - } else { - yamlContent = dump(content, { indent: defaultJsonIndent }); - } - mdElements.push(tickWrap(`yaml value ${variableId}`, trimTrailingNewline(yamlContent))); - break; - } default: { mdElements.push(tickWrap("#", JSON.stringify(element))); } diff --git a/docs/dist/v1/chartifact.markdown.umd.js b/docs/dist/v1/chartifact.markdown.umd.js index 62719582..ef28a70b 100644 --- a/docs/dist/v1/chartifact.markdown.umd.js +++ b/docs/dist/v1/chartifact.markdown.umd.js @@ -548,28 +548,16 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy element.setAttribute(key, attributes[key]); }); if (precedeWithScriptTag) { - const scriptElement = sanitizedScriptTag(content); + const scriptElement = domDocument.createElement("script"); + scriptElement.setAttribute("type", "application/json"); + const safeContent = content.replace(/<\/script>/gi, "<\\/script>"); + scriptElement.innerHTML = safeContent; return scriptElement.outerHTML + element.outerHTML; } else { element.textContent = content; } return element.outerHTML; } - function sanitizedScriptTag(content, attributes) { - if (!domDocument) { - throw new Error("No DOM Document available. Please set domDocument using setDomDocument."); - } - const scriptElement = domDocument.createElement("script"); - scriptElement.setAttribute("type", "application/json"); - if (attributes) { - Object.keys(attributes).forEach((key) => { - scriptElement.setAttribute(key, attributes[key]); - }); - } - const safeContent = content.replace(/<\/script>/gi, "<\\/script>"); - scriptElement.innerHTML = safeContent; - return scriptElement; - } function sanitizeHtmlComment(content) { const tempElement = document.createElement("div"); tempElement.textContent = content; @@ -640,10 +628,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy loading: "0.1", error: "0.5" }; - const pluginName$h = "image"; - const className$f = pluginClassName(pluginName$h); + const pluginName$g = "image"; + const className$e = pluginClassName(pluginName$g); const imagePlugin = { - ...flaggablePlugin(pluginName$h, className$f), + ...flaggablePlugin(pluginName$g, className$e), hydrateComponent: async (renderer, errorHandler, specs) => { const imageInstances = []; for (let index2 = 0; index2 < specs.length; index2++) { @@ -658,11 +646,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy container, null, (error) => { - errorHandler(error, pluginName$h, index2, "load", container, img.src); + errorHandler(error, pluginName$g, index2, "load", container, img.src); } ); const imageInstance = { - id: `${pluginName$h}-${index2}`, + id: `${pluginName$g}-${index2}`, spec, img: null, // Will be set below @@ -720,7 +708,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy if (isSafeImageUrl(src)) { tempImg.setAttribute("src", src); } else { - errorHandler(new Error(`Unsafe image URL: ${src}`), pluginName$h, instanceIndex, "load", null, src); + errorHandler(new Error(`Unsafe image URL: ${src}`), pluginName$g, instanceIndex, "load", null, src); } } tempImg.setAttribute("alt", alt); @@ -839,10 +827,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy } return null; } - const pluginName$g = "placeholders"; - const imageClassName = pluginClassName(pluginName$g + "_image"); + const pluginName$f = "placeholders"; + const imageClassName = pluginClassName(pluginName$f + "_image"); const placeholdersPlugin = { - name: pluginName$g, + name: pluginName$f, initializePlugin: async (md) => { md.use(function(md2) { md2.inline.ruler.after("emphasis", "dynamic_placeholder", function(state, silent) { @@ -963,7 +951,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy for (const element of Array.from(dynamicImages)) { const { dynamicUrl, img } = createImageLoadingLogic(element, null, (error) => { const index2 = -1; - errorHandler(error, pluginName$g, index2, "load", element, img.src); + errorHandler(error, pluginName$f, index2, "load", element, img.src); }); if (!dynamicUrl) { continue; @@ -988,7 +976,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy }); const instances = [ { - id: pluginName$g, + id: pluginName$f, initialSignals, receiveBatch: async (batch) => { var _a, _b; @@ -1100,21 +1088,24 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy const directPlugin = findPlugin(info); if (directPlugin) { return directPlugin; - } - const infoWords = info.split(/\s+/); - if (infoWords.length > 0) { - const pluginPrefix = findPluginByPrefix(infoWords[0]); - if (pluginPrefix) { - return pluginPrefix; + } else { + const infoWords = info.split(/\s+/); + if (infoWords.length > 0) { + const pluginPrefix = findPluginByPrefix(infoWords[0]); + if (pluginPrefix) { + return pluginPrefix; + } } } - if (infoWords[0] === "json" && infoWords.length > 1) { - const jsonPlugin = findPlugin(infoWords[1]); + if (info.startsWith("json ")) { + const jsonPluginName = info.slice(5).trim(); + const jsonPlugin = findPlugin(jsonPluginName); if (jsonPlugin) { return jsonPlugin; } - } else if (info.startsWith("yaml ") && infoWords.length > 1) { - const yamlPlugin = findPlugin(infoWords[1]); + } else if (info.startsWith("yaml ")) { + const yamlPluginName = info.slice(5).trim(); + const yamlPlugin = findPlugin(yamlPluginName); if (yamlPlugin) { return yamlPlugin; } @@ -1132,10 +1123,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy }; return md; } - const pluginName$f = "checkbox"; - const className$e = pluginClassName(pluginName$f); + const pluginName$e = "checkbox"; + const className$d = pluginClassName(pluginName$e); const checkboxPlugin = { - ...flaggablePlugin(pluginName$f, className$e), + ...flaggablePlugin(pluginName$e, className$d), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const checkboxInstances = []; @@ -1156,7 +1147,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy `; container.innerHTML = html; const element = container.querySelector('input[type="checkbox"]'); - const checkboxInstance = { id: `${pluginName$f}-${index2}`, spec, element }; + const checkboxInstance = { id: `${pluginName$e}-${index2}`, spec, element }; checkboxInstances.push(checkboxInstance); } const instances = checkboxInstances.map((checkboxInstance) => { @@ -1199,9 +1190,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy return instances; } }; - const pluginName$e = "#"; + const pluginName$d = "#"; const commentPlugin = { - name: pluginName$e, + name: pluginName$d, fence: (token) => { const content = token.content.trim(); return sanitizeHtmlComment(content); @@ -1430,14 +1421,14 @@ ${reconstitutedRules.join("\n\n")} } return result; } - const pluginName$d = "css"; - const className$d = pluginClassName(pluginName$d); + const pluginName$c = "css"; + const className$c = pluginClassName(pluginName$c); const cssPlugin = { - ...flaggablePlugin(pluginName$d, className$d), + ...flaggablePlugin(pluginName$c, className$c), fence: (token, index2) => { const cssContent = token.content.trim(); const categorizedCss = categorizeCss(cssContent); - return sanitizedHTML("div", { id: `${pluginName$d}-${index2}`, class: className$d }, JSON.stringify(categorizedCss), true); + return sanitizedHTML("div", { id: `${pluginName$c}-${index2}`, class: className$c }, JSON.stringify(categorizedCss), true); }, hydrateComponent: async (renderer, errorHandler, specs) => { const cssInstances = []; @@ -1460,7 +1451,7 @@ ${reconstitutedRules.join("\n\n")} target.appendChild(styleElement); comments.push(``); cssInstances.push({ - id: `${pluginName$d}-${index2}`, + id: `${pluginName$c}-${index2}`, element: styleElement }); } else { @@ -1537,18 +1528,18 @@ ${reconstitutedRules.join("\n\n")} } return result; } - const pluginName$c = "dsv"; - const className$c = pluginClassName(pluginName$c); + const pluginName$b = "dsv"; + const className$b = pluginClassName(pluginName$b); const dsvPlugin = { - name: pluginName$c, + name: pluginName$b, fence: (token, index2) => { const content = token.content.trim(); const info = token.info.trim(); const { delimiter, wasDefaultDelimiter } = parseDelimiter(info); const { variableId, wasDefaultId } = parseVariableId(info, "dsv", index2); return sanitizedHTML("pre", { - id: `${pluginName$c}-${index2}`, - class: className$c, + id: `${pluginName$b}-${index2}`, + class: className$b, style: "display:none", "data-variable-id": variableId, "data-delimiter": delimiter, @@ -1559,7 +1550,7 @@ ${reconstitutedRules.join("\n\n")} hydrateSpecs: (renderer, errorHandler) => { var _a; const flagged = []; - const containers = renderer.element.querySelectorAll(`.${className$c}`); + const containers = renderer.element.querySelectorAll(`.${className$b}`); for (const [index2, container] of Array.from(containers).entries()) { try { const variableId = container.getAttribute("data-variable-id"); @@ -1567,18 +1558,18 @@ ${reconstitutedRules.join("\n\n")} const wasDefaultId = container.getAttribute("data-was-default-id") === "true"; const wasDefaultDelimiter = container.getAttribute("data-was-default-delimiter") === "true"; if (!variableId) { - errorHandler(new Error("No variable ID found"), pluginName$c, index2, "parse", container); + errorHandler(new Error("No variable ID found"), pluginName$b, index2, "parse", container); continue; } if (!delimiter) { - errorHandler(new Error("No delimiter found"), pluginName$c, index2, "parse", container); + errorHandler(new Error("No delimiter found"), pluginName$b, index2, "parse", container); continue; } const spec = { variableId, delimiter, wasDefaultId, wasDefaultDelimiter }; const flaggableSpec = inspectDsvSpec(spec); const f = { approvedSpec: null, - pluginName: pluginName$c, + pluginName: pluginName$b, containerId: container.id }; if (flaggableSpec.hasFlags) { @@ -1589,7 +1580,7 @@ ${reconstitutedRules.join("\n\n")} } flagged.push(f); } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$c, index2, "parse", container); + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); } } return flagged; @@ -1605,19 +1596,19 @@ ${reconstitutedRules.join("\n\n")} } const container = renderer.element.querySelector(`#${specReview.containerId}`); if (!container) { - errorHandler(new Error("Container not found"), pluginName$c, index2, "init", null); + errorHandler(new Error("Container not found"), pluginName$b, index2, "init", null); continue; } try { const content = (_a = container.textContent) == null ? void 0 : _a.trim(); if (!content) { - errorHandler(new Error("No DSV content found"), pluginName$c, index2, "parse", container); + errorHandler(new Error("No DSV content found"), pluginName$b, index2, "parse", container); continue; } const spec = specReview.approvedSpec; const data = vega.read(content, { type: "dsv", delimiter: spec.delimiter }); const dsvInstance = { - id: `${pluginName$c}-${index2}`, + id: `${pluginName$b}-${index2}`, spec, data }; @@ -1626,7 +1617,7 @@ ${reconstitutedRules.join("\n\n")} const comment = sanitizeHtmlComment(`${delimiterName} data loaded: ${data.length} rows for variable '${spec.variableId}'`); container.insertAdjacentHTML("beforebegin", comment); } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$c, index2, "parse", container); + errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); } } const instances = dsvInstances.map((dsvInstance) => { @@ -1669,167 +1660,6 @@ ${reconstitutedRules.join("\n\n")} return dsvPlugin.fence(dsvToken, index2); } }; - function inspectValueSpec(spec) { - const result = { - spec, - hasFlags: false, - reasons: [] - }; - if (spec.wasDefaultId) { - result.hasFlags = true; - result.reasons.push("No variable ID specified - using default"); - } - return result; - } - const pluginName$b = "value"; - const className$b = pluginClassName(pluginName$b); - const valuePlugin = { - name: pluginName$b, - fence: (token, index2) => { - const content = token.content.trim(); - const info = token.info.trim(); - const parts = info.split(/\s+/); - let variableId; - let wasDefaultId = false; - let isYaml = false; - if (parts.length >= 3 && (parts[0] === "json" || parts[0] === "yaml") && parts[1] === "value") { - variableId = parts[2]; - isYaml = parts[0] === "yaml"; - } else if (parts.length >= 2 && (parts[0] === "json" || parts[0] === "yaml") && parts[1] === "value") { - variableId = `${parts[0]}Value${index2}`; - wasDefaultId = true; - isYaml = parts[0] === "yaml"; - } else { - return ""; - } - const scriptElement = sanitizedScriptTag(content, { - id: `${pluginName$b}-${index2}`, - class: className$b, - "data-variable-id": variableId, - "data-was-default-id": wasDefaultId.toString(), - "data-format": isYaml ? "yaml" : "json" - }); - return scriptElement.outerHTML; - }, - hydrateSpecs: (renderer, errorHandler) => { - var _a; - const flagged = []; - const containers = renderer.element.querySelectorAll(`.${className$b}`); - for (const [index2, container] of Array.from(containers).entries()) { - try { - const variableId = container.getAttribute("data-variable-id"); - const wasDefaultId = container.getAttribute("data-was-default-id") === "true"; - if (!variableId) { - errorHandler(new Error("No variable ID found"), pluginName$b, index2, "parse", container); - continue; - } - const spec = { variableId, wasDefaultId }; - const flaggableSpec = inspectValueSpec(spec); - const f = { - approvedSpec: null, - pluginName: pluginName$b, - containerId: container.id - }; - if (flaggableSpec.hasFlags) { - f.blockedSpec = flaggableSpec.spec; - f.reason = ((_a = flaggableSpec.reasons) == null ? void 0 : _a.join(", ")) || "Unknown reason"; - } else { - f.approvedSpec = flaggableSpec.spec; - } - flagged.push(f); - } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); - } - } - return flagged; - }, - hydrateComponent: async (renderer, errorHandler, specs) => { - var _a; - const { signalBus } = renderer; - const valueInstances = []; - for (let index2 = 0; index2 < specs.length; index2++) { - const specReview = specs[index2]; - if (!specReview.approvedSpec) { - continue; - } - const container = renderer.element.querySelector(`#${specReview.containerId}`); - if (!container) { - errorHandler(new Error("Container not found"), pluginName$b, index2, "init", null); - continue; - } - try { - const content = (_a = container.textContent) == null ? void 0 : _a.trim(); - if (!content) { - errorHandler(new Error("No value content found"), pluginName$b, index2, "parse", container); - continue; - } - const spec = specReview.approvedSpec; - const format = container.getAttribute("data-format") || "json"; - let data; - try { - let parsed; - if (format === "yaml") { - parsed = yaml__namespace.load(content); - } else { - parsed = JSON.parse(content); - } - if (Array.isArray(parsed)) { - data = parsed; - } else { - data = [parsed]; - } - } catch (parseError) { - errorHandler( - new Error(`Invalid ${format.toUpperCase()}: ${parseError instanceof Error ? parseError.message : String(parseError)}`), - pluginName$b, - index2, - "parse", - container - ); - continue; - } - const valueInstance = { - id: `${pluginName$b}-${index2}`, - spec, - data - }; - valueInstances.push(valueInstance); - const comment = sanitizeHtmlComment(`${format.toUpperCase()} value loaded: ${data.length} rows for variable '${spec.variableId}'`); - container.insertAdjacentHTML("beforebegin", comment); - } catch (e) { - errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName$b, index2, "parse", container); - } - } - const instances = valueInstances.map((valueInstance) => { - const { spec, data } = valueInstance; - const initialSignals = [{ - name: spec.variableId, - value: data, - priority: 1, - isData: true - }]; - return { - ...valueInstance, - initialSignals, - beginListening() { - const batch = { - [spec.variableId]: { - value: data, - isData: true - } - }; - signalBus.broadcast(valueInstance.id, batch); - }, - getCurrentSignalValue: () => { - return data; - }, - destroy: () => { - } - }; - }); - return instances; - } - }; function isValidGoogleFontsUrl(url) { try { const parsed = new URL(url); @@ -2283,11 +2113,12 @@ ${reconstitutedRules.join("\n\n")} container, signals: {}, tokens, - renderingDiagram: null, lastRenderedDiagram: null }; mermaidInstances.push(mermaidInstance); - await renderRawDiagram(mermaidInstance, spec.diagramText, errorHandler, pluginName$8, index2); + if (spec.diagramText && typeof spec.diagramText === "string") { + await renderRawDiagram(mermaidInstance.id, mermaidInstance.container, spec.diagramText, errorHandler, pluginName$8, index2); + } } const instances = mermaidInstances.map((mermaidInstance, index2) => { const { spec, signals, tokens } = mermaidInstance; @@ -2334,14 +2165,18 @@ ${reconstitutedRules.join("\n\n")} }); } } - await renderRawDiagram(mermaidInstance, diagramText, errorHandler, pluginName$8, index2); + if (diagramText && mermaidInstance.lastRenderedDiagram !== diagramText) { + await renderRawDiagram(mermaidInstance.id, mermaidInstance.container, diagramText, errorHandler, pluginName$8, index2); + mermaidInstance.lastRenderedDiagram = diagramText; + } } else { mermaidInstance.container.innerHTML = '
No data available to render diagram
'; } } else if (variableId && batch[variableId]) { const value = batch[variableId].value; if (typeof value === "string" && value.trim().length > 0) { - await renderRawDiagram(mermaidInstance, value, errorHandler, pluginName$8, index2); + await renderRawDiagram(mermaidInstance.id, mermaidInstance.container, value, errorHandler, pluginName$8, index2); + mermaidInstance.lastRenderedDiagram = value; } else { mermaidInstance.container.innerHTML = '
No diagram to display
'; } @@ -2356,18 +2191,10 @@ ${reconstitutedRules.join("\n\n")} const lines = diagramText.split("\n"); return lines.length > 1 && lines[1].trim().length > 0; } - async function renderRawDiagram(mermaidInstance, diagramText, errorHandler, pluginName2, index2) { - if (!diagramText || typeof diagramText !== "string" || diagramText.trim().length === 0) { - return; - } - if (mermaidInstance.renderingDiagram === diagramText) { - return; - } - mermaidInstance.renderingDiagram = diagramText; + async function renderRawDiagram(id, container, diagramText, errorHandler, pluginName2, index2) { if (typeof mermaid === "undefined") { await loadMermaidFromCDN(); } - const { id, container } = mermaidInstance; if (typeof mermaid === "undefined") { container.innerHTML = '
Mermaid library not loaded dynamically
'; return; @@ -2379,8 +2206,6 @@ ${reconstitutedRules.join("\n\n")} try { const { svg } = await mermaid.render(id, diagramText); container.innerHTML = svg; - mermaidInstance.lastRenderedDiagram = diagramText; - mermaidInstance.renderingDiagram = null; } catch (error) { container.innerHTML = `
Failed to render diagram ${id}
${diagramText}
`; errorHandler(error instanceof Error ? error : new Error(String(error)), pluginName2, index2, "render", container); @@ -3623,9 +3448,7 @@ ${reconstitutedRules.join("\n\n")} this.broadcastingStack.pop(); for (const signalName in batch) { const signalDep = this.signalDeps[signalName]; - if (signalDep) { - signalDep.value = batch[signalName].value; - } + signalDep.value = batch[signalName].value; } if (this.broadcastingStack.length === 0) { for (const peer of this.peers) { @@ -3667,14 +3490,14 @@ ${reconstitutedRules.join("\n\n")} async beginListening() { this.active = true; this.log("beginListening", "begin initial batch", this.signalDeps); - const initialBatch = {}; - for (const signalName in this.signalDeps) { - const signalDep = this.signalDeps[signalName]; - const { value, isData } = signalDep; - initialBatch[signalName] = { value, isData }; - } for (const peer of this.peers) { - peer.receiveBatch && peer.receiveBatch(initialBatch, "initial"); + const batch = {}; + for (const signalName in this.signalDeps) { + const signalDep = this.signalDeps[signalName]; + const { value, isData } = signalDep; + batch[signalName] = { value, isData }; + } + peer.receiveBatch && peer.receiveBatch(batch, "initial"); } for (const peer of this.peers) { peer.broadcastComplete && await peer.broadcastComplete(); @@ -3792,24 +3615,17 @@ ${reconstitutedRules.join("\n\n")} receiveBatch: async (batch, from) => { signalBus.log(vegaInstance.id, "received batch", batch, from); return new Promise((resolve) => { - if (vegaInstance.isListening) { - view.runAfter(async () => { - if (receiveBatch(batch, signalBus, vegaInstance)) { - signalBus.log(vegaInstance.id, "running after _pulse, changes from", from); - view.resize(); - vegaInstance.needToRun = true; - } else { - signalBus.log(vegaInstance.id, "no changes"); - } - signalBus.log(vegaInstance.id, "running view after _pulse finished"); - resolve(); - }); - } else { + view.runAfter(async () => { if (receiveBatch(batch, signalBus, vegaInstance)) { + signalBus.log(vegaInstance.id, "running after _pulse, changes from", from); + view.resize(); vegaInstance.needToRun = true; + } else { + signalBus.log(vegaInstance.id, "no changes"); } + signalBus.log(vegaInstance.id, "running view after _pulse finished"); resolve(); - } + }); }); }, broadcastComplete: async () => { @@ -3856,11 +3672,6 @@ ${reconstitutedRules.join("\n\n")} } } } - vegaInstance.isListening = true; - if (vegaInstance.needToRun) { - view.runAsync(); - vegaInstance.needToRun = false; - } }, getCurrentSignalValue: (signalName) => { var _a; @@ -4103,7 +3914,6 @@ ${reconstitutedRules.join("\n\n")} registerMarkdownPlugin(commentPlugin); registerMarkdownPlugin(cssPlugin); registerMarkdownPlugin(csvPlugin); - registerMarkdownPlugin(valuePlugin); registerMarkdownPlugin(dsvPlugin); registerMarkdownPlugin(googleFontsPlugin); registerMarkdownPlugin(dropdownPlugin); @@ -4235,11 +4045,8 @@ ${reconstitutedRules.join("\n\n")} } } reset() { - const { logLevel, logWatchIds } = this.signalBus; this.signalBus.deactivate(); this.signalBus = new SignalBus(defaultCommonOptions.dataSignalPrefix); - this.signalBus.logLevel = logLevel; - this.signalBus.logWatchIds = logWatchIds; for (const pluginName2 of Object.keys(this.instances)) { const instances = this.instances[pluginName2]; for (const instance of instances) { diff --git a/docs/schema/idoc_v1.d.ts b/docs/schema/idoc_v1.d.ts index 7a83f9ab..3954fc69 100644 --- a/docs/schema/idoc_v1.d.ts +++ b/docs/schema/idoc_v1.d.ts @@ -100,46 +100,76 @@ type DataLoader = DataSource | DataLoaderBySpec; * Licensed under the MIT License. */ /** - * CSV data in markdown fence: ```csv variableId + * Inline Data Elements + * These elements are defined using markdown fence blocks to embed data directly in the document. + */ +/** + * CSV Element + * Embed CSV data inline using markdown fence block: ```csv variableId + * Content should be CSV rows (comma-separated values). + * All values are stored as strings. */ interface CsvElement extends ElementBase { type: 'csv'; variableId: VariableID; - content: string | string[] | string[][]; + /** CSV content as string */ + content: string; } /** - * TSV data in markdown fence: ```tsv variableId + * TSV Element + * Embed TSV data inline using markdown fence block: ```tsv variableId + * Content should be TSV rows (tab-separated values). + * All values are stored as strings. */ interface TsvElement extends ElementBase { type: 'tsv'; variableId: VariableID; - content: string | string[] | string[][]; + /** TSV content as string */ + content: string; } /** - * Custom delimiter-separated data in markdown fence: ```dsv delimiter:| variableId + * DSV Element + * Embed delimiter-separated data inline using markdown fence block: ```dsv delimiter:| variableId + * Content should be delimiter-separated rows. + * All values are stored as strings. */ interface DsvElement extends ElementBase { type: 'dsv'; variableId: VariableID; + /** Custom delimiter character (e.g., '|', ';', etc.) */ delimiter: string; - content: string | string[] | string[][]; + /** DSV content as string */ + content: string; } /** - * JSON data with type preservation in markdown fence: ```json value variableId + * Value Element (JSON) + * Embed JSON data inline using markdown fence block: ```json value variableId + * Content should be a JSON array or object. + * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. + * Use this for structured data with type preservation. */ interface JsonValueElement extends ElementBase { type: 'json-value'; variableId: VariableID; + /** JSON content as array or object */ content: object | object[]; } /** - * YAML data with type preservation in markdown fence: ```yaml value variableId + * Value Element (YAML) + * Embed YAML data inline using markdown fence block: ```yaml value variableId + * Content should be YAML array or object. + * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. + * Use this for structured data with type preservation. */ interface YamlValueElement extends ElementBase { type: 'yaml-value'; variableId: VariableID; - content: object | object[] | string | string[]; + /** YAML content as array or object */ + content: object | object[]; } +/** + * Union type for all inline data elements + */ type InlineDataElement = CsvElement | TsvElement | DsvElement | JsonValueElement | YamlValueElement; /** * Interactive Elements @@ -335,7 +365,7 @@ interface InteractiveDocument { */ type MarkdownElement = string; /** Union type for all possible elements */ -type PageElement = MarkdownElement | InteractiveElement | InlineDataElement; +type PageElement = MarkdownElement | InteractiveElement; interface PageStyle { /** CSS styles, either a string, or array of strings which will be concatenated. The array is for developer ergonomics for authoring and merging. */ css: string | string[]; diff --git a/docs/schema/idoc_v1.json b/docs/schema/idoc_v1.json index 9897fa6c..7a00ba2c 100644 --- a/docs/schema/idoc_v1.json +++ b/docs/schema/idoc_v1.json @@ -1085,29 +1085,11 @@ }, "CsvElement": { "additionalProperties": false, - "description": "CSV data in markdown fence: ```csv variableId", + "description": "CSV Element Embed CSV data inline using markdown fence block: ```csv variableId Content should be CSV rows (comma-separated values). All values are stored as strings.", "properties": { "content": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "items": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": "array" - } - ] + "description": "CSV content as string", + "type": "string" }, "type": { "const": "csv", @@ -1657,31 +1639,14 @@ }, "DsvElement": { "additionalProperties": false, - "description": "Custom delimiter-separated data in markdown fence: ```dsv delimiter:| variableId", + "description": "DSV Element Embed delimiter-separated data inline using markdown fence block: ```dsv delimiter:| variableId Content should be delimiter-separated rows. All values are stored as strings.", "properties": { "content": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "items": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": "array" - } - ] + "description": "DSV content as string", + "type": "string" }, "delimiter": { + "description": "Custom delimiter character (e.g., '|', ';', etc.)", "type": "string" }, "type": { @@ -3246,7 +3211,8 @@ { "$ref": "#/definitions/YamlValueElement" } - ] + ], + "description": "Union type for all inline data elements" }, "InteractiveDocumentWithSchema": { "additionalProperties": false, @@ -3474,7 +3440,7 @@ }, "JsonValueElement": { "additionalProperties": false, - "description": "JSON data with type preservation in markdown fence: ```json value variableId", + "description": "Value Element (JSON) Embed JSON data inline using markdown fence block: ```json value variableId Content should be a JSON array or object. Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. Use this for structured data with type preservation.", "properties": { "content": { "anyOf": [ @@ -3487,7 +3453,8 @@ }, "type": "array" } - ] + ], + "description": "JSON content as array or object" }, "type": { "const": "json-value", @@ -4278,9 +4245,6 @@ }, { "$ref": "#/definitions/InteractiveElement" - }, - { - "$ref": "#/definitions/InlineDataElement" } ], "description": "Union type for all possible elements" @@ -6038,29 +6002,11 @@ }, "TsvElement": { "additionalProperties": false, - "description": "TSV data in markdown fence: ```tsv variableId", + "description": "TSV Element Embed TSV data inline using markdown fence block: ```tsv variableId Content should be TSV rows (tab-separated values). All values are stored as strings.", "properties": { "content": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "items": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": "array" - } - ] + "description": "TSV content as string", + "type": "string" }, "type": { "const": "tsv", @@ -6546,7 +6492,7 @@ }, "YamlValueElement": { "additionalProperties": false, - "description": "YAML data with type preservation in markdown fence: ```yaml value variableId", + "description": "Value Element (YAML) Embed YAML data inline using markdown fence block: ```yaml value variableId Content should be YAML array or object. Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. Use this for structured data with type preservation.", "properties": { "content": { "anyOf": [ @@ -6558,17 +6504,9 @@ "type": "object" }, "type": "array" - }, - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" } - ] + ], + "description": "YAML content as array or object" }, "type": { "const": "yaml-value", From c661a6eb69440dbac84274982e3e6690ac970a1e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 23:13:26 +0000 Subject: [PATCH 28/47] Remove built schema files and interactive.ts from PR Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- docs/schema/idoc_v1.d.ts | 80 +---- docs/schema/idoc_v1.json | 453 ++++++++----------------- packages/schema-doc/src/interactive.ts | 2 - 3 files changed, 150 insertions(+), 385 deletions(-) diff --git a/docs/schema/idoc_v1.d.ts b/docs/schema/idoc_v1.d.ts index 3954fc69..7fb3df38 100644 --- a/docs/schema/idoc_v1.d.ts +++ b/docs/schema/idoc_v1.d.ts @@ -95,82 +95,6 @@ interface DataLoaderBySpec { spec: object; } type DataLoader = DataSource | DataLoaderBySpec; -/** - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ -/** - * Inline Data Elements - * These elements are defined using markdown fence blocks to embed data directly in the document. - */ -/** - * CSV Element - * Embed CSV data inline using markdown fence block: ```csv variableId - * Content should be CSV rows (comma-separated values). - * All values are stored as strings. - */ -interface CsvElement extends ElementBase { - type: 'csv'; - variableId: VariableID; - /** CSV content as string */ - content: string; -} -/** - * TSV Element - * Embed TSV data inline using markdown fence block: ```tsv variableId - * Content should be TSV rows (tab-separated values). - * All values are stored as strings. - */ -interface TsvElement extends ElementBase { - type: 'tsv'; - variableId: VariableID; - /** TSV content as string */ - content: string; -} -/** - * DSV Element - * Embed delimiter-separated data inline using markdown fence block: ```dsv delimiter:| variableId - * Content should be delimiter-separated rows. - * All values are stored as strings. - */ -interface DsvElement extends ElementBase { - type: 'dsv'; - variableId: VariableID; - /** Custom delimiter character (e.g., '|', ';', etc.) */ - delimiter: string; - /** DSV content as string */ - content: string; -} -/** - * Value Element (JSON) - * Embed JSON data inline using markdown fence block: ```json value variableId - * Content should be a JSON array or object. - * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. - * Use this for structured data with type preservation. - */ -interface JsonValueElement extends ElementBase { - type: 'json-value'; - variableId: VariableID; - /** JSON content as array or object */ - content: object | object[]; -} -/** - * Value Element (YAML) - * Embed YAML data inline using markdown fence block: ```yaml value variableId - * Content should be YAML array or object. - * Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. - * Use this for structured data with type preservation. - */ -interface YamlValueElement extends ElementBase { - type: 'yaml-value'; - variableId: VariableID; - /** YAML content as array or object */ - content: object | object[]; -} -/** - * Union type for all inline data elements - */ -type InlineDataElement = CsvElement | TsvElement | DsvElement | JsonValueElement | YamlValueElement; /** * Interactive Elements */ @@ -332,7 +256,7 @@ interface TabulatorElementProps extends OptionalVariableControl { /** * Union type for all possible interactive elements */ -type InteractiveElement = ChartElement | CheckboxElement | DropdownElement | ImageElement | MermaidElement | NumberElement | PresetsElement | SliderElement | TabulatorElement | TextboxElement | TreebarkElement | InlineDataElement; +type InteractiveElement = ChartElement | CheckboxElement | DropdownElement | ImageElement | MermaidElement | NumberElement | PresetsElement | SliderElement | TabulatorElement | TextboxElement | TreebarkElement; interface ElementGroup { groupId: string; elements: PageElement[]; @@ -392,4 +316,4 @@ interface GoogleFontsSpec { type InteractiveDocumentWithSchema = InteractiveDocument & { $schema?: string; }; -export type { Calculation, ChartElement, CheckboxElement, CheckboxProps, CsvElement, DataFrameCalculation, DataLoader, DataLoaderBySpec, DataSource, DataSourceBase, DataSourceBaseFormat, DataSourceByDynamicURL, DataSourceByFile, DataSourceInline, DropdownElement, DropdownElementProps, DsvElement, DynamicDropdownOptions, ElementBase, ElementGroup, GoogleFontsSpec, ImageElement, ImageElementProps, InlineDataElement, InteractiveDocument, InteractiveDocumentWithSchema, InteractiveElement, JsonValueElement, MarkdownElement, MermaidElement, MermaidElementProps, MermaidTemplate, NumberElement, NumberElementProps, OptionalVariableControl, PageElement, PageStyle, Preset, PresetsElement, PresetsElementProps, ReturnType, ScalarCalculation, SliderElement, SliderElementProps, TabulatorElement, TabulatorElementProps, TemplatedUrl, TextboxElement, TextboxElementProps, TreebarkElement, TreebarkElementProps, TsvElement, Variable, VariableControl, VariableID, VariableType, VariableValue, VariableValueArray, VariableValuePrimitive, Vega_or_VegaLite_spec, YamlValueElement }; \ No newline at end of file +export type { Calculation, ChartElement, CheckboxElement, CheckboxProps, DataFrameCalculation, DataLoader, DataLoaderBySpec, DataSource, DataSourceBase, DataSourceBaseFormat, DataSourceByDynamicURL, DataSourceByFile, DataSourceInline, DropdownElement, DropdownElementProps, DynamicDropdownOptions, ElementBase, ElementGroup, GoogleFontsSpec, ImageElement, ImageElementProps, InteractiveDocument, InteractiveDocumentWithSchema, InteractiveElement, MarkdownElement, MermaidElement, MermaidElementProps, MermaidTemplate, NumberElement, NumberElementProps, OptionalVariableControl, PageElement, PageStyle, Preset, PresetsElement, PresetsElementProps, ReturnType, ScalarCalculation, SliderElement, SliderElementProps, TabulatorElement, TabulatorElementProps, TemplatedUrl, TextboxElement, TextboxElementProps, TreebarkElement, TreebarkElementProps, Variable, VariableControl, VariableID, VariableType, VariableValue, VariableValueArray, VariableValuePrimitive, Vega_or_VegaLite_spec }; \ No newline at end of file diff --git a/docs/schema/idoc_v1.json b/docs/schema/idoc_v1.json index 7a00ba2c..1132bcae 100644 --- a/docs/schema/idoc_v1.json +++ b/docs/schema/idoc_v1.json @@ -8,13 +8,13 @@ "a": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -29,13 +29,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -195,13 +195,13 @@ "article": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -216,13 +216,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -260,7 +260,7 @@ "AttributeValue": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/ConditionalValue" @@ -340,19 +340,22 @@ ], "type": "object" }, + "BindPath": { + "type": "string" + }, "BlockquoteTag": { "additionalProperties": false, "properties": { "blockquote": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -367,13 +370,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -417,13 +420,13 @@ "br": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -438,7 +441,7 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "class": { "$ref": "#/definitions/AttributeValue" @@ -529,13 +532,13 @@ "code": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -550,13 +553,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -614,13 +617,13 @@ "$comment": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -635,13 +638,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -730,7 +733,7 @@ } ] }, - "ConditionalBase<(string|TemplateObject)>": { + "ConditionalBase<(InterpolatedString|TemplateObject)>": { "additionalProperties": false, "properties": { "$<": { @@ -749,12 +752,12 @@ "type": "number" }, "$check": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$else": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -780,7 +783,7 @@ "$then": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -813,7 +816,7 @@ "type": "number" }, "$check": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$else": { "$ref": "#/definitions/CSSProperties" @@ -844,7 +847,7 @@ ], "type": "object" }, - "ConditionalBase": { + "ConditionalBase": { "additionalProperties": false, "properties": { "$<": { @@ -863,10 +866,10 @@ "type": "number" }, "$check": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$else": { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, "$in": { "items": { @@ -885,7 +888,7 @@ "type": "boolean" }, "$then": { - "type": "string" + "$ref": "#/definitions/InterpolatedString" } }, "required": [ @@ -895,10 +898,10 @@ "type": "object" }, "ConditionalValue": { - "$ref": "#/definitions/ConditionalBase%3Cstring%3E" + "$ref": "#/definitions/ConditionalBase%3CInterpolatedString%3E" }, "ConditionalValueOrTemplate": { - "$ref": "#/definitions/ConditionalBase%3C(string%7CTemplateObject)%3E" + "$ref": "#/definitions/ConditionalBase%3C(InterpolatedString%7CTemplateObject)%3E" }, "ContourTransform": { "anyOf": [ @@ -1083,29 +1086,6 @@ ], "type": "object" }, - "CsvElement": { - "additionalProperties": false, - "description": "CSV Element Embed CSV data inline using markdown fence block: ```csv variableId Content should be CSV rows (comma-separated values). All values are stored as strings.", - "properties": { - "content": { - "description": "CSV content as string", - "type": "string" - }, - "type": { - "const": "csv", - "type": "string" - }, - "variableId": { - "$ref": "#/definitions/VariableID" - } - }, - "required": [ - "type", - "variableId", - "content" - ], - "type": "object" - }, "DataFrameCalculation": { "additionalProperties": false, "description": "DataFrame calculation for object arrays. Not for primitive/scalar values.", @@ -1501,13 +1481,13 @@ "div": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -1522,13 +1502,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -1637,34 +1617,6 @@ ], "type": "object" }, - "DsvElement": { - "additionalProperties": false, - "description": "DSV Element Embed delimiter-separated data inline using markdown fence block: ```dsv delimiter:| variableId Content should be delimiter-separated rows. All values are stored as strings.", - "properties": { - "content": { - "description": "DSV content as string", - "type": "string" - }, - "delimiter": { - "description": "Custom delimiter character (e.g., '|', ';', etc.)", - "type": "string" - }, - "type": { - "const": "dsv", - "type": "string" - }, - "variableId": { - "$ref": "#/definitions/VariableID" - } - }, - "required": [ - "type", - "variableId", - "delimiter", - "content" - ], - "type": "object" - }, "DynamicDropdownOptions": { "additionalProperties": false, "description": "Dropdown use for selecting from a list of options", @@ -1709,13 +1661,13 @@ "em": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -1730,13 +1682,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -1930,13 +1882,13 @@ "footer": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -1951,13 +1903,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2448,13 +2400,13 @@ "h1": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2469,13 +2421,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2516,13 +2468,13 @@ "h2": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2537,13 +2489,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2584,13 +2536,13 @@ "h3": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2605,13 +2557,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2652,13 +2604,13 @@ "h4": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2673,13 +2625,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2720,13 +2672,13 @@ "h5": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2741,13 +2693,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2788,13 +2740,13 @@ "h6": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2809,13 +2761,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2856,13 +2808,13 @@ "header": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2877,13 +2829,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2977,13 +2929,13 @@ "hr": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -2998,7 +2950,7 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "class": { "$ref": "#/definitions/AttributeValue" @@ -3088,13 +3040,13 @@ "img": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -3109,7 +3061,7 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "alt": { "type": "string" @@ -3194,26 +3146,6 @@ ], "type": "object" }, - "InlineDataElement": { - "anyOf": [ - { - "$ref": "#/definitions/CsvElement" - }, - { - "$ref": "#/definitions/TsvElement" - }, - { - "$ref": "#/definitions/DsvElement" - }, - { - "$ref": "#/definitions/JsonValueElement" - }, - { - "$ref": "#/definitions/YamlValueElement" - } - ], - "description": "Union type for all inline data elements" - }, "InteractiveDocumentWithSchema": { "additionalProperties": false, "description": "JSON Schema version with $schema property for validation", @@ -3307,13 +3239,13 @@ }, { "$ref": "#/definitions/TreebarkElement" - }, - { - "$ref": "#/definitions/InlineDataElement" } ], "description": "Union type for all possible interactive elements" }, + "InterpolatedString": { + "type": "string" + }, "IsocontourTransform": { "additionalProperties": false, "properties": { @@ -3438,39 +3370,6 @@ ], "type": "object" }, - "JsonValueElement": { - "additionalProperties": false, - "description": "Value Element (JSON) Embed JSON data inline using markdown fence block: ```json value variableId Content should be a JSON array or object. Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. Use this for structured data with type preservation.", - "properties": { - "content": { - "anyOf": [ - { - "type": "object" - }, - { - "items": { - "type": "object" - }, - "type": "array" - } - ], - "description": "JSON content as array or object" - }, - "type": { - "const": "json-value", - "type": "string" - }, - "variableId": { - "$ref": "#/definitions/VariableID" - } - }, - "required": [ - "type", - "variableId", - "content" - ], - "type": "object" - }, "KDE2DTransform": { "additionalProperties": false, "properties": { @@ -3701,13 +3600,13 @@ "li": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -3722,13 +3621,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -3902,13 +3801,13 @@ "main": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -3923,13 +3822,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -4077,13 +3976,13 @@ "ol": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -4098,13 +3997,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -4145,13 +4044,13 @@ "p": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -4166,13 +4065,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -4383,13 +4282,13 @@ "pre": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -4404,13 +4303,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -4776,13 +4675,13 @@ "section": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -4797,13 +4696,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -4915,13 +4814,13 @@ "span": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -4936,13 +4835,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5043,13 +4942,13 @@ "strong": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5064,13 +4963,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5121,13 +5020,13 @@ "table": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5142,13 +5041,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5225,13 +5124,13 @@ "tbody": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5246,13 +5145,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5293,13 +5192,13 @@ "td": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5314,13 +5213,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5367,7 +5266,7 @@ "TemplateElement": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5422,13 +5321,13 @@ "th": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5443,13 +5342,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5499,13 +5398,13 @@ "thead": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5520,13 +5419,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5628,13 +5527,13 @@ "tr": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -5649,13 +5548,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -6000,42 +5899,19 @@ ], "type": "object" }, - "TsvElement": { - "additionalProperties": false, - "description": "TSV Element Embed TSV data inline using markdown fence block: ```tsv variableId Content should be TSV rows (tab-separated values). All values are stored as strings.", - "properties": { - "content": { - "description": "TSV content as string", - "type": "string" - }, - "type": { - "const": "tsv", - "type": "string" - }, - "variableId": { - "$ref": "#/definitions/VariableID" - } - }, - "required": [ - "type", - "variableId", - "content" - ], - "type": "object" - }, "UlTag": { "additionalProperties": false, "properties": { "ul": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -6050,13 +5926,13 @@ }, "properties": { "$bind": { - "type": "string" + "$ref": "#/definitions/BindPath" }, "$children": { "items": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/InterpolatedString" }, { "$ref": "#/definitions/TemplateObject" @@ -6489,39 +6365,6 @@ "type" ], "type": "object" - }, - "YamlValueElement": { - "additionalProperties": false, - "description": "Value Element (YAML) Embed YAML data inline using markdown fence block: ```yaml value variableId Content should be YAML array or object. Preserves data types: numbers remain numbers, booleans remain booleans, nested objects are preserved. Use this for structured data with type preservation.", - "properties": { - "content": { - "anyOf": [ - { - "type": "object" - }, - { - "items": { - "type": "object" - }, - "type": "array" - } - ], - "description": "YAML content as array or object" - }, - "type": { - "const": "yaml-value", - "type": "string" - }, - "variableId": { - "$ref": "#/definitions/VariableID" - } - }, - "required": [ - "type", - "variableId", - "content" - ], - "type": "object" } } } \ No newline at end of file diff --git a/packages/schema-doc/src/interactive.ts b/packages/schema-doc/src/interactive.ts index 374feafc..70753307 100644 --- a/packages/schema-doc/src/interactive.ts +++ b/packages/schema-doc/src/interactive.ts @@ -4,7 +4,6 @@ */ import { VariableID, VariableControl, ElementBase, TemplatedUrl, OptionalVariableControl } from './common.js'; import { TemplateElement } from 'treebark'; -import { InlineDataElement } from './inline-data.js'; /** * Interactive Elements @@ -251,5 +250,4 @@ export type InteractiveElement = | TabulatorElement | TextboxElement | TreebarkElement - | InlineDataElement ; From 7724b463d47767e02452431c57dfecf1d60b0ed4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 23:17:20 +0000 Subject: [PATCH 29/47] Remove docs/schema/idoc_v1.json from PR Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- docs/schema/idoc_v1.json | 290 +++++++++++++++++++-------------------- 1 file changed, 142 insertions(+), 148 deletions(-) diff --git a/docs/schema/idoc_v1.json b/docs/schema/idoc_v1.json index 1132bcae..c93a9361 100644 --- a/docs/schema/idoc_v1.json +++ b/docs/schema/idoc_v1.json @@ -8,13 +8,13 @@ "a": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -29,13 +29,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -195,13 +195,13 @@ "article": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -216,13 +216,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -260,7 +260,7 @@ "AttributeValue": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/ConditionalValue" @@ -340,22 +340,19 @@ ], "type": "object" }, - "BindPath": { - "type": "string" - }, "BlockquoteTag": { "additionalProperties": false, "properties": { "blockquote": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -370,13 +367,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -420,13 +417,13 @@ "br": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -441,7 +438,7 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "class": { "$ref": "#/definitions/AttributeValue" @@ -532,13 +529,13 @@ "code": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -553,13 +550,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -617,13 +614,13 @@ "$comment": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -638,13 +635,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -733,7 +730,7 @@ } ] }, - "ConditionalBase<(InterpolatedString|TemplateObject)>": { + "ConditionalBase<(string|TemplateObject)>": { "additionalProperties": false, "properties": { "$<": { @@ -752,12 +749,12 @@ "type": "number" }, "$check": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$else": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -783,7 +780,7 @@ "$then": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -816,7 +813,7 @@ "type": "number" }, "$check": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$else": { "$ref": "#/definitions/CSSProperties" @@ -847,7 +844,7 @@ ], "type": "object" }, - "ConditionalBase": { + "ConditionalBase": { "additionalProperties": false, "properties": { "$<": { @@ -866,10 +863,10 @@ "type": "number" }, "$check": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$else": { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, "$in": { "items": { @@ -888,7 +885,7 @@ "type": "boolean" }, "$then": { - "$ref": "#/definitions/InterpolatedString" + "type": "string" } }, "required": [ @@ -898,10 +895,10 @@ "type": "object" }, "ConditionalValue": { - "$ref": "#/definitions/ConditionalBase%3CInterpolatedString%3E" + "$ref": "#/definitions/ConditionalBase%3Cstring%3E" }, "ConditionalValueOrTemplate": { - "$ref": "#/definitions/ConditionalBase%3C(InterpolatedString%7CTemplateObject)%3E" + "$ref": "#/definitions/ConditionalBase%3C(string%7CTemplateObject)%3E" }, "ContourTransform": { "anyOf": [ @@ -1481,13 +1478,13 @@ "div": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -1502,13 +1499,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -1661,13 +1658,13 @@ "em": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -1682,13 +1679,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -1882,13 +1879,13 @@ "footer": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -1903,13 +1900,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2400,13 +2397,13 @@ "h1": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2421,13 +2418,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2468,13 +2465,13 @@ "h2": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2489,13 +2486,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2536,13 +2533,13 @@ "h3": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2557,13 +2554,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2604,13 +2601,13 @@ "h4": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2625,13 +2622,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2672,13 +2669,13 @@ "h5": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2693,13 +2690,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2740,13 +2737,13 @@ "h6": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2761,13 +2758,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2808,13 +2805,13 @@ "header": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2829,13 +2826,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2929,13 +2926,13 @@ "hr": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -2950,7 +2947,7 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "class": { "$ref": "#/definitions/AttributeValue" @@ -3040,13 +3037,13 @@ "img": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -3061,7 +3058,7 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "alt": { "type": "string" @@ -3243,9 +3240,6 @@ ], "description": "Union type for all possible interactive elements" }, - "InterpolatedString": { - "type": "string" - }, "IsocontourTransform": { "additionalProperties": false, "properties": { @@ -3600,13 +3594,13 @@ "li": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -3621,13 +3615,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -3801,13 +3795,13 @@ "main": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -3822,13 +3816,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -3976,13 +3970,13 @@ "ol": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -3997,13 +3991,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -4044,13 +4038,13 @@ "p": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -4065,13 +4059,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -4282,13 +4276,13 @@ "pre": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -4303,13 +4297,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -4675,13 +4669,13 @@ "section": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -4696,13 +4690,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -4814,13 +4808,13 @@ "span": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -4835,13 +4829,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -4942,13 +4936,13 @@ "strong": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -4963,13 +4957,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5020,13 +5014,13 @@ "table": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5041,13 +5035,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5124,13 +5118,13 @@ "tbody": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5145,13 +5139,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5192,13 +5186,13 @@ "td": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5213,13 +5207,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5266,7 +5260,7 @@ "TemplateElement": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5321,13 +5315,13 @@ "th": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5342,13 +5336,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5398,13 +5392,13 @@ "thead": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5419,13 +5413,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5527,13 +5521,13 @@ "tr": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5548,13 +5542,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5905,13 +5899,13 @@ "ul": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" @@ -5926,13 +5920,13 @@ }, "properties": { "$bind": { - "$ref": "#/definitions/BindPath" + "type": "string" }, "$children": { "items": { "anyOf": [ { - "$ref": "#/definitions/InterpolatedString" + "type": "string" }, { "$ref": "#/definitions/TemplateObject" From cef5728ea0c1e9a1ca019ff2dd7eeea12451f4f5 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 16:21:35 -0800 Subject: [PATCH 30/47] Refactor DSV parsing functions to consolidate variable ID and delimiter parsing into a single utility function --- packages/markdown/src/plugins/dsv.ts | 101 +++++++++++++++------------ 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/packages/markdown/src/plugins/dsv.ts b/packages/markdown/src/plugins/dsv.ts index a12a1e8d..90f821a6 100644 --- a/packages/markdown/src/plugins/dsv.ts +++ b/packages/markdown/src/plugins/dsv.ts @@ -24,65 +24,75 @@ export interface DsvSpec { } /** - * Utility function to parse variable ID from fence info. - * Supports both "pluginName variableId" and "pluginName variableId:name" formats. - * @param info The fence info string (e.g., "csv myData" or "csv variableId:myData") + * Utility function to parse DSV fence info. + * Supports formats like: + * - "dsv products delimiter:|" + * - "dsv delimiter:| variableId:products" + * - "dsv delimiter: | variableId: products" + * @param info The fence info string * @param pluginName The plugin name (csv, tsv, dsv) * @param index The fence index for default naming - * @returns Object with variableId and wasDefaultId flag + * @returns Object with variableId, delimiter, and flags */ -export function parseVariableId(info: string, pluginName: string, index: number): { variableId: string; wasDefaultId: boolean } { +export function parseDsvInfo(info: string, pluginName: string, index: number): { + variableId: string; + delimiter: string; + wasDefaultId: boolean; + wasDefaultDelimiter: boolean; +} { const parts = info.trim().split(/\s+/); - // Check for explicit variableId: parameter - for (const part of parts) { + let variableId: string | null = null; + let delimiter: string | null = null; + + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + + // Parse variableId: parameter if (part.startsWith('variableId:')) { - return { - variableId: part.slice(11).trim(), // Remove 'variableId:' prefix and trim spaces - wasDefaultId: false - }; + const value = part.slice(11); + variableId = value || (i + 1 < parts.length ? parts[++i] : null); + } + // Parse delimiter: parameter + else if (part.startsWith('delimiter:')) { + const value = part.slice(10); + delimiter = value || (i + 1 < parts.length ? parts[++i] : null); + } + // Direct variableId format (not a parameter key) + else if (i > 0 && !variableId && part !== 'variableId:' && part !== 'delimiter:') { + variableId = part; } } - // Check for direct format (second parameter that's not a special parameter) - if (parts.length >= 2) { - const secondPart = parts[1]; - if (!secondPart.startsWith('delimiter:') && !secondPart.startsWith('variableId:')) { - return { - variableId: secondPart, - wasDefaultId: false - }; - } + // Handle special delimiter characters + if (delimiter) { + if (delimiter === '\\t') delimiter = '\t'; + if (delimiter === '\\n') delimiter = '\n'; + if (delimiter === '\\r') delimiter = '\r'; } - // Default variable ID - return { - variableId: `${pluginName}Data${index}`, - wasDefaultId: true + return { + variableId: variableId || `${pluginName}Data${index}`, + delimiter: delimiter || ',', + wasDefaultId: !variableId, + wasDefaultDelimiter: !delimiter }; } /** - * Utility function to parse delimiter from fence info. - * @param info The fence info string (e.g., "dsv delimiter:| variableId:myData") - * @returns Object with delimiter and wasDefaultDelimiter flag + * Utility function to parse variable ID from fence info. + * Used by CSV and TSV plugins. + * @param info The fence info string (e.g., "csv myData" or "csv variableId:myData") + * @param pluginName The plugin name (csv, tsv, dsv) + * @param index The fence index for default naming + * @returns Object with variableId and wasDefaultId flag */ -export function parseDelimiter(info: string): { delimiter: string; wasDefaultDelimiter: boolean } { - const parts = info.trim().split(/\s+/); - - for (const part of parts) { - if (part.startsWith('delimiter:')) { - let delimiter = part.slice(10).trim(); // Remove 'delimiter:' prefix and trim spaces - // Handle special cases - if (delimiter === '\\t') delimiter = '\t'; - if (delimiter === '\\n') delimiter = '\n'; - if (delimiter === '\\r') delimiter = '\r'; - return { delimiter, wasDefaultDelimiter: false }; - } - } - - // Default to comma - return { delimiter: ',', wasDefaultDelimiter: true }; +export function parseVariableId(info: string, pluginName: string, index: number): { variableId: string; wasDefaultId: boolean } { + const result = parseDsvInfo(info, pluginName, index); + return { + variableId: result.variableId, + wasDefaultId: result.wasDefaultId + }; } function inspectDsvSpec(spec: DsvSpec): RawFlaggableSpec { @@ -115,9 +125,8 @@ export const dsvPlugin: Plugin = { const content = token.content.trim(); const info = token.info.trim(); - // Use utility functions to parse delimiter and variable ID - const { delimiter, wasDefaultDelimiter } = parseDelimiter(info); - const { variableId, wasDefaultId } = parseVariableId(info, 'dsv', index); + // Parse both delimiter and variable ID in one pass + const { variableId, delimiter, wasDefaultId, wasDefaultDelimiter } = parseDsvInfo(info, 'dsv', index); return sanitizedHTML('pre', { id: `${pluginName}-${index}`, From ac9004173c34ee8af57f956ece4ef7b237d29a3c Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 17:33:25 -0800 Subject: [PATCH 31/47] Update ignoredSignals to replace 'origins' with 'value' --- packages/compiler/src/validate/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler/src/validate/common.ts b/packages/compiler/src/validate/common.ts index dceca5b8..c0ec5de8 100644 --- a/packages/compiler/src/validate/common.ts +++ b/packages/compiler/src/validate/common.ts @@ -5,7 +5,7 @@ import { validateTransforms } from './transforms.js'; const illegalChars = '/|\\\'"`,.;:~-=+?!@#$%^&*()[]{}<>'; -export const ignoredSignals = ['width', 'height', 'padding', 'autosize', 'background', 'style', 'parent', 'datum', 'item', 'event', 'cursor', 'origins']; +export const ignoredSignals = ['width', 'height', 'padding', 'autosize', 'background', 'style', 'parent', 'datum', 'item', 'event', 'cursor', 'value']; // Utility functions for property validation export function validateRequiredString(value: any, propertyName: string, elementType: string): string[] { From 6af707de99a237de67ce75d6c1f3d4173b7e5a38 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 17:34:09 -0800 Subject: [PATCH 32/47] Add parseFenceInfo function to extract metadata from fence info strings --- packages/markdown/src/plugins/config.ts | 125 ++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/packages/markdown/src/plugins/config.ts b/packages/markdown/src/plugins/config.ts index 6aeefcca..dd8e9371 100644 --- a/packages/markdown/src/plugins/config.ts +++ b/packages/markdown/src/plugins/config.ts @@ -9,6 +9,131 @@ import { getJsonScriptTag } from "./util.js"; import { SpecReview } from 'common'; import * as yaml from 'js-yaml'; +/** + * Parse fence info and extract all metadata. + * @param info The fence info string + * @returns Object with format, pluginName, params (including variableId), and wasDefaultId flag + */ +export function parseFenceInfo(info: string): { + format: 'json' | 'yaml'; + pluginName: string; + params: Map; + wasDefaultId: boolean; +} { + const parts = info.trim().split(/\s+/); + + // Determine format (json is default) + let format: 'json' | 'yaml' = 'json'; + let startIndex = 0; + + if (parts[0] === 'json' || parts[0] === 'yaml') { + format = parts[0]; + startIndex = 1; + } + + // The next part could be the plugin name OR a parameter + let pluginName = ''; + let pluginNameIndex = startIndex; + + if (startIndex < parts.length && !parts[startIndex].includes(':')) { + // It's a plugin name (doesn't have a colon) + pluginName = parts[startIndex]; + pluginNameIndex = startIndex + 1; + } + + const params = new Map(); + let variableId: string | null = null; + + // Parse remaining parts starting after plugin name (if found) + for (let i = pluginNameIndex; i < parts.length; i++) { + const part = parts[i]; + const colonIndex = part.indexOf(':'); + + if (colonIndex > 0) { + // Parameter with colon + const key = part.slice(0, colonIndex); + const value = part.slice(colonIndex + 1); + + if (value) { + // Format: "key:value" + params.set(key, value); + } else if (i + 1 < parts.length) { + // Format: "key: value" (value in next part) + params.set(key, parts[++i]); + } + } else if (!variableId) { + // First non-parameter value becomes variableId + variableId = part; + } + } + + // If variableId param exists, use it; otherwise use the direct value + const explicitVariableId = params.get('variableId'); + const finalVariableId = explicitVariableId || variableId; + const wasDefaultId = !finalVariableId; + + if (finalVariableId) { + params.set('variableId', finalVariableId); + } + + return { format, pluginName, params, wasDefaultId }; +} + +/* +//Tests for parseFenceInfo +const tests: [string, { format: 'json' | 'yaml'; pluginName: string; variableId: string | undefined; wasDefaultId: boolean }][] = [ + //Direct format cases + ["dsv products", { format: "json", pluginName: "dsv", variableId: "products", wasDefaultId: false }], + ["csv officeSupplies", { format: "json", pluginName: "csv", variableId: "officeSupplies", wasDefaultId: false }], + + //Explicit format cases + ["json inventory", { format: "json", pluginName: "inventory", variableId: undefined, wasDefaultId: true }], + ["yaml products", { format: "yaml", pluginName: "products", variableId: undefined, wasDefaultId: true }], + + //Explicit plugin name + ["json value inventory", { format: "json", pluginName: "value", variableId: "inventory", wasDefaultId: false }], + ["yaml value products", { format: "yaml", pluginName: "value", variableId: "products", wasDefaultId: false }], + + //Direct plugin without format + ["value inventory", { format: "json", pluginName: "value", variableId: "inventory", wasDefaultId: false }], + + //Explicit variableId parameter (no space) + ["json variableId:inventory", { format: "json", pluginName: "", variableId: "inventory", wasDefaultId: false }], + ["dsv variableId:products", { format: "json", pluginName: "dsv", variableId: "products", wasDefaultId: false }], + ["json value variableId:inventory", { format: "json", pluginName: "value", variableId: "inventory", wasDefaultId: false }], + ["yaml value variableId:products", { format: "yaml", pluginName: "value", variableId: "products", wasDefaultId: false }], + + //Explicit variableId parameter (with space) + ["json variableId: inventory", { format: "json", pluginName: "", variableId: "inventory", wasDefaultId: false }], + ["dsv variableId: products", { format: "json", pluginName: "dsv", variableId: "products", wasDefaultId: false }], + ["json value variableId: inventory", { format: "json", pluginName: "value", variableId: "inventory", wasDefaultId: false }], + ["yaml value variableId: products", { format: "yaml", pluginName: "value", variableId: "products", wasDefaultId: false }], + + //Multiple parameters + ["dsv products delimiter:|", { format: "json", pluginName: "dsv", variableId: "products", wasDefaultId: false }], + ["dsv delimiter:| variableId:products", { format: "json", pluginName: "dsv", variableId: "products", wasDefaultId: false }], + ["dsv delimiter: | variableId: products", { format: "json", pluginName: "dsv", variableId: "products", wasDefaultId: false }], + + //No variableId + ["json value", { format: "json", pluginName: "value", variableId: undefined, wasDefaultId: true }], + ["dsv", { format: "json", pluginName: "dsv", variableId: undefined, wasDefaultId: true }], +]; + +tests.forEach(([input, expected], i) => { + const result = parseFenceInfo(input); + const variableId = result.params.get('variableId'); + const pass = + result.format === expected.format && + result.pluginName === expected.pluginName && + variableId === expected.variableId && + result.wasDefaultId === expected.wasDefaultId; + + console.log( + `${pass ? '✅' : '❌'} Test ${i + 1}: ${pass ? 'PASS' : `FAIL\n Input: "${input}"\n Got: ${JSON.stringify({format: result.format, pluginName: result.pluginName, variableId, wasDefaultId: result.wasDefaultId})}\n Expected: ${JSON.stringify(expected)}`}` + ); +}); +*/ + /** * Creates a plugin that can parse both JSON and YAML formats */ From 1e21dc2f8bab37f52ccf2b41b57e20770a77525f Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 17:34:18 -0800 Subject: [PATCH 33/47] Add default 'value' plugin fallback for JSON and YAML data in create function --- packages/markdown/src/factory.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/markdown/src/factory.ts b/packages/markdown/src/factory.ts index 0567860d..a2384b84 100644 --- a/packages/markdown/src/factory.ts +++ b/packages/markdown/src/factory.ts @@ -170,6 +170,11 @@ export function create() { if (jsonPlugin) { return jsonPlugin; } + // Default to 'value' plugin for json data (e.g., "json products") + const valuePlugin = findPlugin('value'); + if (valuePlugin) { + return valuePlugin; + } } // Fifth priority: Check if it starts with "yaml " and extract the plugin name else if (info.startsWith('yaml ') && infoWords.length > 1) { @@ -177,6 +182,11 @@ export function create() { if (yamlPlugin) { return yamlPlugin; } + // Default to 'value' plugin for yaml data (e.g., "yaml products") + const valuePlugin = findPlugin('value'); + if (valuePlugin) { + return valuePlugin; + } } } From 5e89f665d8287c251f5f08bf5c4226478e742dd2 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 17:34:30 -0800 Subject: [PATCH 34/47] Refactor value plugin to use parseFenceInfo for improved fence info parsing and variable ID handling --- packages/markdown/src/plugins/value.ts | 36 +++++++++++--------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/packages/markdown/src/plugins/value.ts b/packages/markdown/src/plugins/value.ts index ca1fa9a5..1550c0ff 100644 --- a/packages/markdown/src/plugins/value.ts +++ b/packages/markdown/src/plugins/value.ts @@ -8,7 +8,7 @@ import { sanitizedScriptTag, sanitizeHtmlComment } from '../sanitize.js'; import { pluginClassName } from './util.js'; import { PluginNames } from './interfaces.js'; import { SpecReview } from 'common'; -import { parseVariableId } from './dsv.js'; +import { parseFenceInfo } from './config.js'; import * as yaml from 'js-yaml'; interface ValueInstance { @@ -47,37 +47,31 @@ export const valuePlugin: Plugin = { const content = token.content.trim(); const info = token.info.trim(); - // Parse the fence info - expect "json value variableId" or "yaml value variableId" format - // The factory.ts will route "json value ..." or "yaml value ..." to this plugin - const parts = info.split(/\s+/); + // Parse fence info + const { format, pluginName: parsedPluginName, params, wasDefaultId } = parseFenceInfo(info); - // Check for variable ID (should be after "json value" or "yaml value") + // If pluginName isn't "value" AND isn't empty AND no explicit variableId, + // it's actually the variableId (e.g., "json inventory") let variableId: string; - let wasDefaultId = false; - let isYaml = false; + let actualWasDefaultId: boolean; - if (parts.length >= 3 && (parts[0] === 'json' || parts[0] === 'yaml') && parts[1] === 'value') { - // Format: json value variableId or yaml value variableId - variableId = parts[2]; - isYaml = parts[0] === 'yaml'; - } else if (parts.length >= 2 && (parts[0] === 'json' || parts[0] === 'yaml') && parts[1] === 'value') { - // Format: json value or yaml value (no variable ID provided) - variableId = `${parts[0]}Value${index}`; - wasDefaultId = true; - isYaml = parts[0] === 'yaml'; + if (parsedPluginName && parsedPluginName !== pluginName && !params.has('variableId')) { + // The parsed plugin name is actually the variableId + variableId = parsedPluginName; + actualWasDefaultId = false; } else { - // Not the expected format - return ''; + // Normal case: get variableId from params or use default + variableId = params.get('variableId') || `${format}Value${index}`; + actualWasDefaultId = wasDefaultId; } // Use script tag with application/json type for storage - // Note: We store both JSON and YAML data as JSON in the script tag const scriptElement = sanitizedScriptTag(content, { id: `${pluginName}-${index}`, class: className, 'data-variable-id': variableId, - 'data-was-default-id': wasDefaultId.toString(), - 'data-format': isYaml ? 'yaml' : 'json' + 'data-was-default-id': actualWasDefaultId.toString(), + 'data-format': format }); return scriptElement.outerHTML; From df9d27ccad4c9edd229f5b7d0d4c754eb1831a26 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 17:34:38 -0800 Subject: [PATCH 35/47] Refactor parseDsvInfo to utilize parseFenceInfo for improved parameter handling and simplify delimiter logic --- packages/markdown/src/plugins/dsv.ts | 52 +++++++++------------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/packages/markdown/src/plugins/dsv.ts b/packages/markdown/src/plugins/dsv.ts index 90f821a6..561c25e6 100644 --- a/packages/markdown/src/plugins/dsv.ts +++ b/packages/markdown/src/plugins/dsv.ts @@ -9,6 +9,7 @@ import { sanitizedHTML, sanitizeHtmlComment } from '../sanitize.js'; import { pluginClassName } from './util.js'; import { PluginNames } from './interfaces.js'; import { SpecReview } from 'common'; +import { parseFenceInfo } from './config.js'; interface DsvInstance { id: string; @@ -40,42 +41,25 @@ export function parseDsvInfo(info: string, pluginName: string, index: number): { wasDefaultId: boolean; wasDefaultDelimiter: boolean; } { - const parts = info.trim().split(/\s+/); + const { params, wasDefaultId } = parseFenceInfo(info); - let variableId: string | null = null; - let delimiter: string | null = null; + // Get variableId (already handled by parseFenceInfo) + const variableId = params.get('variableId') || `${pluginName}Data${index}`; - for (let i = 0; i < parts.length; i++) { - const part = parts[i]; - - // Parse variableId: parameter - if (part.startsWith('variableId:')) { - const value = part.slice(11); - variableId = value || (i + 1 < parts.length ? parts[++i] : null); - } - // Parse delimiter: parameter - else if (part.startsWith('delimiter:')) { - const value = part.slice(10); - delimiter = value || (i + 1 < parts.length ? parts[++i] : null); - } - // Direct variableId format (not a parameter key) - else if (i > 0 && !variableId && part !== 'variableId:' && part !== 'delimiter:') { - variableId = part; - } - } + // Get delimiter from parameter or use default + let delimiter = params.get('delimiter') || ','; + const wasDefaultDelimiter = !params.has('delimiter'); // Handle special delimiter characters - if (delimiter) { - if (delimiter === '\\t') delimiter = '\t'; - if (delimiter === '\\n') delimiter = '\n'; - if (delimiter === '\\r') delimiter = '\r'; - } + if (delimiter === '\\t') delimiter = '\t'; + if (delimiter === '\\n') delimiter = '\n'; + if (delimiter === '\\r') delimiter = '\r'; return { - variableId: variableId || `${pluginName}Data${index}`, - delimiter: delimiter || ',', - wasDefaultId: !variableId, - wasDefaultDelimiter: !delimiter + variableId, + delimiter, + wasDefaultId, + wasDefaultDelimiter }; } @@ -102,17 +86,13 @@ function inspectDsvSpec(spec: DsvSpec): RawFlaggableSpec { reasons: [] }; - // Flag if we had to use defaults + // Only flag if we had to use default variable ID + // Using default delimiter (comma) is fine and shouldn't be flagged if (spec.wasDefaultId) { result.hasFlags = true; result.reasons.push('No variable ID specified - using default'); } - if (spec.wasDefaultDelimiter) { - result.hasFlags = true; - result.reasons.push('No delimiter specified - using default comma'); - } - return result; } From e7b10872b4f0b1a2bdb0bd852122360722b1973b Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 17:35:10 -0800 Subject: [PATCH 36/47] Refactor JSON and YAML element types to remove '-value' suffix for consistency --- packages/compiler/src/md.ts | 6 +++--- packages/compiler/src/validate/element.ts | 4 ++-- packages/schema-doc/src/inline-data.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/md.ts b/packages/compiler/src/md.ts index e3d8a20d..5e461135 100644 --- a/packages/compiler/src/md.ts +++ b/packages/compiler/src/md.ts @@ -391,13 +391,13 @@ function groupMarkdown(group: ElementGroup, variables: Variable[], vegaScope: Ve mdElements.push(tickWrap(`dsv delimiter:${delimiter} ${variableId}`, dsvContent)); break; } - case 'json-value': { + case 'json': { const { variableId, content } = element; const jsonContent = JSON.stringify(content, null, defaultJsonIndent); - mdElements.push(tickWrap(`json value ${variableId}`, jsonContent)); + mdElements.push(tickWrap(`json ${variableId}`, jsonContent)); break; } - case 'yaml-value': { + case 'yaml': { const { variableId, content } = element; let yamlContent: string; if (typeof content === 'string') { diff --git a/packages/compiler/src/validate/element.ts b/packages/compiler/src/validate/element.ts index fd34fa73..c685e07e 100644 --- a/packages/compiler/src/validate/element.ts +++ b/packages/compiler/src/validate/element.ts @@ -199,8 +199,8 @@ export async function validateElement(element: PageElement, groupIndex: number, } break; } - case 'json-value': - case 'yaml-value': { + case 'json': + case 'yaml': { errors.push(...validateVariableID(element.variableId)); if (!element.content) { errors.push(`${element.type} element must have content property`); diff --git a/packages/schema-doc/src/inline-data.ts b/packages/schema-doc/src/inline-data.ts index 911ff1aa..2040353c 100644 --- a/packages/schema-doc/src/inline-data.ts +++ b/packages/schema-doc/src/inline-data.ts @@ -36,7 +36,7 @@ export interface DsvElement extends ElementBase { * JSON data with type preservation in markdown fence: ```json value variableId */ export interface JsonValueElement extends ElementBase { - type: 'json-value'; + type: 'json'; variableId: VariableID; content: object | object[]; } @@ -45,7 +45,7 @@ export interface JsonValueElement extends ElementBase { * YAML data with type preservation in markdown fence: ```yaml value variableId */ export interface YamlValueElement extends ElementBase { - type: 'yaml-value'; + type: 'yaml'; variableId: VariableID; content: object | object[] | string | string[]; } From 7028155986a93c1d35bc3e244ac00a9c65fda1d3 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 17:35:32 -0800 Subject: [PATCH 37/47] alphabetize --- packages/markdown/src/plugins/interfaces.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/markdown/src/plugins/interfaces.ts b/packages/markdown/src/plugins/interfaces.ts index 613bd877..ccdc58e8 100644 --- a/packages/markdown/src/plugins/interfaces.ts +++ b/packages/markdown/src/plugins/interfaces.ts @@ -19,14 +19,13 @@ export { TsvSpec } from './tsv.js'; export type PluginNames = '#' | + 'checkbox' | 'css' | 'csv' | - 'checkbox' | - 'value' | 'dropdown' | 'dsv' | - 'image' | 'google-fonts' | + 'image' | 'mermaid' | 'number' | 'placeholders' | @@ -36,5 +35,6 @@ export type PluginNames = 'textbox' | 'treebark' | 'tsv' | - 'vega-lite' | - 'vega'; + 'value' | + 'vega' | + 'vega-lite'; From 62b270677943c1287b5fa249d8b94b619f46ad96 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 17:38:01 -0800 Subject: [PATCH 38/47] common header --- packages/schema-doc/src/inline-data.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/schema-doc/src/inline-data.ts b/packages/schema-doc/src/inline-data.ts index 2040353c..d71814d6 100644 --- a/packages/schema-doc/src/inline-data.ts +++ b/packages/schema-doc/src/inline-data.ts @@ -1,7 +1,7 @@ /** - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ +* Copyright (c) Microsoft Corporation. +* Licensed under the MIT License. +*/ import { VariableID, ElementBase } from './common.js'; /** From 06ca7aecb7a9b4a208c2cb167d2cb92ab1ceb0c4 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 17:38:58 -0800 Subject: [PATCH 39/47] remove comments --- packages/schema-doc/src/inline-data.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/packages/schema-doc/src/inline-data.ts b/packages/schema-doc/src/inline-data.ts index d71814d6..903a91bc 100644 --- a/packages/schema-doc/src/inline-data.ts +++ b/packages/schema-doc/src/inline-data.ts @@ -4,27 +4,18 @@ */ import { VariableID, ElementBase } from './common.js'; -/** - * CSV data in markdown fence: ```csv variableId - */ export interface CsvElement extends ElementBase { type: 'csv'; variableId: VariableID; content: string | string[] | string[][]; } -/** - * TSV data in markdown fence: ```tsv variableId - */ export interface TsvElement extends ElementBase { type: 'tsv'; variableId: VariableID; content: string | string[] | string[][]; } -/** - * Custom delimiter-separated data in markdown fence: ```dsv delimiter:| variableId - */ export interface DsvElement extends ElementBase { type: 'dsv'; variableId: VariableID; @@ -32,18 +23,12 @@ export interface DsvElement extends ElementBase { content: string | string[] | string[][]; } -/** - * JSON data with type preservation in markdown fence: ```json value variableId - */ export interface JsonValueElement extends ElementBase { type: 'json'; variableId: VariableID; content: object | object[]; } -/** - * YAML data with type preservation in markdown fence: ```yaml value variableId - */ export interface YamlValueElement extends ElementBase { type: 'yaml'; variableId: VariableID; From c6a1334222901c182eb76116ab39a02c3d9bfe88 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 17:45:16 -0800 Subject: [PATCH 40/47] remove csv etc --- packages/compiler/src/md.ts | 20 +------------------- packages/compiler/src/validate/element.ts | 9 --------- packages/schema-doc/src/inline-data.ts | 22 ---------------------- 3 files changed, 1 insertion(+), 50 deletions(-) diff --git a/packages/compiler/src/md.ts b/packages/compiler/src/md.ts index 5e461135..0bdda49f 100644 --- a/packages/compiler/src/md.ts +++ b/packages/compiler/src/md.ts @@ -373,24 +373,6 @@ function groupMarkdown(group: ElementGroup, variables: Variable[], vegaScope: Ve addSpec('number', numberSpec, false); break; } - case 'csv': { - const { variableId, content } = element; - const csvContent = Array.isArray(content) ? content.join('\n') : content; - mdElements.push(tickWrap(`csv ${variableId}`, csvContent)); - break; - } - case 'tsv': { - const { variableId, content } = element; - const tsvContent = Array.isArray(content) ? content.join('\n') : content; - mdElements.push(tickWrap(`tsv ${variableId}`, tsvContent)); - break; - } - case 'dsv': { - const { variableId, delimiter, content } = element; - const dsvContent = Array.isArray(content) ? content.join('\n') : content; - mdElements.push(tickWrap(`dsv delimiter:${delimiter} ${variableId}`, dsvContent)); - break; - } case 'json': { const { variableId, content } = element; const jsonContent = JSON.stringify(content, null, defaultJsonIndent); @@ -407,7 +389,7 @@ function groupMarkdown(group: ElementGroup, variables: Variable[], vegaScope: Ve } else { yamlContent = yaml.dump(content, { indent: defaultJsonIndent }); } - mdElements.push(tickWrap(`yaml value ${variableId}`, trimTrailingNewline(yamlContent))); + mdElements.push(tickWrap(`yaml ${variableId}`, trimTrailingNewline(yamlContent))); break; } default: { diff --git a/packages/compiler/src/validate/element.ts b/packages/compiler/src/validate/element.ts index c685e07e..75c168aa 100644 --- a/packages/compiler/src/validate/element.ts +++ b/packages/compiler/src/validate/element.ts @@ -190,15 +190,6 @@ export async function validateElement(element: PageElement, groupIndex: number, errors.push(...validateInputElementWithVariableId(element)); break; } - case 'csv': - case 'tsv': - case 'dsv': { - errors.push(...validateVariableID(element.variableId)); - if (element.type === 'dsv' && !element.delimiter) { - errors.push('DSV element must have a delimiter property'); - } - break; - } case 'json': case 'yaml': { errors.push(...validateVariableID(element.variableId)); diff --git a/packages/schema-doc/src/inline-data.ts b/packages/schema-doc/src/inline-data.ts index 903a91bc..4975fd82 100644 --- a/packages/schema-doc/src/inline-data.ts +++ b/packages/schema-doc/src/inline-data.ts @@ -4,25 +4,6 @@ */ import { VariableID, ElementBase } from './common.js'; -export interface CsvElement extends ElementBase { - type: 'csv'; - variableId: VariableID; - content: string | string[] | string[][]; -} - -export interface TsvElement extends ElementBase { - type: 'tsv'; - variableId: VariableID; - content: string | string[] | string[][]; -} - -export interface DsvElement extends ElementBase { - type: 'dsv'; - variableId: VariableID; - delimiter: string; - content: string | string[] | string[][]; -} - export interface JsonValueElement extends ElementBase { type: 'json'; variableId: VariableID; @@ -36,8 +17,5 @@ export interface YamlValueElement extends ElementBase { } export type InlineDataElement = - | CsvElement - | TsvElement - | DsvElement | JsonValueElement | YamlValueElement; From 510ed09a35b93af915fd9394e305a346fc50441f Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 18:00:57 -0800 Subject: [PATCH 41/47] convert to data loaders --- .../features/2.4.inline-csv-data.idoc.json | 92 +++++++++++-------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json index 3decaebb..29b5fc6e 100644 --- a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json +++ b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json @@ -1,6 +1,42 @@ { "$schema": "../../../../docs/schema/idoc_v1.json", "title": "Feature: Inline Delimited Data", + "dataLoaders": [ + { + "dataSourceName": "officeSupplies", + "type": "inline", + "format": "csv", + "content": [ + "item,price", + "Stapler,12.99", + "Pen,1.25", + "Lamp,29.99" + ] + }, + { + "dataSourceName": "people", + "type": "inline", + "format": "tsv", + "content": [ + "name\tage\tcity", + "Alice\t30\tNew York", + "Bob\t25\tLos Angeles", + "Charlie\t35\tChicago" + ] + }, + { + "dataSourceName": "products", + "type": "inline", + "format": "dsv", + "delimiter": "|", + "content": [ + "product|category|rating", + "Laptop|Electronics|4.5", + "Chair|Furniture|4.2", + "Book|Education|4.8" + ] + } + ], "groups": [ { "groupId": "inline_json", @@ -9,12 +45,24 @@ "You can provide JSON data directly as a code block within the document using the **json plugin** with a `variableId` parameter. This is useful for structured data or when you want to include complex data objects.", "", { - "type": "json-value", + "type": "json", "variableId": "inventory", "content": [ - {"item": "Keyboard", "price": 79.99, "inStock": true}, - {"item": "Mouse", "price": 29.99, "inStock": true}, - {"item": "Monitor", "price": 299.99, "inStock": false} + { + "item": "Keyboard", + "price": 79.99, + "inStock": true + }, + { + "item": "Mouse", + "price": 29.99, + "inStock": true + }, + { + "item": "Monitor", + "price": 299.99, + "inStock": false + } ] }, { @@ -33,17 +81,6 @@ "elements": [ "## Inline CSV Data", "You can provide CSV data directly as a code block within the document using the **csv plugin** with a `variableId` parameter. This is useful for small datasets or when you want to include sample data without external dependencies.", - "", - { - "type": "csv", - "variableId": "officeSupplies", - "content": [ - "item,price", - "Stapler,12.99", - "Pen,1.25", - "Lamp,29.99" - ] - }, { "type": "tabulator", "dataSourceName": "officeSupplies", @@ -60,17 +97,6 @@ "elements": [ "## Inline TSV Data", "You can also use tab-separated values (TSV) with the **tsv plugin** and a `variableId` parameter:", - "", - { - "type": "tsv", - "variableId": "people", - "content": [ - "name\tage\tcity", - "Alice\t30\tNew York", - "Bob\t25\tLos Angeles", - "Charlie\t35\tChicago" - ] - }, { "type": "tabulator", "dataSourceName": "people", @@ -87,18 +113,6 @@ "elements": [ "## Inline DSV Data (Custom Delimiter)", "For custom delimiters, use the **dsv plugin** with `delimiter:` and `variableId:` parameters. Here's an example with pipe-separated values:", - "", - { - "type": "dsv", - "variableId": "products", - "delimiter": "|", - "content": [ - "product|category|rating", - "Laptop|Electronics|4.5", - "Chair|Furniture|4.2", - "Book|Education|4.8" - ] - }, { "type": "tabulator", "dataSourceName": "products", @@ -111,4 +125,4 @@ ] } ] -} +} \ No newline at end of file From 7d93eb41dfb17c5f982f50c6a5a61a0d308bf3d2 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 18:01:07 -0800 Subject: [PATCH 42/47] fix: update type from "yaml-value" to "yaml" in YAML Value Plugin test --- packages/web-deploy/json/features/yaml-value-test.idoc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-deploy/json/features/yaml-value-test.idoc.json b/packages/web-deploy/json/features/yaml-value-test.idoc.json index b3da3e9c..8db9acc2 100644 --- a/packages/web-deploy/json/features/yaml-value-test.idoc.json +++ b/packages/web-deploy/json/features/yaml-value-test.idoc.json @@ -9,7 +9,7 @@ "This example tests the value plugin with YAML format.", "", { - "type": "yaml-value", + "type": "yaml", "variableId": "people", "content": [ {"name": "Alice", "age": 25, "city": "New York"}, From 94fe2e6547313356c325a22077525fbcd6715337 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 18:01:50 -0800 Subject: [PATCH 43/47] refactor: remove YAML Value Plugin test JSON file --- .../json/features/yaml-value-test.idoc.json | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 packages/web-deploy/json/features/yaml-value-test.idoc.json diff --git a/packages/web-deploy/json/features/yaml-value-test.idoc.json b/packages/web-deploy/json/features/yaml-value-test.idoc.json deleted file mode 100644 index 8db9acc2..00000000 --- a/packages/web-deploy/json/features/yaml-value-test.idoc.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "../../../../docs/schema/idoc_v1.json", - "title": "YAML Value Plugin Test", - "groups": [ - { - "groupId": "yaml_test", - "elements": [ - "## Testing YAML Value Plugin", - "This example tests the value plugin with YAML format.", - "", - { - "type": "yaml", - "variableId": "people", - "content": [ - {"name": "Alice", "age": 25, "city": "New York"}, - {"name": "Bob", "age": 30, "city": "Chicago"}, - {"name": "Carol", "age": 35, "city": "Los Angeles"}, - {"name": "David", "age": 28, "city": "Seattle"} - ] - }, - { - "type": "tabulator", - "dataSourceName": "people", - "variableId": "selectedPerson", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "300px", - "selectable": 1 - } - }, - "", - "### Selected Person", - "Selected: **{{selectedPerson[0].name}}** from {{selectedPerson[0].city}} ({{selectedPerson[0].age}} years old)" - ] - } - ] -} From 1a12e45ce18d42122874a54a0839fc605a4c432b Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 18:06:44 -0800 Subject: [PATCH 44/47] refactor: remove InlineDataElement and related exports --- packages/schema-doc/src/index.ts | 1 - packages/schema-doc/src/inline-data.ts | 21 --------------------- packages/schema-doc/src/page.ts | 3 +-- 3 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 packages/schema-doc/src/inline-data.ts diff --git a/packages/schema-doc/src/index.ts b/packages/schema-doc/src/index.ts index 5c19bab9..cfb2a6af 100644 --- a/packages/schema-doc/src/index.ts +++ b/packages/schema-doc/src/index.ts @@ -4,7 +4,6 @@ */ export * from './common.js'; export * from './datasource.js'; -export * from './inline-data.js'; export * from './interactive.js'; export * from './page.js'; export * from './schema.js'; diff --git a/packages/schema-doc/src/inline-data.ts b/packages/schema-doc/src/inline-data.ts deleted file mode 100644 index 4975fd82..00000000 --- a/packages/schema-doc/src/inline-data.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** -* Copyright (c) Microsoft Corporation. -* Licensed under the MIT License. -*/ -import { VariableID, ElementBase } from './common.js'; - -export interface JsonValueElement extends ElementBase { - type: 'json'; - variableId: VariableID; - content: object | object[]; -} - -export interface YamlValueElement extends ElementBase { - type: 'yaml'; - variableId: VariableID; - content: object | object[] | string | string[]; -} - -export type InlineDataElement = - | JsonValueElement - | YamlValueElement; diff --git a/packages/schema-doc/src/page.ts b/packages/schema-doc/src/page.ts index afdea4e9..49ca5d87 100644 --- a/packages/schema-doc/src/page.ts +++ b/packages/schema-doc/src/page.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ import { InteractiveElement } from './interactive.js'; -import { InlineDataElement } from './inline-data.js'; import { DataLoader } from './datasource.js'; import { Variable } from './common.js'; @@ -46,7 +45,7 @@ export interface InteractiveDocument { export type MarkdownElement = string; /** Union type for all possible elements */ -export type PageElement = MarkdownElement | InteractiveElement | InlineDataElement; +export type PageElement = MarkdownElement | InteractiveElement; export interface PageStyle { /** CSS styles, either a string, or array of strings which will be concatenated. The array is for developer ergonomics for authoring and merging. */ From 82edd3cd288e9570c1ba6d5d984474631458ce77 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 18:07:44 -0800 Subject: [PATCH 45/47] refactor: remove JSON and YAML handling from groupMarkdown function --- packages/compiler/src/md.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/compiler/src/md.ts b/packages/compiler/src/md.ts index 0bdda49f..1c49d9fd 100644 --- a/packages/compiler/src/md.ts +++ b/packages/compiler/src/md.ts @@ -373,25 +373,6 @@ function groupMarkdown(group: ElementGroup, variables: Variable[], vegaScope: Ve addSpec('number', numberSpec, false); break; } - case 'json': { - const { variableId, content } = element; - const jsonContent = JSON.stringify(content, null, defaultJsonIndent); - mdElements.push(tickWrap(`json ${variableId}`, jsonContent)); - break; - } - case 'yaml': { - const { variableId, content } = element; - let yamlContent: string; - if (typeof content === 'string') { - yamlContent = content; - } else if (Array.isArray(content) && content.every(item => typeof item === 'string')) { - yamlContent = content.join('\n'); - } else { - yamlContent = yaml.dump(content, { indent: defaultJsonIndent }); - } - mdElements.push(tickWrap(`yaml ${variableId}`, trimTrailingNewline(yamlContent))); - break; - } default: { //output as a comment mdElements.push(tickWrap('#', JSON.stringify(element))); From 9fa8a8435d56b6609ba3c88799d7ef1857ef72d1 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 18:08:13 -0800 Subject: [PATCH 46/47] refactor: remove JSON and YAML element validation from validateElement function --- packages/compiler/src/validate/element.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/compiler/src/validate/element.ts b/packages/compiler/src/validate/element.ts index 75c168aa..a8b3307b 100644 --- a/packages/compiler/src/validate/element.ts +++ b/packages/compiler/src/validate/element.ts @@ -190,14 +190,6 @@ export async function validateElement(element: PageElement, groupIndex: number, errors.push(...validateInputElementWithVariableId(element)); break; } - case 'json': - case 'yaml': { - errors.push(...validateVariableID(element.variableId)); - if (!element.content) { - errors.push(`${element.type} element must have content property`); - } - break; - } default: { errors.push(`Unknown element type ${(element as any).type} at group ${groupIndex}, element index ${elementIndex}: ${JSON.stringify(element)}`); break; From 2169e7b035f9dd04881f33d8f91208ae2a76651c Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Thu, 20 Nov 2025 18:10:52 -0800 Subject: [PATCH 47/47] remove json --- .../features/2.4.inline-csv-data.idoc.json | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json index 29b5fc6e..2e1055f1 100644 --- a/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json +++ b/packages/web-deploy/json/features/2.4.inline-csv-data.idoc.json @@ -38,44 +38,6 @@ } ], "groups": [ - { - "groupId": "inline_json", - "elements": [ - "## Inline JSON Value", - "You can provide JSON data directly as a code block within the document using the **json plugin** with a `variableId` parameter. This is useful for structured data or when you want to include complex data objects.", - "", - { - "type": "json", - "variableId": "inventory", - "content": [ - { - "item": "Keyboard", - "price": 79.99, - "inStock": true - }, - { - "item": "Mouse", - "price": 29.99, - "inStock": true - }, - { - "item": "Monitor", - "price": 299.99, - "inStock": false - } - ] - }, - { - "type": "tabulator", - "dataSourceName": "inventory", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "150px" - } - } - ] - }, { "groupId": "inline_csv", "elements": [