@scrider/formatter 1.4.4 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -999,6 +999,25 @@ declare const tableColAlignFormat: Format<TableColAlignType>;
999
999
  */
1000
1000
  declare const blockFormat: Format<Record<string, unknown>>;
1001
1001
 
1002
+ /**
1003
+ * Code Widget embed format (Phase 8 Part 3.5)
1004
+ *
1005
+ * Delta: { insert: { codeWidget: "https://codesandbox.io/s/abc123" } }
1006
+ *
1007
+ * Value is a URL to an interactive code playground (StackBlitz, CodeSandbox,
1008
+ * Replit, CodePen, JSFiddle). Rendered as an <iframe> carrying a
1009
+ * `data-code-widget` marker so it can be told apart from a plain video iframe
1010
+ * during HTML → Delta (see videoFormat.match guard).
1011
+ *
1012
+ * Markdown: ![Widget](url)
1013
+ * HTML: <iframe data-code-widget src="<embed-url>" frameborder="0" allowfullscreen>
1014
+ *
1015
+ * The src is run through `toCodeWidgetEmbedUrl` at render time, which is
1016
+ * idempotent, so resize/float attributes and the Delta ↔ HTML round-trip stay
1017
+ * stable regardless of whether the stored value is the user URL or embed URL.
1018
+ */
1019
+ declare const codeWidgetFormat: Format<string>;
1020
+
1002
1021
  /**
1003
1022
  * Divider (Horizontal Rule) embed format
1004
1023
  *
@@ -1580,6 +1599,26 @@ declare function escapeHtml(text: string): string;
1580
1599
  * Unescape HTML entities
1581
1600
  */
1582
1601
  declare function unescapeHtml(text: string): string;
1602
+ /**
1603
+ * Convert a code-playground URL to an embeddable iframe URL (Phase 8 Part 3.5).
1604
+ *
1605
+ * Idempotent: a URL that is already in embed form is returned unchanged, so the
1606
+ * Delta → HTML → Delta round-trip is stable regardless of which form is stored.
1607
+ *
1608
+ * Provider | User URL | Embed URL
1609
+ * -------------|----------------------------------|-----------------------------------
1610
+ * StackBlitz | stackblitz.com/edit/{id} | …?embed=1
1611
+ * | stackblitz.com/github/{u}/{r} | …?embed=1
1612
+ * CodeSandbox | codesandbox.io/s/{id} | codesandbox.io/embed/{id}
1613
+ * Replit | replit.com/@{u}/{repl} | …?embed=true
1614
+ * CodePen | codepen.io/{u}/pen/{id} | codepen.io/{u}/embed/{id}
1615
+ * JSFiddle | jsfiddle.net/{u}/{id}/ | jsfiddle.net/{u}/{id}/embedded/
1616
+ *
1617
+ * Unknown hosts are returned unchanged (the marker `data-code-widget` still
1618
+ * makes them render as an iframe; auto-detection of bare URLs lives in the
1619
+ * editor layer).
1620
+ */
1621
+ declare function toCodeWidgetEmbedUrl(url: string): string;
1583
1622
 
1584
1623
  /**
1585
1624
  * GitHub-compatible slugify for heading anchor links.
@@ -1944,4 +1983,4 @@ declare function isTableNewlineOp(op: Op | undefined): boolean;
1944
1983
  */
1945
1984
  declare function extractTableRegion(ops: readonly Op[], hintOpIdx: number): TableRegion | null;
1946
1985
 
1947
- export { ALERT_TYPES, type AlertBlockData, type AlertType, type AlignType, BOX_FLOAT_VALUES, BOX_OVERFLOW_VALUES, type BlockContext, type BlockHandler, BlockHandlerRegistry, type BlockRenderOptions, type BoxBlockData, type BoxFloat, type BoxOpAttributes, type BoxOverflow, BrowserDOMAdapter, type CellAlign, type CellData, type ColumnsBlockData, type DOMAdapter, type DOMDocument, type DOMDocumentFragment, type DOMElement, type DOMNode, type DOMNodeList, type DeltaToHtmlOptions, type DeltaToMarkdownOptions, type DocumentPresentation, type FootnotesBlockData, type Format, type FormatDefinition, type FormatMatchResult, type FormatScope, type HtmlToDeltaOptions, LINE_HEIGHT_BLOCK_TAGS, type ListType, type MarkdownToDeltaOptions, NODE_TYPE, NodeDOMAdapter, PARAGRAPH_SPACING_BLOCK_TAGS, Registry, type ResolvedDocumentPresentation, type ResolvedTablePresentation, SCRIDER_LINE_HEIGHT_KEY, SCRIDER_MARGIN_AFTER_KEY, SCRIDER_MARGIN_BEFORE_KEY, type SanitizeOptions, type TableBlockData, type TableCellAlign, type TableColAlignType, type TablePresentation, type TableRegion, alertBlockHandler, alignFormat, backgroundFormat, blockFormat, blockLineHeightStyleParts, blockMarginAfterStyleParts, blockMarginBeforeStyleParts, blockParagraphMarginStyleParts, blockPresentationStyleParts, blockquoteFormat, boldFormat, boxBlockHandler, browserAdapter, cloneDelta, codeBlockFormat, codeFormat, colorFormat, columnsBlockHandler, createDefaultBlockHandlers, createDefaultRegistry, defaultBlockFormats, defaultEmbedFormats, defaultFormats, defaultInlineFormats, deltaToHtml, deltaToMarkdown, dividerFormat, documentPresentationStyleParts, escapeHtml, extractBoxOpAttributes, extractTableRegion, fontFormat, footnoteRefFormat, footnotesBlockHandler, formulaFormat, getAdapter, getNamedColors, headerFormat, headerIdFormat, htmlToDelta, imageFormat, indentFormat, isAdapterAvailable, isElement, isRemarkAvailable, isTableNewlineOp, isTextNode, isValidColor, isValidHexColor, isZebraBodyRow, italicFormat, kbdFormat, linkFormat, listFormat, markFormat, markdownToDelta, markdownToDeltaSync, nodeAdapter, normalizeDelta, parseScriderLineHeightMultiplier, parseScriderMarginAfterEm, parseScriderMarginBeforeEm, parseScriderMarginEm, preloadRemark, resolveDocumentPresentation, resolveTablePresentation, sanitizeDelta, sizeFormat, slugify, slugifyWithDedup, softBreakFormat, strikeFormat, subscriptFormat, superscriptFormat, tableBlockHandler, tableColAlignFormat, tableColFormat, tableHeaderFormat, tableRowFormat, toHexColor, underlineFormat, unescapeHtml, validateDelta, videoFormat };
1986
+ export { ALERT_TYPES, type AlertBlockData, type AlertType, type AlignType, BOX_FLOAT_VALUES, BOX_OVERFLOW_VALUES, type BlockContext, type BlockHandler, BlockHandlerRegistry, type BlockRenderOptions, type BoxBlockData, type BoxFloat, type BoxOpAttributes, type BoxOverflow, BrowserDOMAdapter, type CellAlign, type CellData, type ColumnsBlockData, type DOMAdapter, type DOMDocument, type DOMDocumentFragment, type DOMElement, type DOMNode, type DOMNodeList, type DeltaToHtmlOptions, type DeltaToMarkdownOptions, type DocumentPresentation, type FootnotesBlockData, type Format, type FormatDefinition, type FormatMatchResult, type FormatScope, type HtmlToDeltaOptions, LINE_HEIGHT_BLOCK_TAGS, type ListType, type MarkdownToDeltaOptions, NODE_TYPE, NodeDOMAdapter, PARAGRAPH_SPACING_BLOCK_TAGS, Registry, type ResolvedDocumentPresentation, type ResolvedTablePresentation, SCRIDER_LINE_HEIGHT_KEY, SCRIDER_MARGIN_AFTER_KEY, SCRIDER_MARGIN_BEFORE_KEY, type SanitizeOptions, type TableBlockData, type TableCellAlign, type TableColAlignType, type TablePresentation, type TableRegion, alertBlockHandler, alignFormat, backgroundFormat, blockFormat, blockLineHeightStyleParts, blockMarginAfterStyleParts, blockMarginBeforeStyleParts, blockParagraphMarginStyleParts, blockPresentationStyleParts, blockquoteFormat, boldFormat, boxBlockHandler, browserAdapter, cloneDelta, codeBlockFormat, codeFormat, codeWidgetFormat, colorFormat, columnsBlockHandler, createDefaultBlockHandlers, createDefaultRegistry, defaultBlockFormats, defaultEmbedFormats, defaultFormats, defaultInlineFormats, deltaToHtml, deltaToMarkdown, dividerFormat, documentPresentationStyleParts, escapeHtml, extractBoxOpAttributes, extractTableRegion, fontFormat, footnoteRefFormat, footnotesBlockHandler, formulaFormat, getAdapter, getNamedColors, headerFormat, headerIdFormat, htmlToDelta, imageFormat, indentFormat, isAdapterAvailable, isElement, isRemarkAvailable, isTableNewlineOp, isTextNode, isValidColor, isValidHexColor, isZebraBodyRow, italicFormat, kbdFormat, linkFormat, listFormat, markFormat, markdownToDelta, markdownToDeltaSync, nodeAdapter, normalizeDelta, parseScriderLineHeightMultiplier, parseScriderMarginAfterEm, parseScriderMarginBeforeEm, parseScriderMarginEm, preloadRemark, resolveDocumentPresentation, resolveTablePresentation, sanitizeDelta, sizeFormat, slugify, slugifyWithDedup, softBreakFormat, strikeFormat, subscriptFormat, superscriptFormat, tableBlockHandler, tableColAlignFormat, tableColFormat, tableHeaderFormat, tableRowFormat, toCodeWidgetEmbedUrl, toHexColor, underlineFormat, unescapeHtml, validateDelta, videoFormat };
package/dist/index.d.ts CHANGED
@@ -999,6 +999,25 @@ declare const tableColAlignFormat: Format<TableColAlignType>;
999
999
  */
1000
1000
  declare const blockFormat: Format<Record<string, unknown>>;
1001
1001
 
1002
+ /**
1003
+ * Code Widget embed format (Phase 8 Part 3.5)
1004
+ *
1005
+ * Delta: { insert: { codeWidget: "https://codesandbox.io/s/abc123" } }
1006
+ *
1007
+ * Value is a URL to an interactive code playground (StackBlitz, CodeSandbox,
1008
+ * Replit, CodePen, JSFiddle). Rendered as an <iframe> carrying a
1009
+ * `data-code-widget` marker so it can be told apart from a plain video iframe
1010
+ * during HTML → Delta (see videoFormat.match guard).
1011
+ *
1012
+ * Markdown: ![Widget](url)
1013
+ * HTML: <iframe data-code-widget src="<embed-url>" frameborder="0" allowfullscreen>
1014
+ *
1015
+ * The src is run through `toCodeWidgetEmbedUrl` at render time, which is
1016
+ * idempotent, so resize/float attributes and the Delta ↔ HTML round-trip stay
1017
+ * stable regardless of whether the stored value is the user URL or embed URL.
1018
+ */
1019
+ declare const codeWidgetFormat: Format<string>;
1020
+
1002
1021
  /**
1003
1022
  * Divider (Horizontal Rule) embed format
1004
1023
  *
@@ -1580,6 +1599,26 @@ declare function escapeHtml(text: string): string;
1580
1599
  * Unescape HTML entities
1581
1600
  */
1582
1601
  declare function unescapeHtml(text: string): string;
1602
+ /**
1603
+ * Convert a code-playground URL to an embeddable iframe URL (Phase 8 Part 3.5).
1604
+ *
1605
+ * Idempotent: a URL that is already in embed form is returned unchanged, so the
1606
+ * Delta → HTML → Delta round-trip is stable regardless of which form is stored.
1607
+ *
1608
+ * Provider | User URL | Embed URL
1609
+ * -------------|----------------------------------|-----------------------------------
1610
+ * StackBlitz | stackblitz.com/edit/{id} | …?embed=1
1611
+ * | stackblitz.com/github/{u}/{r} | …?embed=1
1612
+ * CodeSandbox | codesandbox.io/s/{id} | codesandbox.io/embed/{id}
1613
+ * Replit | replit.com/@{u}/{repl} | …?embed=true
1614
+ * CodePen | codepen.io/{u}/pen/{id} | codepen.io/{u}/embed/{id}
1615
+ * JSFiddle | jsfiddle.net/{u}/{id}/ | jsfiddle.net/{u}/{id}/embedded/
1616
+ *
1617
+ * Unknown hosts are returned unchanged (the marker `data-code-widget` still
1618
+ * makes them render as an iframe; auto-detection of bare URLs lives in the
1619
+ * editor layer).
1620
+ */
1621
+ declare function toCodeWidgetEmbedUrl(url: string): string;
1583
1622
 
1584
1623
  /**
1585
1624
  * GitHub-compatible slugify for heading anchor links.
@@ -1944,4 +1983,4 @@ declare function isTableNewlineOp(op: Op | undefined): boolean;
1944
1983
  */
1945
1984
  declare function extractTableRegion(ops: readonly Op[], hintOpIdx: number): TableRegion | null;
1946
1985
 
1947
- export { ALERT_TYPES, type AlertBlockData, type AlertType, type AlignType, BOX_FLOAT_VALUES, BOX_OVERFLOW_VALUES, type BlockContext, type BlockHandler, BlockHandlerRegistry, type BlockRenderOptions, type BoxBlockData, type BoxFloat, type BoxOpAttributes, type BoxOverflow, BrowserDOMAdapter, type CellAlign, type CellData, type ColumnsBlockData, type DOMAdapter, type DOMDocument, type DOMDocumentFragment, type DOMElement, type DOMNode, type DOMNodeList, type DeltaToHtmlOptions, type DeltaToMarkdownOptions, type DocumentPresentation, type FootnotesBlockData, type Format, type FormatDefinition, type FormatMatchResult, type FormatScope, type HtmlToDeltaOptions, LINE_HEIGHT_BLOCK_TAGS, type ListType, type MarkdownToDeltaOptions, NODE_TYPE, NodeDOMAdapter, PARAGRAPH_SPACING_BLOCK_TAGS, Registry, type ResolvedDocumentPresentation, type ResolvedTablePresentation, SCRIDER_LINE_HEIGHT_KEY, SCRIDER_MARGIN_AFTER_KEY, SCRIDER_MARGIN_BEFORE_KEY, type SanitizeOptions, type TableBlockData, type TableCellAlign, type TableColAlignType, type TablePresentation, type TableRegion, alertBlockHandler, alignFormat, backgroundFormat, blockFormat, blockLineHeightStyleParts, blockMarginAfterStyleParts, blockMarginBeforeStyleParts, blockParagraphMarginStyleParts, blockPresentationStyleParts, blockquoteFormat, boldFormat, boxBlockHandler, browserAdapter, cloneDelta, codeBlockFormat, codeFormat, colorFormat, columnsBlockHandler, createDefaultBlockHandlers, createDefaultRegistry, defaultBlockFormats, defaultEmbedFormats, defaultFormats, defaultInlineFormats, deltaToHtml, deltaToMarkdown, dividerFormat, documentPresentationStyleParts, escapeHtml, extractBoxOpAttributes, extractTableRegion, fontFormat, footnoteRefFormat, footnotesBlockHandler, formulaFormat, getAdapter, getNamedColors, headerFormat, headerIdFormat, htmlToDelta, imageFormat, indentFormat, isAdapterAvailable, isElement, isRemarkAvailable, isTableNewlineOp, isTextNode, isValidColor, isValidHexColor, isZebraBodyRow, italicFormat, kbdFormat, linkFormat, listFormat, markFormat, markdownToDelta, markdownToDeltaSync, nodeAdapter, normalizeDelta, parseScriderLineHeightMultiplier, parseScriderMarginAfterEm, parseScriderMarginBeforeEm, parseScriderMarginEm, preloadRemark, resolveDocumentPresentation, resolveTablePresentation, sanitizeDelta, sizeFormat, slugify, slugifyWithDedup, softBreakFormat, strikeFormat, subscriptFormat, superscriptFormat, tableBlockHandler, tableColAlignFormat, tableColFormat, tableHeaderFormat, tableRowFormat, toHexColor, underlineFormat, unescapeHtml, validateDelta, videoFormat };
1986
+ export { ALERT_TYPES, type AlertBlockData, type AlertType, type AlignType, BOX_FLOAT_VALUES, BOX_OVERFLOW_VALUES, type BlockContext, type BlockHandler, BlockHandlerRegistry, type BlockRenderOptions, type BoxBlockData, type BoxFloat, type BoxOpAttributes, type BoxOverflow, BrowserDOMAdapter, type CellAlign, type CellData, type ColumnsBlockData, type DOMAdapter, type DOMDocument, type DOMDocumentFragment, type DOMElement, type DOMNode, type DOMNodeList, type DeltaToHtmlOptions, type DeltaToMarkdownOptions, type DocumentPresentation, type FootnotesBlockData, type Format, type FormatDefinition, type FormatMatchResult, type FormatScope, type HtmlToDeltaOptions, LINE_HEIGHT_BLOCK_TAGS, type ListType, type MarkdownToDeltaOptions, NODE_TYPE, NodeDOMAdapter, PARAGRAPH_SPACING_BLOCK_TAGS, Registry, type ResolvedDocumentPresentation, type ResolvedTablePresentation, SCRIDER_LINE_HEIGHT_KEY, SCRIDER_MARGIN_AFTER_KEY, SCRIDER_MARGIN_BEFORE_KEY, type SanitizeOptions, type TableBlockData, type TableCellAlign, type TableColAlignType, type TablePresentation, type TableRegion, alertBlockHandler, alignFormat, backgroundFormat, blockFormat, blockLineHeightStyleParts, blockMarginAfterStyleParts, blockMarginBeforeStyleParts, blockParagraphMarginStyleParts, blockPresentationStyleParts, blockquoteFormat, boldFormat, boxBlockHandler, browserAdapter, cloneDelta, codeBlockFormat, codeFormat, codeWidgetFormat, colorFormat, columnsBlockHandler, createDefaultBlockHandlers, createDefaultRegistry, defaultBlockFormats, defaultEmbedFormats, defaultFormats, defaultInlineFormats, deltaToHtml, deltaToMarkdown, dividerFormat, documentPresentationStyleParts, escapeHtml, extractBoxOpAttributes, extractTableRegion, fontFormat, footnoteRefFormat, footnotesBlockHandler, formulaFormat, getAdapter, getNamedColors, headerFormat, headerIdFormat, htmlToDelta, imageFormat, indentFormat, isAdapterAvailable, isElement, isRemarkAvailable, isTableNewlineOp, isTextNode, isValidColor, isValidHexColor, isZebraBodyRow, italicFormat, kbdFormat, linkFormat, listFormat, markFormat, markdownToDelta, markdownToDeltaSync, nodeAdapter, normalizeDelta, parseScriderLineHeightMultiplier, parseScriderMarginAfterEm, parseScriderMarginBeforeEm, parseScriderMarginEm, preloadRemark, resolveDocumentPresentation, resolveTablePresentation, sanitizeDelta, sizeFormat, slugify, slugifyWithDedup, softBreakFormat, strikeFormat, subscriptFormat, superscriptFormat, tableBlockHandler, tableColAlignFormat, tableColFormat, tableHeaderFormat, tableRowFormat, toCodeWidgetEmbedUrl, toHexColor, underlineFormat, unescapeHtml, validateDelta, videoFormat };
package/dist/index.js CHANGED
@@ -1897,6 +1897,25 @@ var EMBED_RENDERERS = {
1897
1897
  }
1898
1898
  return `<video src="${escapeHtml(src)}" controls${float}${style}></video>`;
1899
1899
  },
1900
+ codeWidget: (value, attrs) => {
1901
+ const src = typeof value === "string" ? value : "";
1902
+ const floatVal = attrs?.float;
1903
+ const widthVal = attrs?.width;
1904
+ const heightVal = attrs?.height;
1905
+ const float = floatVal != null && typeof floatVal === "string" && floatVal !== "none" ? ` data-float="${escapeHtml(floatVal)}"` : "";
1906
+ const styles = [];
1907
+ if (widthVal != null && (typeof widthVal === "string" || typeof widthVal === "number")) {
1908
+ const w = String(widthVal);
1909
+ if (w && w !== "auto") styles.push(`width: ${/^\d+$/.test(w) ? w + "px" : w}`);
1910
+ }
1911
+ if (heightVal != null && (typeof heightVal === "string" || typeof heightVal === "number")) {
1912
+ const h = String(heightVal);
1913
+ if (h && h !== "auto") styles.push(`height: ${/^\d+$/.test(h) ? h + "px" : h}`);
1914
+ }
1915
+ const style = styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
1916
+ const embedSrc = toCodeWidgetEmbedUrl(src);
1917
+ return `<iframe data-code-widget src="${escapeHtml(embedSrc)}" frameborder="0" allowfullscreen${float}${style}></iframe>`;
1918
+ },
1900
1919
  formula: (value) => {
1901
1920
  const latex = typeof value === "string" ? value : "";
1902
1921
  return `<span class="formula" data-formula="${escapeHtml(latex)}">${escapeHtml(latex)}</span>`;
@@ -1990,6 +2009,124 @@ function fromVideoEmbedUrl(embedUrl) {
1990
2009
  }
1991
2010
  return embedUrl;
1992
2011
  }
2012
+ function splitUrl(url) {
2013
+ let rest = url;
2014
+ let hash = "";
2015
+ const hashIdx = rest.indexOf("#");
2016
+ if (hashIdx >= 0) {
2017
+ hash = rest.slice(hashIdx);
2018
+ rest = rest.slice(0, hashIdx);
2019
+ }
2020
+ let query = "";
2021
+ const qIdx = rest.indexOf("?");
2022
+ if (qIdx >= 0) {
2023
+ query = rest.slice(qIdx);
2024
+ rest = rest.slice(0, qIdx);
2025
+ }
2026
+ return { base: rest, query, hash };
2027
+ }
2028
+ function hasQueryParam(url, key) {
2029
+ const { query } = splitUrl(url);
2030
+ return new RegExp(`[?&]${key}=`, "i").test(query);
2031
+ }
2032
+ function appendQueryParam(url, key, value) {
2033
+ const { base, query, hash } = splitUrl(url);
2034
+ const next = query ? `${query}&${key}=${value}` : `?${key}=${value}`;
2035
+ return `${base}${next}${hash}`;
2036
+ }
2037
+ function toCodeWidgetEmbedUrl(url) {
2038
+ const u = typeof url === "string" ? url.trim() : "";
2039
+ if (!u) return "";
2040
+ if (/(?:\/\/|^)(?:[\w-]+\.)*stackblitz\.com\//i.test(u)) {
2041
+ return hasQueryParam(u, "embed") ? u : appendQueryParam(u, "embed", "1");
2042
+ }
2043
+ if (/(?:\/\/|^)(?:[\w-]+\.)*codesandbox\.io\//i.test(u)) {
2044
+ if (/codesandbox\.io\/embed\//i.test(u)) return u;
2045
+ return u.replace(/codesandbox\.io\/s\//i, "codesandbox.io/embed/");
2046
+ }
2047
+ if (/(?:\/\/|^)(?:[\w-]+\.)*replit\.com\//i.test(u)) {
2048
+ return hasQueryParam(u, "embed") ? u : appendQueryParam(u, "embed", "true");
2049
+ }
2050
+ if (/(?:\/\/|^)(?:[\w-]+\.)*codepen\.io\//i.test(u)) {
2051
+ if (/codepen\.io\/[^/]+\/embed\//i.test(u)) return u;
2052
+ return u.replace(/(codepen\.io\/[^/]+)\/pen\//i, "$1/embed/");
2053
+ }
2054
+ if (/(?:\/\/|^)(?:[\w-]+\.)*jsfiddle\.net\//i.test(u)) {
2055
+ const { base, query, hash } = splitUrl(u);
2056
+ if (/\/embedded(?:\/|$)/i.test(base)) return u;
2057
+ const trimmed = base.replace(/\/+$/, "");
2058
+ return `${trimmed}/embedded/${query}${hash}`;
2059
+ }
2060
+ return u;
2061
+ }
2062
+
2063
+ // src/schema/formats/embed/codeWidget.ts
2064
+ var codeWidgetFormat = {
2065
+ name: "codeWidget",
2066
+ scope: "embed",
2067
+ normalize(value) {
2068
+ return typeof value === "string" ? value.trim() : value;
2069
+ },
2070
+ validate(value) {
2071
+ if (typeof value !== "string" || value.length === 0) {
2072
+ return false;
2073
+ }
2074
+ const trimmed = value.trim();
2075
+ if (trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../")) {
2076
+ return true;
2077
+ }
2078
+ if (trimmed.startsWith("//")) {
2079
+ return true;
2080
+ }
2081
+ try {
2082
+ const url = new URL(trimmed);
2083
+ return url.protocol === "http:" || url.protocol === "https:";
2084
+ } catch {
2085
+ return false;
2086
+ }
2087
+ },
2088
+ render(value, attributes) {
2089
+ const src = typeof value === "string" ? value : "";
2090
+ const floatVal = attributes?.float;
2091
+ const widthVal = attributes?.width;
2092
+ const heightVal = attributes?.height;
2093
+ const float = floatVal != null && typeof floatVal === "string" && floatVal !== "none" ? ` data-float="${escapeHtml(floatVal)}"` : "";
2094
+ const styles = [];
2095
+ if (widthVal != null && (typeof widthVal === "string" || typeof widthVal === "number")) {
2096
+ const w = String(widthVal);
2097
+ if (w && w !== "auto") styles.push(`width: ${/^\d+$/.test(w) ? w + "px" : w}`);
2098
+ }
2099
+ if (heightVal != null && (typeof heightVal === "string" || typeof heightVal === "number")) {
2100
+ const h = String(heightVal);
2101
+ if (h && h !== "auto") styles.push(`height: ${/^\d+$/.test(h) ? h + "px" : h}`);
2102
+ }
2103
+ const style = styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
2104
+ const embedSrc = toCodeWidgetEmbedUrl(src);
2105
+ return `<iframe data-code-widget src="${escapeHtml(embedSrc)}" frameborder="0" allowfullscreen${float}${style}></iframe>`;
2106
+ },
2107
+ match(element) {
2108
+ if (element.tagName.toLowerCase() !== "iframe") return null;
2109
+ if (element.getAttribute("data-code-widget") === null) return null;
2110
+ const src = element.getAttribute("src");
2111
+ if (!src) return null;
2112
+ const attrs = {};
2113
+ const float = element.getAttribute("data-float");
2114
+ const styleAttr = element.getAttribute("style") || "";
2115
+ if (float) attrs.float = float;
2116
+ const widthMatch = styleAttr.match(/(?:^|;\s*)width:\s*([^;]+)/);
2117
+ if (widthMatch?.[1]) attrs.width = widthMatch[1].trim().replace(/px$/, "");
2118
+ const heightMatch = styleAttr.match(/(?:^|;\s*)height:\s*([^;]+)/);
2119
+ if (heightMatch?.[1]) attrs.height = heightMatch[1].trim().replace(/px$/, "");
2120
+ if (Object.keys(attrs).length > 0) {
2121
+ return { value: src, attributes: attrs };
2122
+ }
2123
+ return { value: src };
2124
+ },
2125
+ toMarkdown(value) {
2126
+ const src = typeof value === "string" ? value : "";
2127
+ return `![Widget](${src})`;
2128
+ }
2129
+ };
1993
2130
 
1994
2131
  // src/schema/formats/embed/divider.ts
1995
2132
  var dividerFormat = {
@@ -2234,6 +2371,7 @@ var videoFormat = {
2234
2371
  return `<video src="${escapeHtml(src)}" controls${float}${style}></video>`;
2235
2372
  },
2236
2373
  match(element) {
2374
+ if (element.getAttribute("data-code-widget") !== null) return null;
2237
2375
  const tagName = element.tagName.toLowerCase();
2238
2376
  if (tagName !== "video" && tagName !== "iframe") return null;
2239
2377
  const src = element.getAttribute("src");
@@ -2286,6 +2424,7 @@ var defaultBlockFormats = [
2286
2424
  var defaultEmbedFormats = [
2287
2425
  imageFormat,
2288
2426
  videoFormat,
2427
+ codeWidgetFormat,
2289
2428
  formulaFormat,
2290
2429
  dividerFormat,
2291
2430
  softBreakFormat,
@@ -2732,7 +2871,16 @@ function deltaToHtml(delta, options = {}) {
2732
2871
  counters = [];
2733
2872
  const codeLines = collectCodeBlockLines(lines, i);
2734
2873
  const language = getCodeBlockLanguage(line.attributes);
2735
- html += renderCodeBlock(codeLines, language, embedRenderers, pretty, blockHandlers, options);
2874
+ const codeBlockId = getCodeBlockId(line.attributes);
2875
+ html += renderCodeBlock(
2876
+ codeLines,
2877
+ language,
2878
+ embedRenderers,
2879
+ pretty,
2880
+ blockHandlers,
2881
+ options,
2882
+ codeBlockId
2883
+ );
2736
2884
  i += codeLines.length - 1;
2737
2885
  continue;
2738
2886
  }
@@ -3003,11 +3151,13 @@ function collectCodeBlockLines(lines, startIndex) {
3003
3151
  const startLine = lines[startIndex];
3004
3152
  if (!startLine) return codeLines;
3005
3153
  const startLang = getCodeBlockLanguage(startLine.attributes);
3154
+ const startId = getCodeBlockId(startLine.attributes);
3006
3155
  for (let i = startIndex; i < lines.length; i++) {
3007
3156
  const line = lines[i];
3008
3157
  if (!line || !line.attributes?.["code-block"]) break;
3009
3158
  const lang = getCodeBlockLanguage(line.attributes);
3010
- if (i > startIndex && lang !== startLang) break;
3159
+ const id = getCodeBlockId(line.attributes);
3160
+ if (i > startIndex && (lang !== startLang || id !== startId)) break;
3011
3161
  codeLines.push(line);
3012
3162
  }
3013
3163
  return codeLines;
@@ -3020,14 +3170,20 @@ function getCodeBlockLanguage(attributes) {
3020
3170
  }
3021
3171
  return void 0;
3022
3172
  }
3023
- function renderCodeBlock(codeLines, language, embedRenderers, pretty, blockHandlers, options) {
3173
+ function getCodeBlockId(attributes) {
3174
+ if (!attributes) return void 0;
3175
+ const id = attributes["code-block-id"];
3176
+ return typeof id === "string" ? id : void 0;
3177
+ }
3178
+ function renderCodeBlock(codeLines, language, embedRenderers, pretty, blockHandlers, options, codeBlockId) {
3024
3179
  const lineContents = codeLines.map(
3025
3180
  (line) => renderLineContent(line.ops, embedRenderers, blockHandlers, options)
3026
3181
  );
3027
3182
  const code = lineContents.join("\n");
3028
3183
  const langClass = language ? ` class="language-${escapeHtml(language)}"` : "";
3029
3184
  const langAttr = language ? ` data-language="${escapeHtml(language)}"` : "";
3030
- const html = `<pre${langAttr}><code${langClass}>${code}
3185
+ const idAttr = codeBlockId ? ` data-code-block-id="${escapeHtml(codeBlockId)}"` : "";
3186
+ const html = `<pre${langAttr}${idAttr}><code${langClass}>${code}
3031
3187
  </code></pre>`;
3032
3188
  return pretty ? html + "\n" : html;
3033
3189
  }
@@ -3241,6 +3397,7 @@ function htmlToDelta(html, options = {}) {
3241
3397
  let currentBlockAttributes = {};
3242
3398
  let pendingText = "";
3243
3399
  let atLineStart = true;
3400
+ let codeBlockIdSeq = 0;
3244
3401
  const context = {
3245
3402
  delta,
3246
3403
  get attributes() {
@@ -3453,6 +3610,8 @@ function htmlToDelta(html, options = {}) {
3453
3610
  }
3454
3611
  const codeBlockValue = language || true;
3455
3612
  currentBlockAttributes["code-block"] = codeBlockValue;
3613
+ const existingId = element.getAttribute("data-code-block-id");
3614
+ currentBlockAttributes["code-block-id"] = existingId || `cb-${++codeBlockIdSeq}`;
3456
3615
  const sourceElement = codeElement || element;
3457
3616
  const rawText = sourceElement.textContent ?? "";
3458
3617
  const text = rawText.endsWith("\n") ? rawText.slice(0, -1) : rawText;
@@ -3608,6 +3767,10 @@ function htmlToDelta(html, options = {}) {
3608
3767
  const heightMatch = style.match(/(?:^|;\s*)height:\s*([^;]+)/);
3609
3768
  if (heightMatch?.[1]) attrs.height = heightMatch[1].trim().replace(/px$/, "");
3610
3769
  const embedAttrs = Object.keys(attrs).length > 0 ? attrs : void 0;
3770
+ if (element.getAttribute("data-code-widget") !== null) {
3771
+ context.pushEmbed({ codeWidget: src }, embedAttrs);
3772
+ return;
3773
+ }
3611
3774
  context.pushEmbed({ video: fromVideoEmbedUrl(src) }, embedAttrs);
3612
3775
  }
3613
3776
  function processFootnotesSection(section) {
@@ -4188,11 +4351,13 @@ function collectCodeBlock(lines, startIndex) {
4188
4351
  const startLine = lines[startIndex];
4189
4352
  if (!startLine) return codeLines;
4190
4353
  const startLang = getCodeBlockLanguage2(startLine.attributes);
4354
+ const startId = getCodeBlockId2(startLine.attributes);
4191
4355
  for (let i = startIndex; i < lines.length; i++) {
4192
4356
  const line = lines[i];
4193
4357
  if (!line || !line.attributes["code-block"]) break;
4194
4358
  const lang = getCodeBlockLanguage2(line.attributes);
4195
- if (i > startIndex && lang !== startLang) break;
4359
+ const id = getCodeBlockId2(line.attributes);
4360
+ if (i > startIndex && (lang !== startLang || id !== startId)) break;
4196
4361
  codeLines.push(line);
4197
4362
  }
4198
4363
  return codeLines;
@@ -4310,6 +4475,10 @@ function getCodeBlockLanguage2(attributes) {
4310
4475
  }
4311
4476
  return void 0;
4312
4477
  }
4478
+ function getCodeBlockId2(attributes) {
4479
+ const id = attributes["code-block-id"];
4480
+ return typeof id === "string" ? id : void 0;
4481
+ }
4313
4482
  function renderLineContent2(ops, embedRenderers, inCodeBlock, useLatexDelimiters = false, blockHandlers, prettyHtml = false, registry, softBreakStyle = "spaces", inTableCell = false) {
4314
4483
  let result = "";
4315
4484
  for (const op of ops) {
@@ -4459,6 +4628,28 @@ function renderEmbed2(embed, attributes, customRenderers, useLatexDelimiters = f
4459
4628
  }
4460
4629
  return `![Video](${src})`;
4461
4630
  }
4631
+ if (embedType === "codeWidget") {
4632
+ const src = typeof embedValue === "string" ? embedValue : "";
4633
+ const hasFloat = attributes?.float != null && typeof attributes.float === "string" && attributes.float !== "none";
4634
+ const hasWidth = attributes?.width != null;
4635
+ const hasHeight = attributes?.height != null;
4636
+ if (hasFloat || hasWidth || hasHeight) {
4637
+ const floatAttr = hasFloat ? ` data-float="${escapeHtml(String(attributes.float))}"` : "";
4638
+ const styles = [];
4639
+ if (hasWidth) {
4640
+ const w = typeof attributes.width === "string" || typeof attributes.width === "number" ? String(attributes.width) : "";
4641
+ if (w && w !== "auto") styles.push(`width: ${/^\d+$/.test(w) ? w + "px" : w}`);
4642
+ }
4643
+ if (hasHeight) {
4644
+ const h = typeof attributes.height === "string" || typeof attributes.height === "number" ? String(attributes.height) : "";
4645
+ if (h && h !== "auto") styles.push(`height: ${/^\d+$/.test(h) ? h + "px" : h}`);
4646
+ }
4647
+ const styleAttr = styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
4648
+ const embedSrc = toCodeWidgetEmbedUrl(src);
4649
+ return `<iframe data-code-widget src="${escapeHtml(embedSrc)}" frameborder="0" allowfullscreen${floatAttr}${styleAttr}></iframe>`;
4650
+ }
4651
+ return `![Widget](${src})`;
4652
+ }
4462
4653
  if (embedType === "formula") {
4463
4654
  const latex = typeof embedValue === "string" ? embedValue : "";
4464
4655
  return useLatexDelimiters ? `\\(${latex}\\)` : `$${latex}$`;
@@ -4663,6 +4854,7 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
4663
4854
  let pendingText = "";
4664
4855
  const spanAttrStack = [];
4665
4856
  const footnoteDefinitions = /* @__PURE__ */ new Map();
4857
+ let codeBlockIdSeq = 0;
4666
4858
  const context = {
4667
4859
  delta,
4668
4860
  pushText(text, attrs) {
@@ -4924,6 +5116,10 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
4924
5116
  context.pushEmbed({ video: url });
4925
5117
  return;
4926
5118
  }
5119
+ if (alt.toLowerCase() === "widget") {
5120
+ context.pushEmbed({ codeWidget: url });
5121
+ return;
5122
+ }
4927
5123
  const attrs = {};
4928
5124
  if (node.alt) attrs.alt = node.alt;
4929
5125
  if (url.toLowerCase().endsWith(".drawio")) {
@@ -4978,7 +5174,8 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
4978
5174
  }
4979
5175
  const lines = code.split("\n");
4980
5176
  const codeBlockAttr = {
4981
- "code-block": lang ?? true
5177
+ "code-block": lang ?? true,
5178
+ "code-block-id": `cb-${++codeBlockIdSeq}`
4982
5179
  };
4983
5180
  for (const line of lines) {
4984
5181
  context.pushText(line);
@@ -4992,7 +5189,10 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
4992
5189
  context.pushNewline();
4993
5190
  } else {
4994
5191
  const lines = value.split("\n");
4995
- const mathBlockAttr = { "code-block": "math" };
5192
+ const mathBlockAttr = {
5193
+ "code-block": "math",
5194
+ "code-block-id": `cb-${++codeBlockIdSeq}`
5195
+ };
4996
5196
  for (const line of lines) {
4997
5197
  context.pushText(line);
4998
5198
  context.pushNewline(mathBlockAttr);
@@ -5286,6 +5486,7 @@ export {
5286
5486
  cloneDelta,
5287
5487
  codeBlockFormat,
5288
5488
  codeFormat,
5489
+ codeWidgetFormat,
5289
5490
  colorFormat,
5290
5491
  columnsBlockHandler,
5291
5492
  createDefaultBlockHandlers,
@@ -5349,6 +5550,7 @@ export {
5349
5550
  tableColFormat,
5350
5551
  tableHeaderFormat,
5351
5552
  tableRowFormat,
5553
+ toCodeWidgetEmbedUrl,
5352
5554
  toHexColor,
5353
5555
  underlineFormat,
5354
5556
  unescapeHtml,