diff --git a/docs/assets/chartifact-examples-json.zip b/docs/assets/chartifact-examples-json.zip index 361ff174..ffedb24a 100644 Binary files a/docs/assets/chartifact-examples-json.zip and b/docs/assets/chartifact-examples-json.zip differ diff --git a/docs/assets/chartifact-examples-markdown.zip b/docs/assets/chartifact-examples-markdown.zip index 9222d552..59766e1c 100644 Binary files a/docs/assets/chartifact-examples-markdown.zip and b/docs/assets/chartifact-examples-markdown.zip differ diff --git a/docs/assets/chartifact-examples.zip b/docs/assets/chartifact-examples.zip index 3b16bef9..888bcddc 100644 Binary files a/docs/assets/chartifact-examples.zip and b/docs/assets/chartifact-examples.zip differ diff --git a/docs/assets/examples/json/features/2.4.inline-csv-data.idoc.json b/docs/assets/examples/json/features/2.4.inline-csv-data.idoc.json index 961e11f9..f51d8dd5 100644 --- a/docs/assets/examples/json/features/2.4.inline-csv-data.idoc.json +++ b/docs/assets/examples/json/features/2.4.inline-csv-data.idoc.json @@ -1,14 +1,48 @@ { "$schema": "https://microsoft.github.io/chartifact/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_csv", "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.", - "", - "```csv officeSupplies\nitem,price\nStapler,12.99\nPen,1.25\nLamp,29.99\n```", { "type": "tabulator", "dataSourceName": "officeSupplies", @@ -25,8 +59,6 @@ "elements": [ "## 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": "tabulator", "dataSourceName": "people", @@ -43,8 +75,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:", - "", - "```dsv delimiter:| variableId:products\nproduct|category|rating\nLaptop|Electronics|4.5\nChair|Furniture|4.2\nBook|Education|4.8\n```", { "type": "tabulator", "dataSourceName": "products", diff --git a/docs/assets/examples/markdown/features/2.4.inline-csv-data.idoc.md b/docs/assets/examples/markdown/features/2.4.inline-csv-data.idoc.md index 5cc35819..f05775cb 100644 --- a/docs/assets/examples/markdown/features/2.4.inline-csv-data.idoc.md +++ b/docs/assets/examples/markdown/features/2.4.inline-csv-data.idoc.md @@ -1,13 +1,6 @@ ## 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 -item,price -Stapler,12.99 -Pen,1.25 -Lamp,29.99 -``` - ```json tabulator { @@ -24,13 +17,6 @@ Lamp,29.99 ## Inline TSV Data You can also use tab-separated values (TSV) with the **tsv plugin** and a `variableId` parameter: -```tsv people -name age city -Alice 30 New York -Bob 25 Los Angeles -Charlie 35 Chicago -``` - ```json tabulator { @@ -47,13 +33,6 @@ Charlie 35 Chicago ## 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 -product|category|rating -Laptop|Electronics|4.5 -Chair|Furniture|4.2 -Book|Education|4.8 -``` - ```json tabulator { @@ -64,4 +43,28 @@ Book|Education|4.8 "maxHeight": "150px" } } +``` + + +```csv officeSupplies +item,price +Stapler,12.99 +Pen,1.25 +Lamp,29.99 +``` + + +```tsv people +name age city +Alice 30 New York +Bob 25 Los Angeles +Charlie 35 Chicago +``` + + +```dsv delimiter:| variableId:products +product|category|rating +Laptop|Electronics|4.5 +Chair|Furniture|4.2 +Book|Education|4.8 ``` \ No newline at end of file diff --git a/docs/dist/v1/chartifact-1.0.8.vsix b/docs/dist/v1/chartifact-1.0.9.vsix similarity index 63% rename from docs/dist/v1/chartifact-1.0.8.vsix rename to docs/dist/v1/chartifact-1.0.9.vsix index 4887b792..fc90234d 100644 Binary files a/docs/dist/v1/chartifact-1.0.8.vsix and b/docs/dist/v1/chartifact-1.0.9.vsix differ diff --git a/docs/dist/v1/chartifact.compiler.umd.js b/docs/dist/v1/chartifact.compiler.umd.js index 251fc872..5ab12216 100644 --- a/docs/dist/v1/chartifact.compiler.umd.js +++ b/docs/dist/v1/chartifact.compiler.umd.js @@ -509,7 +509,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy return errors; } const illegalChars = "/|\\'\"`,.;:~-=+?!@#$%^&*()[]{}<>"; - const ignoredSignals = ["width", "height", "padding", "autosize", "background", "style", "parent", "datum", "item", "event", "cursor", "origins"]; + const ignoredSignals = ["width", "height", "padding", "autosize", "background", "style", "parent", "datum", "item", "event", "cursor", "value"]; function validateRequiredString(value, propertyName, elementType) { const errors = []; if (!value) { diff --git a/docs/dist/v1/chartifact.editor.umd.js b/docs/dist/v1/chartifact.editor.umd.js index 296e7627..1abe43d7 100644 --- a/docs/dist/v1/chartifact.editor.umd.js +++ b/docs/dist/v1/chartifact.editor.umd.js @@ -493,7 +493,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy return errors; } const illegalChars = "/|\\'\"`,.;:~-=+?!@#$%^&*()[]{}<>"; - const ignoredSignals = ["width", "height", "padding", "autosize", "background", "style", "parent", "datum", "item", "event", "cursor", "origins"]; + const ignoredSignals = ["width", "height", "padding", "autosize", "background", "style", "parent", "datum", "item", "event", "cursor", "value"]; function validateRequiredString(value, propertyName, elementType) { const errors = []; if (!value) { diff --git a/docs/dist/v1/chartifact.host.umd.js b/docs/dist/v1/chartifact.host.umd.js index a51216d3..76eae070 100644 --- a/docs/dist/v1/chartifact.host.umd.js +++ b/docs/dist/v1/chartifact.host.umd.js @@ -493,7 +493,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy return errors; } const illegalChars = "/|\\'\"`,.;:~-=+?!@#$%^&*()[]{}<>"; - const ignoredSignals = ["width", "height", "padding", "autosize", "background", "style", "parent", "datum", "item", "event", "cursor", "origins"]; + const ignoredSignals = ["width", "height", "padding", "autosize", "background", "style", "parent", "datum", "item", "event", "cursor", "value"]; function validateRequiredString(value, propertyName, elementType) { const errors = []; if (!value) { diff --git a/docs/dist/v1/chartifact.markdown.umd.js b/docs/dist/v1/chartifact.markdown.umd.js index c8fa689d..bf384f13 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; @@ -567,6 +579,45 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy container.appendChild(comment); return container.innerHTML; } + function parseFenceInfo(info) { + const parts = info.trim().split(/\s+/); + let format = "json"; + let startIndex = 0; + if (parts[0] === "json" || parts[0] === "yaml") { + format = parts[0]; + startIndex = 1; + } + let pluginName2 = ""; + let pluginNameIndex = startIndex; + if (startIndex < parts.length && !parts[startIndex].includes(":")) { + pluginName2 = parts[startIndex]; + pluginNameIndex = startIndex + 1; + } + const params = /* @__PURE__ */ new Map(); + let variableId = null; + for (let i = pluginNameIndex; i < parts.length; i++) { + const part = parts[i]; + const colonIndex = part.indexOf(":"); + if (colonIndex > 0) { + const key = part.slice(0, colonIndex); + const value = part.slice(colonIndex + 1); + if (value) { + params.set(key, value); + } else if (i + 1 < parts.length) { + params.set(key, parts[++i]); + } + } else if (!variableId) { + variableId = part; + } + } + const explicitVariableId = params.get("variableId"); + const finalVariableId = explicitVariableId || variableId; + const wasDefaultId = !finalVariableId; + if (finalVariableId) { + params.set("variableId", finalVariableId); + } + return { format, pluginName: pluginName2, params, wasDefaultId }; + } function flaggablePlugin(pluginName2, className2, flagger, attrs) { const plugin = { name: pluginName2, @@ -628,10 +679,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 +697,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 +759,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 +878,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 +1002,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 +1027,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,27 +1139,32 @@ 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); + const valuePlugin2 = findPlugin("value"); + if (valuePlugin2) { + return valuePlugin2; + } + } else if (info.startsWith("yaml ") && infoWords.length > 1) { + const yamlPlugin = findPlugin(infoWords[1]); if (yamlPlugin) { return yamlPlugin; } + const valuePlugin2 = findPlugin("value"); + if (valuePlugin2) { + return valuePlugin2; + } } } if (originalFence) { @@ -1123,10 +1179,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 +1203,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 +1246,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 +1477,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 +1507,7 @@ ${reconstitutedRules.join("\n\n")} target.appendChild(styleElement); comments.push(``); cssInstances.push({ - id: `${pluginName$c}-${index2}`, + id: `${pluginName$d}-${index2}`, element: styleElement }); } else { @@ -1474,43 +1530,27 @@ ${reconstitutedRules.join("\n\n")} return instances; } }; - function parseVariableId(info, pluginName2, index2) { - const parts = info.trim().split(/\s+/); - for (const part of parts) { - if (part.startsWith("variableId:")) { - return { - variableId: part.slice(11).trim(), - // Remove 'variableId:' prefix and trim spaces - wasDefaultId: false - }; - } - } - if (parts.length >= 2) { - const secondPart = parts[1]; - if (!secondPart.startsWith("delimiter:") && !secondPart.startsWith("variableId:")) { - return { - variableId: secondPart, - wasDefaultId: false - }; - } - } + function parseDsvInfo(info, pluginName2, index2) { + const { params, wasDefaultId } = parseFenceInfo(info); + const variableId = params.get("variableId") || `${pluginName2}Data${index2}`; + let delimiter = params.get("delimiter") || ","; + const wasDefaultDelimiter = !params.has("delimiter"); + if (delimiter === "\\t") delimiter = " "; + if (delimiter === "\\n") delimiter = "\n"; + if (delimiter === "\\r") delimiter = "\r"; return { - variableId: `${pluginName2}Data${index2}`, - wasDefaultId: true + variableId, + delimiter, + wasDefaultId, + wasDefaultDelimiter }; } - function parseDelimiter(info) { - const parts = info.trim().split(/\s+/); - for (const part of parts) { - if (part.startsWith("delimiter:")) { - let delimiter = part.slice(10).trim(); - if (delimiter === "\\t") delimiter = " "; - if (delimiter === "\\n") delimiter = "\n"; - if (delimiter === "\\r") delimiter = "\r"; - return { delimiter, wasDefaultDelimiter: false }; - } - } - return { delimiter: ",", wasDefaultDelimiter: true }; + function parseVariableId(info, pluginName2, index2) { + const result = parseDsvInfo(info, pluginName2, index2); + return { + variableId: result.variableId, + wasDefaultId: result.wasDefaultId + }; } function inspectDsvSpec(spec) { const result = { @@ -1522,24 +1562,19 @@ ${reconstitutedRules.join("\n\n")} 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; } - 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); + const { variableId, delimiter, wasDefaultId, wasDefaultDelimiter } = parseDsvInfo(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 +1585,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 +1593,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 +1615,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 +1631,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 +1652,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 +1695,163 @@ ${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 { format, pluginName: parsedPluginName, params, wasDefaultId } = parseFenceInfo(info); + let variableId; + let actualWasDefaultId; + if (parsedPluginName && parsedPluginName !== pluginName$b && !params.has("variableId")) { + variableId = parsedPluginName; + actualWasDefaultId = false; + } else { + variableId = params.get("variableId") || `${format}Value${index2}`; + actualWasDefaultId = wasDefaultId; + } + const scriptElement = sanitizedScriptTag(content, { + id: `${pluginName$b}-${index2}`, + class: className$b, + "data-variable-id": variableId, + "data-was-default-id": actualWasDefaultId.toString(), + "data-format": format + }); + 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 +2305,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 +2356,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 +2378,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 +2401,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); @@ -3459,7 +3656,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) { @@ -3501,14 +3700,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(); @@ -3626,17 +3825,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 () => { @@ -3683,6 +3889,11 @@ ${reconstitutedRules.join("\n\n")} } } } + vegaInstance.isListening = true; + if (vegaInstance.needToRun) { + view.runAsync(); + vegaInstance.needToRun = false; + } }, getCurrentSignalValue: (signalName) => { var _a; @@ -3925,6 +4136,7 @@ ${reconstitutedRules.join("\n\n")} registerMarkdownPlugin(commentPlugin); registerMarkdownPlugin(cssPlugin); registerMarkdownPlugin(csvPlugin); + registerMarkdownPlugin(valuePlugin); registerMarkdownPlugin(dsvPlugin); registerMarkdownPlugin(googleFontsPlugin); registerMarkdownPlugin(dropdownPlugin); @@ -4056,8 +4268,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/package-lock.json b/package-lock.json index 9d4b6c4b..8430a926 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15448,7 +15448,7 @@ }, "packages/vscode": { "name": "chartifact", - "version": "1.0.8", + "version": "1.0.9", "license": "MIT", "devDependencies": { "@vscode/vsce": "^3.7.0" diff --git a/packages/vscode/CHANGELOG.md b/packages/vscode/CHANGELOG.md index a8c7d3a3..97050c61 100644 --- a/packages/vscode/CHANGELOG.md +++ b/packages/vscode/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## [1.0.9] + +Bug fixes + ## [1.0.8] Updated examples diff --git a/packages/vscode/package.json b/packages/vscode/package.json index 0bcccfad..b1c2c323 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -2,7 +2,7 @@ "name": "chartifact", "displayName": "Chartifact", "description": "Declarative, interactive data documents", - "version": "1.0.8", + "version": "1.0.9", "publisher": "msrvida", "repository": { "type": "git",