@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.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${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,124 @@ 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
+ function toCodeWidgetEmbedUrl(url) {
2164
+ const u = typeof url === "string" ? url.trim() : "";
2165
+ if (!u) return "";
2166
+ if (/(?:\/\/|^)(?:[\w-]+\.)*stackblitz\.com\//i.test(u)) {
2167
+ return hasQueryParam(u, "embed") ? u : appendQueryParam(u, "embed", "1");
2168
+ }
2169
+ if (/(?:\/\/|^)(?:[\w-]+\.)*codesandbox\.io\//i.test(u)) {
2170
+ if (/codesandbox\.io\/embed\//i.test(u)) return u;
2171
+ return u.replace(/codesandbox\.io\/s\//i, "codesandbox.io/embed/");
2172
+ }
2173
+ if (/(?:\/\/|^)(?:[\w-]+\.)*replit\.com\//i.test(u)) {
2174
+ return hasQueryParam(u, "embed") ? u : appendQueryParam(u, "embed", "true");
2175
+ }
2176
+ if (/(?:\/\/|^)(?:[\w-]+\.)*codepen\.io\//i.test(u)) {
2177
+ if (/codepen\.io\/[^/]+\/embed\//i.test(u)) return u;
2178
+ return u.replace(/(codepen\.io\/[^/]+)\/pen\//i, "$1/embed/");
2179
+ }
2180
+ if (/(?:\/\/|^)(?:[\w-]+\.)*jsfiddle\.net\//i.test(u)) {
2181
+ const { base, query, hash } = splitUrl(u);
2182
+ if (/\/embedded(?:\/|$)/i.test(base)) return u;
2183
+ const trimmed = base.replace(/\/+$/, "");
2184
+ return `${trimmed}/embedded/${query}${hash}`;
2185
+ }
2186
+ return u;
2187
+ }
2188
+
2189
+ // src/schema/formats/embed/codeWidget.ts
2190
+ var codeWidgetFormat = {
2191
+ name: "codeWidget",
2192
+ scope: "embed",
2193
+ normalize(value) {
2194
+ return typeof value === "string" ? value.trim() : value;
2195
+ },
2196
+ validate(value) {
2197
+ if (typeof value !== "string" || value.length === 0) {
2198
+ return false;
2199
+ }
2200
+ const trimmed = value.trim();
2201
+ if (trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../")) {
2202
+ return true;
2203
+ }
2204
+ if (trimmed.startsWith("//")) {
2205
+ return true;
2206
+ }
2207
+ try {
2208
+ const url = new URL(trimmed);
2209
+ return url.protocol === "http:" || url.protocol === "https:";
2210
+ } catch {
2211
+ return false;
2212
+ }
2213
+ },
2214
+ render(value, attributes) {
2215
+ const src = typeof value === "string" ? value : "";
2216
+ const floatVal = attributes?.float;
2217
+ const widthVal = attributes?.width;
2218
+ const heightVal = attributes?.height;
2219
+ const float = floatVal != null && typeof floatVal === "string" && floatVal !== "none" ? ` data-float="${escapeHtml(floatVal)}"` : "";
2220
+ const styles = [];
2221
+ if (widthVal != null && (typeof widthVal === "string" || typeof widthVal === "number")) {
2222
+ const w = String(widthVal);
2223
+ if (w && w !== "auto") styles.push(`width: ${/^\d+$/.test(w) ? w + "px" : w}`);
2224
+ }
2225
+ if (heightVal != null && (typeof heightVal === "string" || typeof heightVal === "number")) {
2226
+ const h = String(heightVal);
2227
+ if (h && h !== "auto") styles.push(`height: ${/^\d+$/.test(h) ? h + "px" : h}`);
2228
+ }
2229
+ const style = styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
2230
+ const embedSrc = toCodeWidgetEmbedUrl(src);
2231
+ return `<iframe data-code-widget src="${escapeHtml(embedSrc)}" frameborder="0" allowfullscreen${float}${style}></iframe>`;
2232
+ },
2233
+ match(element) {
2234
+ if (element.tagName.toLowerCase() !== "iframe") return null;
2235
+ if (element.getAttribute("data-code-widget") === null) return null;
2236
+ const src = element.getAttribute("src");
2237
+ if (!src) return null;
2238
+ const attrs = {};
2239
+ const float = element.getAttribute("data-float");
2240
+ const styleAttr = element.getAttribute("style") || "";
2241
+ if (float) attrs.float = float;
2242
+ const widthMatch = styleAttr.match(/(?:^|;\s*)width:\s*([^;]+)/);
2243
+ if (widthMatch?.[1]) attrs.width = widthMatch[1].trim().replace(/px$/, "");
2244
+ const heightMatch = styleAttr.match(/(?:^|;\s*)height:\s*([^;]+)/);
2245
+ if (heightMatch?.[1]) attrs.height = heightMatch[1].trim().replace(/px$/, "");
2246
+ if (Object.keys(attrs).length > 0) {
2247
+ return { value: src, attributes: attrs };
2248
+ }
2249
+ return { value: src };
2250
+ },
2251
+ toMarkdown(value) {
2252
+ const src = typeof value === "string" ? value : "";
2253
+ return `![Widget](${src})`;
2254
+ }
2255
+ };
2117
2256
 
2118
2257
  // src/schema/formats/embed/divider.ts
2119
2258
  var dividerFormat = {
@@ -2358,6 +2497,7 @@ var videoFormat = {
2358
2497
  return `<video src="${escapeHtml(src)}" controls${float}${style}></video>`;
2359
2498
  },
2360
2499
  match(element) {
2500
+ if (element.getAttribute("data-code-widget") !== null) return null;
2361
2501
  const tagName = element.tagName.toLowerCase();
2362
2502
  if (tagName !== "video" && tagName !== "iframe") return null;
2363
2503
  const src = element.getAttribute("src");
@@ -2410,6 +2550,7 @@ var defaultBlockFormats = [
2410
2550
  var defaultEmbedFormats = [
2411
2551
  imageFormat,
2412
2552
  videoFormat,
2553
+ codeWidgetFormat,
2413
2554
  formulaFormat,
2414
2555
  dividerFormat,
2415
2556
  softBreakFormat,
@@ -2856,7 +2997,16 @@ function deltaToHtml(delta, options = {}) {
2856
2997
  counters = [];
2857
2998
  const codeLines = collectCodeBlockLines(lines, i);
2858
2999
  const language = getCodeBlockLanguage(line.attributes);
2859
- html += renderCodeBlock(codeLines, language, embedRenderers, pretty, blockHandlers, options);
3000
+ const codeBlockId = getCodeBlockId(line.attributes);
3001
+ html += renderCodeBlock(
3002
+ codeLines,
3003
+ language,
3004
+ embedRenderers,
3005
+ pretty,
3006
+ blockHandlers,
3007
+ options,
3008
+ codeBlockId
3009
+ );
2860
3010
  i += codeLines.length - 1;
2861
3011
  continue;
2862
3012
  }
@@ -3127,11 +3277,13 @@ function collectCodeBlockLines(lines, startIndex) {
3127
3277
  const startLine = lines[startIndex];
3128
3278
  if (!startLine) return codeLines;
3129
3279
  const startLang = getCodeBlockLanguage(startLine.attributes);
3280
+ const startId = getCodeBlockId(startLine.attributes);
3130
3281
  for (let i = startIndex; i < lines.length; i++) {
3131
3282
  const line = lines[i];
3132
3283
  if (!line || !line.attributes?.["code-block"]) break;
3133
3284
  const lang = getCodeBlockLanguage(line.attributes);
3134
- if (i > startIndex && lang !== startLang) break;
3285
+ const id = getCodeBlockId(line.attributes);
3286
+ if (i > startIndex && (lang !== startLang || id !== startId)) break;
3135
3287
  codeLines.push(line);
3136
3288
  }
3137
3289
  return codeLines;
@@ -3144,14 +3296,20 @@ function getCodeBlockLanguage(attributes) {
3144
3296
  }
3145
3297
  return void 0;
3146
3298
  }
3147
- function renderCodeBlock(codeLines, language, embedRenderers, pretty, blockHandlers, options) {
3299
+ function getCodeBlockId(attributes) {
3300
+ if (!attributes) return void 0;
3301
+ const id = attributes["code-block-id"];
3302
+ return typeof id === "string" ? id : void 0;
3303
+ }
3304
+ function renderCodeBlock(codeLines, language, embedRenderers, pretty, blockHandlers, options, codeBlockId) {
3148
3305
  const lineContents = codeLines.map(
3149
3306
  (line) => renderLineContent(line.ops, embedRenderers, blockHandlers, options)
3150
3307
  );
3151
3308
  const code = lineContents.join("\n");
3152
3309
  const langClass = language ? ` class="language-${escapeHtml(language)}"` : "";
3153
3310
  const langAttr = language ? ` data-language="${escapeHtml(language)}"` : "";
3154
- const html = `<pre${langAttr}><code${langClass}>${code}
3311
+ const idAttr = codeBlockId ? ` data-code-block-id="${escapeHtml(codeBlockId)}"` : "";
3312
+ const html = `<pre${langAttr}${idAttr}><code${langClass}>${code}
3155
3313
  </code></pre>`;
3156
3314
  return pretty ? html + "\n" : html;
3157
3315
  }
@@ -3365,6 +3523,7 @@ function htmlToDelta(html, options = {}) {
3365
3523
  let currentBlockAttributes = {};
3366
3524
  let pendingText = "";
3367
3525
  let atLineStart = true;
3526
+ let codeBlockIdSeq = 0;
3368
3527
  const context = {
3369
3528
  delta,
3370
3529
  get attributes() {
@@ -3577,6 +3736,8 @@ function htmlToDelta(html, options = {}) {
3577
3736
  }
3578
3737
  const codeBlockValue = language || true;
3579
3738
  currentBlockAttributes["code-block"] = codeBlockValue;
3739
+ const existingId = element.getAttribute("data-code-block-id");
3740
+ currentBlockAttributes["code-block-id"] = existingId || `cb-${++codeBlockIdSeq}`;
3580
3741
  const sourceElement = codeElement || element;
3581
3742
  const rawText = sourceElement.textContent ?? "";
3582
3743
  const text = rawText.endsWith("\n") ? rawText.slice(0, -1) : rawText;
@@ -3732,6 +3893,10 @@ function htmlToDelta(html, options = {}) {
3732
3893
  const heightMatch = style.match(/(?:^|;\s*)height:\s*([^;]+)/);
3733
3894
  if (heightMatch?.[1]) attrs.height = heightMatch[1].trim().replace(/px$/, "");
3734
3895
  const embedAttrs = Object.keys(attrs).length > 0 ? attrs : void 0;
3896
+ if (element.getAttribute("data-code-widget") !== null) {
3897
+ context.pushEmbed({ codeWidget: src }, embedAttrs);
3898
+ return;
3899
+ }
3735
3900
  context.pushEmbed({ video: fromVideoEmbedUrl(src) }, embedAttrs);
3736
3901
  }
3737
3902
  function processFootnotesSection(section) {
@@ -4312,11 +4477,13 @@ function collectCodeBlock(lines, startIndex) {
4312
4477
  const startLine = lines[startIndex];
4313
4478
  if (!startLine) return codeLines;
4314
4479
  const startLang = getCodeBlockLanguage2(startLine.attributes);
4480
+ const startId = getCodeBlockId2(startLine.attributes);
4315
4481
  for (let i = startIndex; i < lines.length; i++) {
4316
4482
  const line = lines[i];
4317
4483
  if (!line || !line.attributes["code-block"]) break;
4318
4484
  const lang = getCodeBlockLanguage2(line.attributes);
4319
- if (i > startIndex && lang !== startLang) break;
4485
+ const id = getCodeBlockId2(line.attributes);
4486
+ if (i > startIndex && (lang !== startLang || id !== startId)) break;
4320
4487
  codeLines.push(line);
4321
4488
  }
4322
4489
  return codeLines;
@@ -4434,6 +4601,10 @@ function getCodeBlockLanguage2(attributes) {
4434
4601
  }
4435
4602
  return void 0;
4436
4603
  }
4604
+ function getCodeBlockId2(attributes) {
4605
+ const id = attributes["code-block-id"];
4606
+ return typeof id === "string" ? id : void 0;
4607
+ }
4437
4608
  function renderLineContent2(ops, embedRenderers, inCodeBlock, useLatexDelimiters = false, blockHandlers, prettyHtml = false, registry, softBreakStyle = "spaces", inTableCell = false) {
4438
4609
  let result = "";
4439
4610
  for (const op of ops) {
@@ -4583,6 +4754,28 @@ function renderEmbed2(embed, attributes, customRenderers, useLatexDelimiters = f
4583
4754
  }
4584
4755
  return `![Video](${src})`;
4585
4756
  }
4757
+ if (embedType === "codeWidget") {
4758
+ const src = typeof embedValue === "string" ? embedValue : "";
4759
+ const hasFloat = attributes?.float != null && typeof attributes.float === "string" && attributes.float !== "none";
4760
+ const hasWidth = attributes?.width != null;
4761
+ const hasHeight = attributes?.height != null;
4762
+ if (hasFloat || hasWidth || hasHeight) {
4763
+ const floatAttr = hasFloat ? ` data-float="${escapeHtml(String(attributes.float))}"` : "";
4764
+ const styles = [];
4765
+ if (hasWidth) {
4766
+ const w = typeof attributes.width === "string" || typeof attributes.width === "number" ? String(attributes.width) : "";
4767
+ if (w && w !== "auto") styles.push(`width: ${/^\d+$/.test(w) ? w + "px" : w}`);
4768
+ }
4769
+ if (hasHeight) {
4770
+ const h = typeof attributes.height === "string" || typeof attributes.height === "number" ? String(attributes.height) : "";
4771
+ if (h && h !== "auto") styles.push(`height: ${/^\d+$/.test(h) ? h + "px" : h}`);
4772
+ }
4773
+ const styleAttr = styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
4774
+ const embedSrc = toCodeWidgetEmbedUrl(src);
4775
+ return `<iframe data-code-widget src="${escapeHtml(embedSrc)}" frameborder="0" allowfullscreen${floatAttr}${styleAttr}></iframe>`;
4776
+ }
4777
+ return `![Widget](${src})`;
4778
+ }
4586
4779
  if (embedType === "formula") {
4587
4780
  const latex = typeof embedValue === "string" ? embedValue : "";
4588
4781
  return useLatexDelimiters ? `\\(${latex}\\)` : `$${latex}$`;
@@ -4787,6 +4980,7 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
4787
4980
  let pendingText = "";
4788
4981
  const spanAttrStack = [];
4789
4982
  const footnoteDefinitions = /* @__PURE__ */ new Map();
4983
+ let codeBlockIdSeq = 0;
4790
4984
  const context = {
4791
4985
  delta,
4792
4986
  pushText(text, attrs) {
@@ -5048,6 +5242,10 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
5048
5242
  context.pushEmbed({ video: url });
5049
5243
  return;
5050
5244
  }
5245
+ if (alt.toLowerCase() === "widget") {
5246
+ context.pushEmbed({ codeWidget: url });
5247
+ return;
5248
+ }
5051
5249
  const attrs = {};
5052
5250
  if (node.alt) attrs.alt = node.alt;
5053
5251
  if (url.toLowerCase().endsWith(".drawio")) {
@@ -5102,7 +5300,8 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
5102
5300
  }
5103
5301
  const lines = code.split("\n");
5104
5302
  const codeBlockAttr = {
5105
- "code-block": lang ?? true
5303
+ "code-block": lang ?? true,
5304
+ "code-block-id": `cb-${++codeBlockIdSeq}`
5106
5305
  };
5107
5306
  for (const line of lines) {
5108
5307
  context.pushText(line);
@@ -5116,7 +5315,10 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
5116
5315
  context.pushNewline();
5117
5316
  } else {
5118
5317
  const lines = value.split("\n");
5119
- const mathBlockAttr = { "code-block": "math" };
5318
+ const mathBlockAttr = {
5319
+ "code-block": "math",
5320
+ "code-block-id": `cb-${++codeBlockIdSeq}`
5321
+ };
5120
5322
  for (const line of lines) {
5121
5323
  context.pushText(line);
5122
5324
  context.pushNewline(mathBlockAttr);
@@ -5411,6 +5613,7 @@ function extractTableRegion(ops, hintOpIdx) {
5411
5613
  cloneDelta,
5412
5614
  codeBlockFormat,
5413
5615
  codeFormat,
5616
+ codeWidgetFormat,
5414
5617
  colorFormat,
5415
5618
  columnsBlockHandler,
5416
5619
  createDefaultBlockHandlers,
@@ -5474,6 +5677,7 @@ function extractTableRegion(ops, hintOpIdx) {
5474
5677
  tableColFormat,
5475
5678
  tableHeaderFormat,
5476
5679
  tableRowFormat,
5680
+ toCodeWidgetEmbedUrl,
5477
5681
  toHexColor,
5478
5682
  underlineFormat,
5479
5683
  unescapeHtml,