@scrider/formatter 1.5.0 → 1.6.1

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.cjs CHANGED
@@ -60,6 +60,7 @@ __export(index_exports, {
60
60
  cloneDelta: () => cloneDelta,
61
61
  codeBlockFormat: () => codeBlockFormat,
62
62
  codeFormat: () => codeFormat,
63
+ codeWidgetFormat: () => codeWidgetFormat,
63
64
  colorFormat: () => colorFormat,
64
65
  columnsBlockHandler: () => columnsBlockHandler,
65
66
  createDefaultBlockHandlers: () => createDefaultBlockHandlers,
@@ -123,6 +124,7 @@ __export(index_exports, {
123
124
  tableColFormat: () => tableColFormat,
124
125
  tableHeaderFormat: () => tableHeaderFormat,
125
126
  tableRowFormat: () => tableRowFormat,
127
+ toCodeWidgetEmbedUrl: () => toCodeWidgetEmbedUrl,
126
128
  toHexColor: () => toHexColor,
127
129
  underlineFormat: () => underlineFormat,
128
130
  unescapeHtml: () => unescapeHtml,
@@ -2021,6 +2023,25 @@ var EMBED_RENDERERS = {
2021
2023
  }
2022
2024
  return `<video src="${escapeHtml(src)}" controls${float}${style}></video>`;
2023
2025
  },
2026
+ codeWidget: (value, attrs) => {
2027
+ const src = typeof value === "string" ? value : "";
2028
+ const floatVal = attrs?.float;
2029
+ const widthVal = attrs?.width;
2030
+ const heightVal = attrs?.height;
2031
+ const float = floatVal != null && typeof floatVal === "string" && floatVal !== "none" ? ` data-float="${escapeHtml(floatVal)}"` : "";
2032
+ const styles = [];
2033
+ if (widthVal != null && (typeof widthVal === "string" || typeof widthVal === "number")) {
2034
+ const w = String(widthVal);
2035
+ if (w && w !== "auto") styles.push(`width: ${/^\d+$/.test(w) ? w + "px" : w}`);
2036
+ }
2037
+ if (heightVal != null && (typeof heightVal === "string" || typeof heightVal === "number")) {
2038
+ const h = String(heightVal);
2039
+ if (h && h !== "auto") styles.push(`height: ${/^\d+$/.test(h) ? h + "px" : h}`);
2040
+ }
2041
+ const style = styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
2042
+ const embedSrc = toCodeWidgetEmbedUrl(src);
2043
+ return `<iframe data-code-widget src="${escapeHtml(embedSrc)}" frameborder="0" allowfullscreen allow="${CODE_WIDGET_IFRAME_ALLOW}"${float}${style}></iframe>`;
2044
+ },
2024
2045
  formula: (value) => {
2025
2046
  const latex = typeof value === "string" ? value : "";
2026
2047
  return `<span class="formula" data-formula="${escapeHtml(latex)}">${escapeHtml(latex)}</span>`;
@@ -2114,6 +2135,125 @@ function fromVideoEmbedUrl(embedUrl) {
2114
2135
  }
2115
2136
  return embedUrl;
2116
2137
  }
2138
+ function splitUrl(url) {
2139
+ let rest = url;
2140
+ let hash = "";
2141
+ const hashIdx = rest.indexOf("#");
2142
+ if (hashIdx >= 0) {
2143
+ hash = rest.slice(hashIdx);
2144
+ rest = rest.slice(0, hashIdx);
2145
+ }
2146
+ let query = "";
2147
+ const qIdx = rest.indexOf("?");
2148
+ if (qIdx >= 0) {
2149
+ query = rest.slice(qIdx);
2150
+ rest = rest.slice(0, qIdx);
2151
+ }
2152
+ return { base: rest, query, hash };
2153
+ }
2154
+ function hasQueryParam(url, key) {
2155
+ const { query } = splitUrl(url);
2156
+ return new RegExp(`[?&]${key}=`, "i").test(query);
2157
+ }
2158
+ function appendQueryParam(url, key, value) {
2159
+ const { base, query, hash } = splitUrl(url);
2160
+ const next = query ? `${query}&${key}=${value}` : `?${key}=${value}`;
2161
+ return `${base}${next}${hash}`;
2162
+ }
2163
+ var CODE_WIDGET_IFRAME_ALLOW = "accelerometer; camera; encrypted-media; geolocation; gyroscope; microphone; midi; payment; usb; vr; xr-spatial-tracking; cross-origin-isolated";
2164
+ function toCodeWidgetEmbedUrl(url) {
2165
+ const u = typeof url === "string" ? url.trim() : "";
2166
+ if (!u) return "";
2167
+ if (/(?:\/\/|^)(?:[\w-]+\.)*stackblitz\.com\//i.test(u)) {
2168
+ return hasQueryParam(u, "embed") ? u : appendQueryParam(u, "embed", "1");
2169
+ }
2170
+ if (/(?:\/\/|^)(?:[\w-]+\.)*codesandbox\.io\//i.test(u)) {
2171
+ if (/codesandbox\.io\/embed\//i.test(u)) return u;
2172
+ return u.replace(/codesandbox\.io\/s\//i, "codesandbox.io/embed/");
2173
+ }
2174
+ if (/(?:\/\/|^)(?:[\w-]+\.)*replit\.com\//i.test(u)) {
2175
+ return hasQueryParam(u, "embed") ? u : appendQueryParam(u, "embed", "true");
2176
+ }
2177
+ if (/(?:\/\/|^)(?:[\w-]+\.)*codepen\.io\//i.test(u)) {
2178
+ if (/codepen\.io\/[^/]+\/embed\//i.test(u)) return u;
2179
+ return u.replace(/(codepen\.io\/[^/]+)\/pen\//i, "$1/embed/");
2180
+ }
2181
+ if (/(?:\/\/|^)(?:[\w-]+\.)*jsfiddle\.net\//i.test(u)) {
2182
+ const { base, query, hash } = splitUrl(u);
2183
+ if (/\/embedded(?:\/|$)/i.test(base)) return u;
2184
+ const trimmed = base.replace(/\/+$/, "");
2185
+ return `${trimmed}/embedded/${query}${hash}`;
2186
+ }
2187
+ return u;
2188
+ }
2189
+
2190
+ // src/schema/formats/embed/codeWidget.ts
2191
+ var codeWidgetFormat = {
2192
+ name: "codeWidget",
2193
+ scope: "embed",
2194
+ normalize(value) {
2195
+ return typeof value === "string" ? value.trim() : value;
2196
+ },
2197
+ validate(value) {
2198
+ if (typeof value !== "string" || value.length === 0) {
2199
+ return false;
2200
+ }
2201
+ const trimmed = value.trim();
2202
+ if (trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../")) {
2203
+ return true;
2204
+ }
2205
+ if (trimmed.startsWith("//")) {
2206
+ return true;
2207
+ }
2208
+ try {
2209
+ const url = new URL(trimmed);
2210
+ return url.protocol === "http:" || url.protocol === "https:";
2211
+ } catch {
2212
+ return false;
2213
+ }
2214
+ },
2215
+ render(value, attributes) {
2216
+ const src = typeof value === "string" ? value : "";
2217
+ const floatVal = attributes?.float;
2218
+ const widthVal = attributes?.width;
2219
+ const heightVal = attributes?.height;
2220
+ const float = floatVal != null && typeof floatVal === "string" && floatVal !== "none" ? ` data-float="${escapeHtml(floatVal)}"` : "";
2221
+ const styles = [];
2222
+ if (widthVal != null && (typeof widthVal === "string" || typeof widthVal === "number")) {
2223
+ const w = String(widthVal);
2224
+ if (w && w !== "auto") styles.push(`width: ${/^\d+$/.test(w) ? w + "px" : w}`);
2225
+ }
2226
+ if (heightVal != null && (typeof heightVal === "string" || typeof heightVal === "number")) {
2227
+ const h = String(heightVal);
2228
+ if (h && h !== "auto") styles.push(`height: ${/^\d+$/.test(h) ? h + "px" : h}`);
2229
+ }
2230
+ const style = styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
2231
+ const embedSrc = toCodeWidgetEmbedUrl(src);
2232
+ return `<iframe data-code-widget src="${escapeHtml(embedSrc)}" frameborder="0" allowfullscreen allow="${CODE_WIDGET_IFRAME_ALLOW}"${float}${style}></iframe>`;
2233
+ },
2234
+ match(element) {
2235
+ if (element.tagName.toLowerCase() !== "iframe") return null;
2236
+ if (element.getAttribute("data-code-widget") === null) return null;
2237
+ const src = element.getAttribute("src");
2238
+ if (!src) return null;
2239
+ const attrs = {};
2240
+ const float = element.getAttribute("data-float");
2241
+ const styleAttr = element.getAttribute("style") || "";
2242
+ if (float) attrs.float = float;
2243
+ const widthMatch = styleAttr.match(/(?:^|;\s*)width:\s*([^;]+)/);
2244
+ if (widthMatch?.[1]) attrs.width = widthMatch[1].trim().replace(/px$/, "");
2245
+ const heightMatch = styleAttr.match(/(?:^|;\s*)height:\s*([^;]+)/);
2246
+ if (heightMatch?.[1]) attrs.height = heightMatch[1].trim().replace(/px$/, "");
2247
+ if (Object.keys(attrs).length > 0) {
2248
+ return { value: src, attributes: attrs };
2249
+ }
2250
+ return { value: src };
2251
+ },
2252
+ toMarkdown(value) {
2253
+ const src = typeof value === "string" ? value : "";
2254
+ return `![Widget](${src})`;
2255
+ }
2256
+ };
2117
2257
 
2118
2258
  // src/schema/formats/embed/divider.ts
2119
2259
  var dividerFormat = {
@@ -2358,6 +2498,7 @@ var videoFormat = {
2358
2498
  return `<video src="${escapeHtml(src)}" controls${float}${style}></video>`;
2359
2499
  },
2360
2500
  match(element) {
2501
+ if (element.getAttribute("data-code-widget") !== null) return null;
2361
2502
  const tagName = element.tagName.toLowerCase();
2362
2503
  if (tagName !== "video" && tagName !== "iframe") return null;
2363
2504
  const src = element.getAttribute("src");
@@ -2410,6 +2551,7 @@ var defaultBlockFormats = [
2410
2551
  var defaultEmbedFormats = [
2411
2552
  imageFormat,
2412
2553
  videoFormat,
2554
+ codeWidgetFormat,
2413
2555
  formulaFormat,
2414
2556
  dividerFormat,
2415
2557
  softBreakFormat,
@@ -3752,6 +3894,10 @@ function htmlToDelta(html, options = {}) {
3752
3894
  const heightMatch = style.match(/(?:^|;\s*)height:\s*([^;]+)/);
3753
3895
  if (heightMatch?.[1]) attrs.height = heightMatch[1].trim().replace(/px$/, "");
3754
3896
  const embedAttrs = Object.keys(attrs).length > 0 ? attrs : void 0;
3897
+ if (element.getAttribute("data-code-widget") !== null) {
3898
+ context.pushEmbed({ codeWidget: src }, embedAttrs);
3899
+ return;
3900
+ }
3755
3901
  context.pushEmbed({ video: fromVideoEmbedUrl(src) }, embedAttrs);
3756
3902
  }
3757
3903
  function processFootnotesSection(section) {
@@ -4609,6 +4755,28 @@ function renderEmbed2(embed, attributes, customRenderers, useLatexDelimiters = f
4609
4755
  }
4610
4756
  return `![Video](${src})`;
4611
4757
  }
4758
+ if (embedType === "codeWidget") {
4759
+ const src = typeof embedValue === "string" ? embedValue : "";
4760
+ const hasFloat = attributes?.float != null && typeof attributes.float === "string" && attributes.float !== "none";
4761
+ const hasWidth = attributes?.width != null;
4762
+ const hasHeight = attributes?.height != null;
4763
+ if (hasFloat || hasWidth || hasHeight) {
4764
+ const floatAttr = hasFloat ? ` data-float="${escapeHtml(String(attributes.float))}"` : "";
4765
+ const styles = [];
4766
+ if (hasWidth) {
4767
+ const w = typeof attributes.width === "string" || typeof attributes.width === "number" ? String(attributes.width) : "";
4768
+ if (w && w !== "auto") styles.push(`width: ${/^\d+$/.test(w) ? w + "px" : w}`);
4769
+ }
4770
+ if (hasHeight) {
4771
+ const h = typeof attributes.height === "string" || typeof attributes.height === "number" ? String(attributes.height) : "";
4772
+ if (h && h !== "auto") styles.push(`height: ${/^\d+$/.test(h) ? h + "px" : h}`);
4773
+ }
4774
+ const styleAttr = styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
4775
+ const embedSrc = toCodeWidgetEmbedUrl(src);
4776
+ return `<iframe data-code-widget src="${escapeHtml(embedSrc)}" frameborder="0" allowfullscreen${floatAttr}${styleAttr}></iframe>`;
4777
+ }
4778
+ return `![Widget](${src})`;
4779
+ }
4612
4780
  if (embedType === "formula") {
4613
4781
  const latex = typeof embedValue === "string" ? embedValue : "";
4614
4782
  return useLatexDelimiters ? `\\(${latex}\\)` : `$${latex}$`;
@@ -5075,6 +5243,10 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
5075
5243
  context.pushEmbed({ video: url });
5076
5244
  return;
5077
5245
  }
5246
+ if (alt.toLowerCase() === "widget") {
5247
+ context.pushEmbed({ codeWidget: url });
5248
+ return;
5249
+ }
5078
5250
  const attrs = {};
5079
5251
  if (node.alt) attrs.alt = node.alt;
5080
5252
  if (url.toLowerCase().endsWith(".drawio")) {
@@ -5442,6 +5614,7 @@ function extractTableRegion(ops, hintOpIdx) {
5442
5614
  cloneDelta,
5443
5615
  codeBlockFormat,
5444
5616
  codeFormat,
5617
+ codeWidgetFormat,
5445
5618
  colorFormat,
5446
5619
  columnsBlockHandler,
5447
5620
  createDefaultBlockHandlers,
@@ -5505,6 +5678,7 @@ function extractTableRegion(ops, hintOpIdx) {
5505
5678
  tableColFormat,
5506
5679
  tableHeaderFormat,
5507
5680
  tableRowFormat,
5681
+ toCodeWidgetEmbedUrl,
5508
5682
  toHexColor,
5509
5683
  underlineFormat,
5510
5684
  unescapeHtml,