@aeriz/wysiwyg 1.0.2 → 1.0.3

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.
Files changed (108) hide show
  1. package/README.md +610 -96
  2. package/angular/README.md +112 -0
  3. package/angular/aeriz-editor.directive.ts +243 -0
  4. package/angular/aeriz-wysiwyg.module.ts +14 -0
  5. package/angular/index.ts +5 -0
  6. package/dist/adapters/jquery.cjs +1 -0
  7. package/dist/adapters/jquery.js +91 -0
  8. package/dist/adapters/react.cjs +1 -0
  9. package/dist/adapters/react.js +66 -0
  10. package/dist/adapters/shared.cjs +1 -0
  11. package/dist/adapters/shared.js +87 -0
  12. package/dist/adapters/svelte.cjs +1 -0
  13. package/dist/adapters/svelte.js +29 -0
  14. package/dist/adapters/vue.cjs +1 -0
  15. package/dist/adapters/vue.js +110 -0
  16. package/dist/adapters/vue2.cjs +1 -0
  17. package/dist/adapters/vue2.js +117 -0
  18. package/dist/adapters/wc.cjs +1 -0
  19. package/dist/adapters/wc.js +207 -0
  20. package/dist/aeriz-wysiwyg-adapter.js +169 -0
  21. package/dist/aeriz-wysiwyg-adapter.min.js +1 -0
  22. package/dist/aeriz-wysiwyg-jquery.js +275 -0
  23. package/dist/aeriz-wysiwyg-jquery.min.js +1 -0
  24. package/dist/aeriz-wysiwyg-wc.js +401 -0
  25. package/dist/aeriz-wysiwyg-wc.min.js +1 -0
  26. package/dist/aeriz-wysiwyg.cjs +6912 -0
  27. package/dist/aeriz-wysiwyg.css +37 -129
  28. package/dist/aeriz-wysiwyg.esm.js +1170 -149
  29. package/dist/aeriz-wysiwyg.esm.min.js +2057 -1209
  30. package/dist/aeriz-wysiwyg.js +1169 -148
  31. package/dist/aeriz-wysiwyg.min.cjs +54 -0
  32. package/dist/aeriz-wysiwyg.min.css +1 -1
  33. package/dist/aeriz-wysiwyg.min.js +44 -13
  34. package/dist/server.cjs +32 -0
  35. package/dist/server.js +666 -0
  36. package/dist-types/adapters/jquery.d.ts +34 -0
  37. package/dist-types/adapters/jquery.d.ts.map +1 -0
  38. package/dist-types/adapters/react.d.ts +28 -0
  39. package/dist-types/adapters/react.d.ts.map +1 -0
  40. package/dist-types/adapters/shared.d.ts +83 -0
  41. package/dist-types/adapters/shared.d.ts.map +1 -0
  42. package/dist-types/adapters/svelte.d.ts +38 -0
  43. package/dist-types/adapters/svelte.d.ts.map +1 -0
  44. package/dist-types/adapters/vue.d.ts +189 -0
  45. package/dist-types/adapters/vue.d.ts.map +1 -0
  46. package/dist-types/adapters/vue2.d.ts +175 -0
  47. package/dist-types/adapters/vue2.d.ts.map +1 -0
  48. package/dist-types/adapters/wc.d.ts +96 -0
  49. package/dist-types/adapters/wc.d.ts.map +1 -0
  50. package/dist-types/cjs/adapters/jquery.d.cts +34 -0
  51. package/dist-types/cjs/adapters/react.d.cts +28 -0
  52. package/dist-types/cjs/adapters/shared.d.cts +83 -0
  53. package/dist-types/cjs/adapters/svelte.d.cts +38 -0
  54. package/dist-types/cjs/adapters/vue.d.cts +189 -0
  55. package/dist-types/cjs/adapters/vue2.d.cts +175 -0
  56. package/dist-types/cjs/adapters/wc.d.cts +96 -0
  57. package/dist-types/cjs/core/AerizEditor.d.cts +357 -0
  58. package/dist-types/cjs/core/AerizViewer.d.cts +61 -0
  59. package/dist-types/cjs/core/block-meta.d.cts +32 -0
  60. package/dist-types/cjs/core/blocks.d.cts +368 -0
  61. package/dist-types/cjs/core/fonts.d.cts +60 -0
  62. package/dist-types/cjs/core/highlight.d.cts +44 -0
  63. package/dist-types/cjs/core/html-mini.d.cts +65 -0
  64. package/dist-types/cjs/core/i18n.d.cts +209 -0
  65. package/dist-types/cjs/core/import-html.d.cts +39 -0
  66. package/dist-types/cjs/core/import-markdown.d.cts +37 -0
  67. package/dist-types/cjs/core/markdown-export.d.cts +30 -0
  68. package/dist-types/cjs/core/plain-text.d.cts +17 -0
  69. package/dist-types/cjs/core/render-string.d.cts +68 -0
  70. package/dist-types/cjs/core/snapshot.d.cts +51 -0
  71. package/dist-types/cjs/index.d.cts +20 -0
  72. package/dist-types/cjs/server/index.d.cts +37 -0
  73. package/dist-types/cjs/types/index.d.cts +61 -0
  74. package/dist-types/cjs/ui/Toolbar.d.cts +21 -0
  75. package/dist-types/cjs/ui/dialogs.d.cts +74 -0
  76. package/dist-types/cjs/ui/icons.d.cts +2 -0
  77. package/dist-types/cjs/ui/popovers.d.cts +70 -0
  78. package/dist-types/core/AerizEditor.d.ts +30 -10
  79. package/dist-types/core/AerizEditor.d.ts.map +1 -1
  80. package/dist-types/core/AerizViewer.d.ts +16 -4
  81. package/dist-types/core/AerizViewer.d.ts.map +1 -1
  82. package/dist-types/core/block-meta.d.ts +32 -0
  83. package/dist-types/core/block-meta.d.ts.map +1 -0
  84. package/dist-types/core/blocks.d.ts +62 -7
  85. package/dist-types/core/blocks.d.ts.map +1 -1
  86. package/dist-types/core/fonts.d.ts +11 -0
  87. package/dist-types/core/fonts.d.ts.map +1 -1
  88. package/dist-types/core/html-mini.d.ts +65 -0
  89. package/dist-types/core/html-mini.d.ts.map +1 -0
  90. package/dist-types/core/import-html.d.ts.map +1 -1
  91. package/dist-types/core/markdown-export.d.ts +30 -0
  92. package/dist-types/core/markdown-export.d.ts.map +1 -0
  93. package/dist-types/core/plain-text.d.ts +17 -0
  94. package/dist-types/core/plain-text.d.ts.map +1 -0
  95. package/dist-types/core/render-string.d.ts +68 -0
  96. package/dist-types/core/render-string.d.ts.map +1 -0
  97. package/dist-types/core/snapshot.d.ts +51 -0
  98. package/dist-types/core/snapshot.d.ts.map +1 -0
  99. package/dist-types/index.d.ts +19 -10
  100. package/dist-types/index.d.ts.map +1 -1
  101. package/dist-types/server/index.d.ts +37 -0
  102. package/dist-types/server/index.d.ts.map +1 -0
  103. package/dist-types/types/index.d.ts +21 -0
  104. package/dist-types/types/index.d.ts.map +1 -1
  105. package/dist-types/ui/Toolbar.d.ts +1 -1
  106. package/dist-types/ui/popovers.d.ts +1 -1
  107. package/dist-types/ui/popovers.d.ts.map +1 -1
  108. package/package.json +213 -58
@@ -884,27 +884,80 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
884
884
  return null;
885
885
  }
886
886
  let loaded = false;
887
+ const unavailable = /* @__PURE__ */ new Set();
888
+ const listeners = /* @__PURE__ */ new Set();
889
+ const SAMPLE_TEXT = "가A";
890
+ function getAvailableFontFamilies() {
891
+ if (unavailable.size === 0) return FONT_FAMILIES;
892
+ return FONT_FAMILIES.filter((f) => !unavailable.has(f.id));
893
+ }
894
+ function subscribeFontAvailability(listener) {
895
+ listeners.add(listener);
896
+ return () => {
897
+ listeners.delete(listener);
898
+ };
899
+ }
900
+ function isInstalledLocally(stack) {
901
+ const family = firstFamilyToken(stack);
902
+ if (!family) return false;
903
+ const ctx = document.createElement("canvas").getContext("2d");
904
+ if (!ctx) return false;
905
+ const widthOf = (spec) => {
906
+ ctx.font = `16px ${spec}`;
907
+ return ctx.measureText("가나다ABCwi").width;
908
+ };
909
+ return ["monospace", "serif"].some(
910
+ (fallback) => widthOf(`"${family}", ${fallback}`) !== widthOf(fallback)
911
+ );
912
+ }
913
+ function markUnavailable(fonts) {
914
+ let changed = false;
915
+ for (const font of fonts) {
916
+ if (unavailable.has(font.id)) continue;
917
+ if (isInstalledLocally(font.stack)) continue;
918
+ unavailable.add(font.id);
919
+ changed = true;
920
+ }
921
+ if (!changed) return;
922
+ for (const listener of listeners) listener();
923
+ }
924
+ function verifyLoaded(fonts) {
925
+ const set = document.fonts;
926
+ if (!set || typeof set.load !== "function") return;
927
+ for (const font of fonts) {
928
+ const family = firstFamilyToken(font.stack);
929
+ if (!family) continue;
930
+ set.load(`16px "${family}"`, SAMPLE_TEXT).then((faces) => {
931
+ if (faces.length === 0) markUnavailable([font]);
932
+ }).catch(() => markUnavailable([font]));
933
+ }
934
+ }
887
935
  function ensureFontFamiliesLoaded() {
888
936
  if (loaded) return;
889
937
  loaded = true;
890
938
  if (typeof document === "undefined") return;
891
- const googleParams = FONT_FAMILIES.map((f) => f.googleFonts).filter((p) => typeof p === "string" && p.length > 0);
892
- if (googleParams.length > 0) {
893
- const url = "https://fonts.googleapis.com/css2?" + googleParams.map((p) => `family=${p}`).join("&") + "&display=swap";
894
- appendStylesheet(url);
939
+ const googleFonts = FONT_FAMILIES.filter((f) => f.googleFonts);
940
+ if (googleFonts.length > 0) {
941
+ const url = "https://fonts.googleapis.com/css2?" + googleFonts.map((f) => `family=${f.googleFonts}`).join("&") + "&display=swap";
942
+ appendStylesheet(url, googleFonts);
895
943
  }
896
- for (const f of FONT_FAMILIES) {
897
- if (f.cssUrl) appendStylesheet(f.cssUrl);
944
+ for (const font of FONT_FAMILIES) {
945
+ if (font.cssUrl) appendStylesheet(font.cssUrl, [font]);
898
946
  }
899
947
  }
900
- function appendStylesheet(href) {
948
+ function appendStylesheet(href, provides) {
901
949
  const existing = document.querySelector(
902
950
  `link[rel="stylesheet"][href="${href}"]`
903
951
  );
904
- if (existing) return;
952
+ if (existing) {
953
+ verifyLoaded(provides);
954
+ return;
955
+ }
905
956
  const link = document.createElement("link");
906
957
  link.rel = "stylesheet";
907
958
  link.href = href;
959
+ link.addEventListener("error", () => markUnavailable(provides));
960
+ link.addEventListener("load", () => verifyLoaded(provides));
908
961
  document.head.appendChild(link);
909
962
  }
910
963
  function findFontIdFromCss(css) {
@@ -1632,9 +1685,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1632
1685
  return m;
1633
1686
  })();
1634
1687
  function highlightCode(code, langId) {
1635
- if (!langId) return escapeHtml$1(code);
1688
+ if (!langId) return escapeHtml$2(code);
1636
1689
  const lang = LANG_BY_ID.get(langId);
1637
- if (!lang) return escapeHtml$1(code);
1690
+ if (!lang) return escapeHtml$2(code);
1638
1691
  if (lang.custom) return lang.custom(code);
1639
1692
  return tokenize(code, lang);
1640
1693
  }
@@ -1648,14 +1701,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1648
1701
  if (lang.lineComment && code.startsWith(lang.lineComment, i)) {
1649
1702
  const nl = code.indexOf("\n", i);
1650
1703
  const stop = nl === -1 ? n : nl;
1651
- out += `<span class="hl-comment">${escapeHtml$1(code.slice(i, stop))}</span>`;
1704
+ out += `<span class="hl-comment">${escapeHtml$2(code.slice(i, stop))}</span>`;
1652
1705
  i = stop;
1653
1706
  continue;
1654
1707
  }
1655
1708
  if (lang.blockComment && code.startsWith(lang.blockComment.start, i)) {
1656
1709
  const closeAt = code.indexOf(lang.blockComment.end, i + lang.blockComment.start.length);
1657
1710
  const stop = closeAt === -1 ? n : closeAt + lang.blockComment.end.length;
1658
- out += `<span class="hl-comment">${escapeHtml$1(code.slice(i, stop))}</span>`;
1711
+ out += `<span class="hl-comment">${escapeHtml$2(code.slice(i, stop))}</span>`;
1659
1712
  i = stop;
1660
1713
  continue;
1661
1714
  }
@@ -1675,7 +1728,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1675
1728
  if (c === "\n" && sd !== "`") break;
1676
1729
  j++;
1677
1730
  }
1678
- out += `<span class="hl-string">${escapeHtml$1(code.slice(i, j))}</span>`;
1731
+ out += `<span class="hl-string">${escapeHtml$2(code.slice(i, j))}</span>`;
1679
1732
  i = j;
1680
1733
  continue;
1681
1734
  }
@@ -1683,7 +1736,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1683
1736
  if (/[0-9]/.test(ch)) {
1684
1737
  const m = NUMBER_RE.exec(code.slice(i));
1685
1738
  if (m) {
1686
- out += `<span class="hl-number">${escapeHtml$1(m[0])}</span>`;
1739
+ out += `<span class="hl-number">${escapeHtml$2(m[0])}</span>`;
1687
1740
  i += m[0].length;
1688
1741
  continue;
1689
1742
  }
@@ -1694,22 +1747,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1694
1747
  const word = code.slice(i, j);
1695
1748
  const lookup = ci ? word.toLowerCase() : word;
1696
1749
  if (lang.keywords.has(lookup)) {
1697
- out += `<span class="hl-keyword">${escapeHtml$1(word)}</span>`;
1750
+ out += `<span class="hl-keyword">${escapeHtml$2(word)}</span>`;
1698
1751
  } else if ((_b = lang.builtins) == null ? void 0 : _b.has(lookup)) {
1699
- out += `<span class="hl-builtin">${escapeHtml$1(word)}</span>`;
1752
+ out += `<span class="hl-builtin">${escapeHtml$2(word)}</span>`;
1700
1753
  } else {
1701
1754
  let k = j;
1702
1755
  while (k < n && (code[k] === " " || code[k] === " ")) k++;
1703
1756
  if (code[k] === "(") {
1704
- out += `<span class="hl-fn">${escapeHtml$1(word)}</span>`;
1757
+ out += `<span class="hl-fn">${escapeHtml$2(word)}</span>`;
1705
1758
  } else {
1706
- out += escapeHtml$1(word);
1759
+ out += escapeHtml$2(word);
1707
1760
  }
1708
1761
  }
1709
1762
  i = j;
1710
1763
  continue;
1711
1764
  }
1712
- out += escapeHtml$1(ch);
1765
+ out += escapeHtml$2(ch);
1713
1766
  i++;
1714
1767
  }
1715
1768
  return out;
@@ -1723,14 +1776,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1723
1776
  if (code.startsWith("<!--", i)) {
1724
1777
  const end = code.indexOf("-->", i + 4);
1725
1778
  const stop = end === -1 ? n : end + 3;
1726
- out += `<span class="hl-comment">${escapeHtml$1(code.slice(i, stop))}</span>`;
1779
+ out += `<span class="hl-comment">${escapeHtml$2(code.slice(i, stop))}</span>`;
1727
1780
  i = stop;
1728
1781
  continue;
1729
1782
  }
1730
1783
  if (code[i] === "<") {
1731
1784
  const end = code.indexOf(">", i);
1732
1785
  if (end === -1) {
1733
- out += escapeHtml$1(code.slice(i));
1786
+ out += escapeHtml$2(code.slice(i));
1734
1787
  break;
1735
1788
  }
1736
1789
  const tag = code.slice(i, end + 1);
@@ -1738,7 +1791,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1738
1791
  i = end + 1;
1739
1792
  continue;
1740
1793
  }
1741
- out += escapeHtml$1(code[i] ?? "");
1794
+ out += escapeHtml$2(code[i] ?? "");
1742
1795
  i++;
1743
1796
  }
1744
1797
  return out;
@@ -1748,27 +1801,27 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1748
1801
  let result = '<span class="hl-punct">&lt;</span>';
1749
1802
  const nameMatch = /^\/?[a-zA-Z][a-zA-Z0-9-]*/.exec(inner);
1750
1803
  if (nameMatch) {
1751
- result += `<span class="hl-tag">${escapeHtml$1(nameMatch[0])}</span>`;
1804
+ result += `<span class="hl-tag">${escapeHtml$2(nameMatch[0])}</span>`;
1752
1805
  let rest = inner.slice(nameMatch[0].length);
1753
1806
  const ATTR_RE = /\s+([a-zA-Z_:][a-zA-Z0-9_:.-]*)(\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
1754
1807
  let last = 0;
1755
1808
  let m;
1756
1809
  while (m = ATTR_RE.exec(rest)) {
1757
- result += escapeHtml$1(rest.slice(last, m.index));
1758
- result += escapeHtml$1(rest.slice(m.index, m.index + m[0].indexOf(m[1] ?? "")));
1759
- result += `<span class="hl-attr">${escapeHtml$1(m[1] ?? "")}</span>`;
1810
+ result += escapeHtml$2(rest.slice(last, m.index));
1811
+ result += escapeHtml$2(rest.slice(m.index, m.index + m[0].indexOf(m[1] ?? "")));
1812
+ result += `<span class="hl-attr">${escapeHtml$2(m[1] ?? "")}</span>`;
1760
1813
  const valuePart = rest.slice(m.index + (m[0].indexOf(m[1] ?? "") + (m[1] ?? "").length), m.index + m[0].length);
1761
1814
  if (valuePart.includes("=")) {
1762
1815
  const eqIdx = valuePart.indexOf("=");
1763
- result += escapeHtml$1(valuePart.slice(0, eqIdx + 1));
1816
+ result += escapeHtml$2(valuePart.slice(0, eqIdx + 1));
1764
1817
  const valueStr = valuePart.slice(eqIdx + 1);
1765
- result += `<span class="hl-string">${escapeHtml$1(valueStr)}</span>`;
1818
+ result += `<span class="hl-string">${escapeHtml$2(valueStr)}</span>`;
1766
1819
  }
1767
1820
  last = m.index + m[0].length;
1768
1821
  }
1769
- result += escapeHtml$1(rest.slice(last));
1822
+ result += escapeHtml$2(rest.slice(last));
1770
1823
  } else {
1771
- result += escapeHtml$1(inner);
1824
+ result += escapeHtml$2(inner);
1772
1825
  }
1773
1826
  result += '<span class="hl-punct">&gt;</span>';
1774
1827
  return result;
@@ -1782,20 +1835,20 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1782
1835
  if (code.startsWith("/*", i)) {
1783
1836
  const end = code.indexOf("*/", i + 2);
1784
1837
  const stop = end === -1 ? n : end + 2;
1785
- out += `<span class="hl-comment">${escapeHtml$1(code.slice(i, stop))}</span>`;
1838
+ out += `<span class="hl-comment">${escapeHtml$2(code.slice(i, stop))}</span>`;
1786
1839
  i = stop;
1787
1840
  continue;
1788
1841
  }
1789
1842
  const ch = code[i] ?? "";
1790
1843
  if (ch === "{") {
1791
1844
  inBlock = true;
1792
- out += escapeHtml$1(ch);
1845
+ out += escapeHtml$2(ch);
1793
1846
  i++;
1794
1847
  continue;
1795
1848
  }
1796
1849
  if (ch === "}") {
1797
1850
  inBlock = false;
1798
- out += escapeHtml$1(ch);
1851
+ out += escapeHtml$2(ch);
1799
1852
  i++;
1800
1853
  continue;
1801
1854
  }
@@ -1807,7 +1860,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1807
1860
  else j++;
1808
1861
  }
1809
1862
  j = Math.min(j + 1, n);
1810
- out += `<span class="hl-string">${escapeHtml$1(code.slice(i, j))}</span>`;
1863
+ out += `<span class="hl-string">${escapeHtml$2(code.slice(i, j))}</span>`;
1811
1864
  i = j;
1812
1865
  continue;
1813
1866
  }
@@ -1816,17 +1869,17 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1816
1869
  while (j < n && /[a-zA-Z0-9_-]/.test(code[j] ?? "")) j++;
1817
1870
  const word = code.slice(i, j);
1818
1871
  if (ch === "@") {
1819
- out += `<span class="hl-keyword">${escapeHtml$1(word)}</span>`;
1872
+ out += `<span class="hl-keyword">${escapeHtml$2(word)}</span>`;
1820
1873
  } else if (inBlock && /^[a-zA-Z-]+$/.test(word)) {
1821
1874
  let k = j;
1822
1875
  while (k < n && (code[k] === " " || code[k] === " ")) k++;
1823
1876
  if (code[k] === ":") {
1824
- out += `<span class="hl-prop">${escapeHtml$1(word)}</span>`;
1877
+ out += `<span class="hl-prop">${escapeHtml$2(word)}</span>`;
1825
1878
  } else {
1826
- out += escapeHtml$1(word);
1879
+ out += escapeHtml$2(word);
1827
1880
  }
1828
1881
  } else {
1829
- out += `<span class="hl-tag">${escapeHtml$1(word)}</span>`;
1882
+ out += `<span class="hl-tag">${escapeHtml$2(word)}</span>`;
1830
1883
  }
1831
1884
  i = j;
1832
1885
  continue;
@@ -1834,12 +1887,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1834
1887
  if (/[0-9]/.test(ch)) {
1835
1888
  const m = /^[0-9.]+(px|em|rem|%|vh|vw|s|ms|deg|fr)?/.exec(code.slice(i));
1836
1889
  if (m) {
1837
- out += `<span class="hl-number">${escapeHtml$1(m[0])}</span>`;
1890
+ out += `<span class="hl-number">${escapeHtml$2(m[0])}</span>`;
1838
1891
  i += m[0].length;
1839
1892
  continue;
1840
1893
  }
1841
1894
  }
1842
- out += escapeHtml$1(ch);
1895
+ out += escapeHtml$2(ch);
1843
1896
  i++;
1844
1897
  }
1845
1898
  return out;
@@ -1850,22 +1903,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1850
1903
  function highlightMdLine(line) {
1851
1904
  const h = /^(#{1,6})\s+(.*)$/.exec(line);
1852
1905
  if (h) {
1853
- return `<span class="hl-keyword">${escapeHtml$1(h[1] ?? "")}</span> <span class="hl-tag">${highlightMdInline(h[2] ?? "")}</span>`;
1906
+ return `<span class="hl-keyword">${escapeHtml$2(h[1] ?? "")}</span> <span class="hl-tag">${highlightMdInline(h[2] ?? "")}</span>`;
1854
1907
  }
1855
1908
  if (line.startsWith(">")) {
1856
- return `<span class="hl-comment">${escapeHtml$1(line)}</span>`;
1909
+ return `<span class="hl-comment">${escapeHtml$2(line)}</span>`;
1857
1910
  }
1858
1911
  if (line.startsWith("```")) {
1859
- return `<span class="hl-keyword">${escapeHtml$1(line)}</span>`;
1912
+ return `<span class="hl-keyword">${escapeHtml$2(line)}</span>`;
1860
1913
  }
1861
1914
  const li = /^(\s*)([-*+]|\d+\.)\s+(.*)$/.exec(line);
1862
1915
  if (li) {
1863
- return `${li[1] ?? ""}<span class="hl-keyword">${escapeHtml$1(li[2] ?? "")}</span> ${highlightMdInline(li[3] ?? "")}`;
1916
+ return `${li[1] ?? ""}<span class="hl-keyword">${escapeHtml$2(li[2] ?? "")}</span> ${highlightMdInline(li[3] ?? "")}`;
1864
1917
  }
1865
1918
  return highlightMdInline(line);
1866
1919
  }
1867
1920
  function highlightMdInline(s) {
1868
- let out = escapeHtml$1(s);
1921
+ let out = escapeHtml$2(s);
1869
1922
  out = out.replace(/\*\*([^*]+)\*\*/g, '<span class="hl-keyword">**$1**</span>');
1870
1923
  out = out.replace(new RegExp("(?<!\\*)\\*([^*]+)\\*(?!\\*)", "g"), '<span class="hl-string">*$1*</span>');
1871
1924
  out = out.replace(/`([^`]+)`/g, '<span class="hl-builtin">`$1`</span>');
@@ -1875,7 +1928,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1875
1928
  );
1876
1929
  return out;
1877
1930
  }
1878
- function escapeHtml$1(s) {
1931
+ function escapeHtml$2(s) {
1879
1932
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1880
1933
  }
1881
1934
  function preserveSelection(el) {
@@ -2146,31 +2199,42 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2146
2199
  popover.className = "aw-popover aw-font-family-popover";
2147
2200
  popover.dataset.popover = "true";
2148
2201
  preserveSelection(popover);
2149
- for (const font of FONT_FAMILIES) {
2150
- const btn = document.createElement("button");
2151
- btn.type = "button";
2152
- btn.className = "aw-popover-item aw-font-family-item";
2153
- if (currentId && currentId === font.id) {
2154
- btn.classList.add("aw-popover-item-active");
2202
+ const render = () => {
2203
+ popover.replaceChildren();
2204
+ for (const font of getAvailableFontFamilies()) {
2205
+ const btn = document.createElement("button");
2206
+ btn.type = "button";
2207
+ btn.className = "aw-popover-item aw-font-family-item";
2208
+ if (currentId && currentId === font.id) {
2209
+ btn.classList.add("aw-popover-item-active");
2210
+ }
2211
+ btn.title = font.label;
2212
+ const name = document.createElement("span");
2213
+ name.className = "aw-font-family-name";
2214
+ name.textContent = font.label;
2215
+ const preview = document.createElement("span");
2216
+ preview.className = "aw-font-family-preview";
2217
+ preview.textContent = "가A";
2218
+ preview.style.fontFamily = font.stack;
2219
+ btn.appendChild(name);
2220
+ btn.appendChild(preview);
2221
+ preserveSelection(btn);
2222
+ btn.addEventListener("click", (e) => {
2223
+ e.stopPropagation();
2224
+ popover.remove();
2225
+ onPick({ id: font.id, label: font.label, stack: font.stack });
2226
+ });
2227
+ popover.appendChild(btn);
2155
2228
  }
2156
- btn.title = font.label;
2157
- const name = document.createElement("span");
2158
- name.className = "aw-font-family-name";
2159
- name.textContent = font.label;
2160
- const preview = document.createElement("span");
2161
- preview.className = "aw-font-family-preview";
2162
- preview.textContent = "가A";
2163
- preview.style.fontFamily = font.stack;
2164
- btn.appendChild(name);
2165
- btn.appendChild(preview);
2166
- preserveSelection(btn);
2167
- btn.addEventListener("click", (e) => {
2168
- e.stopPropagation();
2169
- popover.remove();
2170
- onPick({ id: font.id, label: font.label, stack: font.stack });
2171
- });
2172
- popover.appendChild(btn);
2173
- }
2229
+ };
2230
+ render();
2231
+ const unsubscribe = subscribeFontAvailability(() => {
2232
+ if (!popover.isConnected) {
2233
+ unsubscribe();
2234
+ return;
2235
+ }
2236
+ render();
2237
+ });
2174
2238
  anchorPopover(popover, anchor);
2175
2239
  }
2176
2240
  function openCodeLanguageDropdown(anchor, onPick, currentId) {
@@ -2501,6 +2565,24 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2501
2565
  "stroke-width",
2502
2566
  "text-anchor"
2503
2567
  ]);
2568
+ const CURRENT_COLOR_DEFAULTED = /* @__PURE__ */ new Set([
2569
+ "border-block-end-color",
2570
+ "border-block-start-color",
2571
+ "border-bottom-color",
2572
+ "border-inline-end-color",
2573
+ "border-inline-start-color",
2574
+ "border-left-color",
2575
+ "border-right-color",
2576
+ "border-top-color",
2577
+ "caret-color",
2578
+ "column-rule-color",
2579
+ "row-rule-color",
2580
+ "outline-color",
2581
+ "text-decoration-color",
2582
+ "text-emphasis-color",
2583
+ "-webkit-text-fill-color",
2584
+ "-webkit-text-stroke-color"
2585
+ ]);
2504
2586
  async function importHtmlWithComputedStyles(html, options = {}) {
2505
2587
  const settleMs = options.settleMs ?? 1e3;
2506
2588
  const timeoutMs = options.timeoutMs ?? 3e4;
@@ -2657,8 +2739,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2657
2739
  };
2658
2740
  return { get, cleanup };
2659
2741
  }
2660
- function computeMinimalInline(el, parent, baselineTagName, getBaseline, win) {
2661
- const cs = win.getComputedStyle(el);
2742
+ function computeMinimalInline(el, parent, baselineTagName, getBaseline, win, pseudo) {
2743
+ const cs = win.getComputedStyle(el, pseudo ?? null);
2662
2744
  const parentCs = parent ? win.getComputedStyle(parent) : null;
2663
2745
  const baseline = getBaseline(baselineTagName);
2664
2746
  let cssText = "";
@@ -2667,6 +2749,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2667
2749
  if (!prop) continue;
2668
2750
  const val = cs.getPropertyValue(prop);
2669
2751
  if (!val) continue;
2752
+ if (pseudo && prop === "content") continue;
2753
+ if (CURRENT_COLOR_DEFAULTED.has(prop) && val === cs.getPropertyValue("color")) continue;
2670
2754
  if (INHERITED_PROPERTIES.has(prop)) {
2671
2755
  if (parentCs && parentCs.getPropertyValue(prop) === val) continue;
2672
2756
  } else {
@@ -2676,6 +2760,56 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2676
2760
  }
2677
2761
  return cssText;
2678
2762
  }
2763
+ function textFromCssContent(value) {
2764
+ let text = "";
2765
+ const segments = /"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'/g;
2766
+ let match;
2767
+ while ((match = segments.exec(value)) !== null) {
2768
+ const raw = match[1] ?? match[2] ?? "";
2769
+ text += raw.replace(
2770
+ /\\(?:([0-9a-fA-F]{1,6})\s?|(.))/g,
2771
+ (_all, hex, ch) => hex ? String.fromCodePoint(parseInt(hex, 16)) : ch ?? ""
2772
+ );
2773
+ }
2774
+ return text;
2775
+ }
2776
+ const NO_PHRASING_CHILDREN = /* @__PURE__ */ new Set([
2777
+ "TABLE",
2778
+ "THEAD",
2779
+ "TBODY",
2780
+ "TFOOT",
2781
+ "TR",
2782
+ "UL",
2783
+ "OL",
2784
+ "DL",
2785
+ "SELECT",
2786
+ "OPTGROUP"
2787
+ ]);
2788
+ function materializePseudo(orig, copy, which, getBaseline, win) {
2789
+ if (NO_PHRASING_CHILDREN.has(copy.tagName)) return;
2790
+ const selector = which === "before" ? "::before" : "::after";
2791
+ const cs = win.getComputedStyle(orig, selector);
2792
+ const content = cs.getPropertyValue("content");
2793
+ if (!content || content === "none" || content === "normal") return;
2794
+ if (cs.getPropertyValue("display") === "none") return;
2795
+ const text = textFromCssContent(content);
2796
+ const url = /url\((["']?)([^"')]+)\1\)/.exec(content);
2797
+ const cssText = computeMinimalInline(orig, orig, "span", getBaseline, win, selector);
2798
+ if (!text && !url && !cssText) return;
2799
+ const doc = copy.ownerDocument;
2800
+ const span = doc.createElement("span");
2801
+ span.setAttribute("data-imported-pseudo", which);
2802
+ if (cssText) span.setAttribute("style", cssText);
2803
+ if (url == null ? void 0 : url[2]) {
2804
+ const img = doc.createElement("img");
2805
+ img.setAttribute("src", url[2]);
2806
+ img.setAttribute("alt", "");
2807
+ span.appendChild(img);
2808
+ }
2809
+ if (text) span.appendChild(doc.createTextNode(text));
2810
+ if (which === "before") copy.insertBefore(span, copy.firstChild);
2811
+ else copy.appendChild(span);
2812
+ }
2679
2813
  function applyComputedStylesParallel(orig, copy, origParent, getBaseline, win) {
2680
2814
  if (orig.nodeType === 1 && copy.nodeType === 1) {
2681
2815
  const cssText = computeMinimalInline(
@@ -2699,6 +2833,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2699
2833
  const cc = copyChildren[i];
2700
2834
  if (oc && cc) applyComputedStylesParallel(oc, cc, orig, getBaseline, win);
2701
2835
  }
2836
+ if (orig.nodeType === 1 && copy.nodeType === 1) {
2837
+ materializePseudo(orig, copy, "before", getBaseline, win);
2838
+ materializePseudo(orig, copy, "after", getBaseline, win);
2839
+ }
2702
2840
  }
2703
2841
  function markdownToBlocks(md) {
2704
2842
  const html = markdownToHtml(md);
@@ -2731,9 +2869,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2731
2869
  i++;
2732
2870
  }
2733
2871
  if (i < lines.length) i++;
2734
- const langAttr = lang ? ` data-language="${escapeAttr(lang)}"` : "";
2872
+ const langAttr = lang ? ` data-language="${escapeAttr$1(lang)}"` : "";
2735
2873
  blocks.push(
2736
- `<pre><code${langAttr}>${escapeHtml(codeLines.join("\n"))}</code></pre>`
2874
+ `<pre><code${langAttr}>${escapeHtml$1(codeLines.join("\n"))}</code></pre>`
2737
2875
  );
2738
2876
  continue;
2739
2877
  }
@@ -2867,42 +3005,115 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2867
3005
  }
2868
3006
  return parts.join("");
2869
3007
  }
3008
+ const CODE_SLOT = String.fromCharCode(57600);
3009
+ const CODE_SLOT_RE = new RegExp(CODE_SLOT + "([0-9]+)" + CODE_SLOT, "g");
2870
3010
  function parseInline(src) {
2871
3011
  const codes = [];
2872
3012
  let working = src.replace(/(`+)([\s\S]+?)\1/g, (_m, _b, code) => {
2873
- const idx = codes.push(`<code>${escapeHtml(code)}</code>`) - 1;
2874
- return `\0CODE${idx}\0`;
3013
+ const idx = codes.push(`<code>${escapeHtml$1(code)}</code>`) - 1;
3014
+ return `${CODE_SLOT}${idx}${CODE_SLOT}`;
2875
3015
  });
2876
- working = escapeHtml(working);
3016
+ working = escapeHtml$1(working);
2877
3017
  working = working.replace(
2878
3018
  /&lt;(https?:\/\/[^\s&]+)&gt;/g,
2879
- (_m, url) => `<a href="${escapeAttr(url)}" target="_blank" rel="noopener">${escapeHtml(url)}</a>`
3019
+ (_m, url) => `<a href="${escapeAttr$1(url)}" target="_blank" rel="noopener">${escapeHtml$1(url)}</a>`
2880
3020
  );
2881
3021
  working = working.replace(
2882
3022
  /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g,
2883
- (_m, alt, url) => `<img src="${escapeAttr(url)}" alt="${escapeAttr(alt)}">`
3023
+ (_m, alt, url) => `<img src="${escapeAttr$1(url)}" alt="${escapeAttr$1(alt)}">`
2884
3024
  );
2885
3025
  working = working.replace(
2886
3026
  /\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g,
2887
- (_m, text, url) => `<a href="${escapeAttr(url)}" target="_blank" rel="noopener">${text}</a>`
3027
+ (_m, text, url) => `<a href="${escapeAttr$1(url)}" target="_blank" rel="noopener">${text}</a>`
2888
3028
  );
2889
3029
  working = working.replace(/\*\*([^*\n]+?)\*\*/g, "<strong>$1</strong>");
2890
3030
  working = working.replace(/__([^_\n]+?)__/g, "<strong>$1</strong>");
2891
3031
  working = working.replace(/(^|[^*])\*([^*\n]+?)\*(?!\*)/g, "$1<em>$2</em>");
2892
3032
  working = working.replace(/(^|[^_])_([^_\n]+?)_(?!_)/g, "$1<em>$2</em>");
2893
3033
  working = working.replace(/~~([^~\n]+?)~~/g, "<s>$1</s>");
2894
- working = working.replace(/CODE(\d+)/g, (_m, n) => {
3034
+ working = working.replace(CODE_SLOT_RE, (_m, n) => {
2895
3035
  const idx = Number(n);
2896
3036
  return codes[idx] ?? "";
2897
3037
  });
2898
3038
  return working;
2899
3039
  }
2900
- function escapeHtml(s) {
3040
+ function escapeHtml$1(s) {
2901
3041
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2902
3042
  }
2903
- function escapeAttr(s) {
3043
+ function escapeAttr$1(s) {
2904
3044
  return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
2905
3045
  }
3046
+ function formatBytes(bytes) {
3047
+ if (bytes < 1024) return `${bytes} B`;
3048
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3049
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
3050
+ return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;
3051
+ }
3052
+ function fileExtensionLabel(name, mime) {
3053
+ if (name) {
3054
+ const idx = name.lastIndexOf(".");
3055
+ if (idx > 0 && idx < name.length - 1) {
3056
+ return name.slice(idx + 1).toUpperCase();
3057
+ }
3058
+ }
3059
+ if (mime) {
3060
+ const sub = mime.split("/").pop() ?? "";
3061
+ return sub.toUpperCase();
3062
+ }
3063
+ return "";
3064
+ }
3065
+ function hostnameFromUrl(url) {
3066
+ try {
3067
+ return new URL(url).hostname;
3068
+ } catch {
3069
+ return "";
3070
+ }
3071
+ }
3072
+ function defaultFaviconUrl(url) {
3073
+ const host = hostnameFromUrl(url);
3074
+ if (!host) return "";
3075
+ return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(host)}&sz=64`;
3076
+ }
3077
+ const FILE_ICON_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><path d="M14 3v6h6"/></svg>';
3078
+ const FILE_ACTION_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>';
3079
+ const YOUTUBE_IFRAME_ALLOW = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture";
3080
+ const YOUTUBE_EMBED_BASE = "https://www.youtube-nocookie.com/embed/";
3081
+ const FILE_FALLBACK_NAME = "파일";
3082
+ const BLOCK_ID_ATTR = "data-block-id";
3083
+ const SNAPSHOT_VERSION = 1;
3084
+ function snapshotVersionOf(snapshot) {
3085
+ const raw = snapshot.version;
3086
+ return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : 1;
3087
+ }
3088
+ const KNOWN_KINDS = /* @__PURE__ */ new Set([
3089
+ "paragraph",
3090
+ "heading",
3091
+ "bulletList",
3092
+ "orderedList",
3093
+ "blockquote",
3094
+ "codeBlock",
3095
+ "image",
3096
+ "table",
3097
+ "hr",
3098
+ "linkCard",
3099
+ "youtube",
3100
+ "file",
3101
+ "importedHtml",
3102
+ "unknown"
3103
+ ]);
3104
+ function upgradeSnapshots(input) {
3105
+ return input.map((snap) => upgradeSnapshot(snap));
3106
+ }
3107
+ function upgradeSnapshot(snapshot) {
3108
+ const kind = KNOWN_KINDS.has(snapshot.kind) ? snapshot.kind : "unknown";
3109
+ const version = Math.max(snapshotVersionOf(snapshot), 1);
3110
+ return {
3111
+ ...snapshot,
3112
+ kind,
3113
+ version,
3114
+ html: typeof snapshot.html === "string" ? snapshot.html : ""
3115
+ };
3116
+ }
2906
3117
  const ALL_INLINE_MARKS = [
2907
3118
  "bold",
2908
3119
  "italic",
@@ -3146,12 +3357,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3146
3357
  if (attrs == null ? void 0 : attrs.wrapperStyle) wrap.setAttribute("style", attrs.wrapperStyle);
3147
3358
  const iframe = document.createElement("iframe");
3148
3359
  if (videoId) {
3149
- iframe.src = `https://www.youtube-nocookie.com/embed/${videoId}`;
3360
+ iframe.src = `${YOUTUBE_EMBED_BASE}${videoId}`;
3150
3361
  }
3151
- iframe.setAttribute(
3152
- "allow",
3153
- "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
3154
- );
3362
+ iframe.setAttribute("allow", YOUTUBE_IFRAME_ALLOW);
3155
3363
  iframe.setAttribute("allowfullscreen", "");
3156
3364
  iframe.setAttribute("frameborder", "0");
3157
3365
  iframe.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
@@ -3239,18 +3447,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3239
3447
  },
3240
3448
  isEmpty: (el) => !el.getAttribute("href")
3241
3449
  };
3242
- function hostnameFromUrl(url) {
3243
- try {
3244
- return new URL(url).hostname;
3245
- } catch {
3246
- return "";
3247
- }
3248
- }
3249
- function defaultFaviconUrl(url) {
3250
- const host = hostnameFromUrl(url);
3251
- if (!host) return "";
3252
- return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(host)}&sz=64`;
3253
- }
3254
3450
  const FILE = {
3255
3451
  kind: "file",
3256
3452
  label: "파일",
@@ -3289,12 +3485,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3289
3485
  const iconSpan = document.createElement("span");
3290
3486
  iconSpan.className = "aw-file-icon";
3291
3487
  iconSpan.setAttribute("aria-hidden", "true");
3292
- iconSpan.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><path d="M14 3v6h6"/></svg>';
3488
+ iconSpan.innerHTML = FILE_ICON_SVG;
3293
3489
  const meta = document.createElement("span");
3294
3490
  meta.className = "aw-file-meta";
3295
3491
  const nameSpan = document.createElement("span");
3296
3492
  nameSpan.className = "aw-file-name";
3297
- nameSpan.textContent = (attrs == null ? void 0 : attrs.fileName) ?? "파일";
3493
+ nameSpan.textContent = (attrs == null ? void 0 : attrs.fileName) ?? FILE_FALLBACK_NAME;
3298
3494
  meta.appendChild(nameSpan);
3299
3495
  const infoParts = [];
3300
3496
  const ext = fileExtensionLabel(attrs == null ? void 0 : attrs.fileName, attrs == null ? void 0 : attrs.fileType);
@@ -3309,7 +3505,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3309
3505
  const action = document.createElement("span");
3310
3506
  action.className = "aw-file-action";
3311
3507
  action.setAttribute("aria-hidden", "true");
3312
- action.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>';
3508
+ action.innerHTML = FILE_ACTION_SVG;
3313
3509
  a.appendChild(iconSpan);
3314
3510
  a.appendChild(meta);
3315
3511
  a.appendChild(action);
@@ -3317,25 +3513,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3317
3513
  },
3318
3514
  isEmpty: (el) => !el.getAttribute("href")
3319
3515
  };
3320
- function formatBytes(bytes) {
3321
- if (bytes < 1024) return `${bytes} B`;
3322
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3323
- if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
3324
- return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;
3325
- }
3326
- function fileExtensionLabel(name, mime) {
3327
- if (name) {
3328
- const idx = name.lastIndexOf(".");
3329
- if (idx > 0 && idx < name.length - 1) {
3330
- return name.slice(idx + 1).toUpperCase();
3331
- }
3332
- }
3333
- if (mime) {
3334
- const sub = mime.split("/").pop() ?? "";
3335
- return sub.toUpperCase();
3336
- }
3337
- return "";
3338
- }
3339
3516
  const IMPORTED_HTML = {
3340
3517
  kind: "importedHtml",
3341
3518
  label: "HTML",
@@ -3370,7 +3547,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3370
3547
  allowsColor: false,
3371
3548
  allowsFontSize: false,
3372
3549
  matches: () => null,
3373
- create: () => fillEmpty(document.createElement("p")),
3550
+ // `<div>` 로 만든다. unknown 은 미래 버전의 블록이 강등되어 오는 자리이고,
3551
+ // 그 html 에 블록 레벨 요소가 들어 있을 수 있다. `<p>` 로 감싸면 브라우저가
3552
+ // 단락을 조기에 닫아 내용이 래퍼 밖으로 튀어나간다 (문자열 렌더러
3553
+ // render-string.ts 도 같은 이유로 div 를 쓴다 — 두 경로가 일치해야 한다).
3554
+ create: () => fillEmpty(document.createElement("div")),
3374
3555
  isEmpty: (el) => isElementBlank(el)
3375
3556
  };
3376
3557
  const BLOCK_TYPES = [
@@ -3400,7 +3581,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3400
3581
  }
3401
3582
  return { type: UNKNOWN, match: { kind: "unknown", attrs: {} } };
3402
3583
  }
3403
- const BLOCK_ID_ATTR = "data-block-id";
3404
3584
  class BlockIdRegistry {
3405
3585
  constructor() {
3406
3586
  __publicField(this, "counter", 0);
@@ -3457,6 +3637,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3457
3637
  }
3458
3638
  }
3459
3639
  function normalizeContent(root) {
3640
+ restoreAlignFromClasses(root);
3460
3641
  for (const node of Array.from(root.childNodes)) {
3461
3642
  if (node.nodeType !== Node.TEXT_NODE) continue;
3462
3643
  const text = node.textContent ?? "";
@@ -3531,13 +3712,25 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3531
3712
  }
3532
3713
  }
3533
3714
  function serializeBlock(block) {
3715
+ const style = block.element.getAttribute("style");
3534
3716
  return {
3717
+ version: SNAPSHOT_VERSION,
3535
3718
  id: block.id,
3536
3719
  kind: block.kind,
3537
3720
  attrs: { ...block.attrs },
3538
- html: block.element.innerHTML
3721
+ html: innerHtmlWithoutChrome(block.element),
3722
+ // 빈 문자열은 굳이 싣지 않는다 — 스냅샷이 커지기만 한다.
3723
+ ...style ? { style } : {}
3539
3724
  };
3540
3725
  }
3726
+ function innerHtmlWithoutChrome(element) {
3727
+ if (!element.querySelector(".aw-block-align-controls, .aw-resize-handle")) {
3728
+ return element.innerHTML;
3729
+ }
3730
+ const clone = element.cloneNode(true);
3731
+ stripEditorOnlyNodes(clone);
3732
+ return clone.innerHTML;
3733
+ }
3541
3734
  function serializeBlocks(blocks) {
3542
3735
  return blocks.map(serializeBlock);
3543
3736
  }
@@ -3545,6 +3738,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3545
3738
  const type = BLOCK_TYPE_BY_KIND[snapshot.kind] ?? BLOCK_TYPE_BY_KIND.unknown;
3546
3739
  const el = type.create(snapshot.attrs);
3547
3740
  el.setAttribute(BLOCK_ID_ATTR, snapshot.id);
3741
+ if (snapshot.style) el.setAttribute("style", snapshot.style);
3548
3742
  if (snapshot.html.length > 0) {
3549
3743
  el.innerHTML = snapshot.html;
3550
3744
  }
@@ -3552,7 +3746,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3552
3746
  }
3553
3747
  function renderBlocks(root, snapshots) {
3554
3748
  while (root.firstChild) root.removeChild(root.firstChild);
3555
- for (const snap of snapshots) {
3749
+ for (const snap of upgradeSnapshots(snapshots)) {
3556
3750
  root.appendChild(renderBlock(snap));
3557
3751
  }
3558
3752
  }
@@ -3888,6 +4082,57 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3888
4082
  }
3889
4083
  return out;
3890
4084
  }
4085
+ const ALIGN_CLASS_BY_VALUE = {
4086
+ left: "aw-align-left",
4087
+ center: "aw-align-center",
4088
+ right: "aw-align-right",
4089
+ justify: "aw-align-justify"
4090
+ };
4091
+ function applyBlockClassNames(root) {
4092
+ for (const child of Array.from(root.children)) {
4093
+ if (!(child instanceof HTMLElement)) continue;
4094
+ const { match } = classifyBlockElement(child);
4095
+ child.classList.add("aw-block", `aw-block-${match.kind}`);
4096
+ }
4097
+ for (const el of Array.from(root.querySelectorAll("[style]"))) {
4098
+ if (el.closest("[data-imported-html]")) continue;
4099
+ hoistTextAlignToClass(el);
4100
+ }
4101
+ }
4102
+ function hoistTextAlignToClass(el) {
4103
+ var _a;
4104
+ const value = el.style.textAlign.trim().toLowerCase();
4105
+ const className = ALIGN_CLASS_BY_VALUE[value];
4106
+ const stale = Object.values(ALIGN_CLASS_BY_VALUE).filter(
4107
+ (name) => name !== className && el.classList.contains(name)
4108
+ );
4109
+ for (const name of stale) el.classList.remove(name);
4110
+ if (!className) return;
4111
+ el.classList.add(className);
4112
+ el.style.removeProperty("text-align");
4113
+ if (((_a = el.getAttribute("style")) == null ? void 0 : _a.trim()) === "") el.removeAttribute("style");
4114
+ }
4115
+ function restoreAlignFromClasses(root) {
4116
+ var _a;
4117
+ for (const [value, className] of Object.entries(ALIGN_CLASS_BY_VALUE)) {
4118
+ for (const el of Array.from(
4119
+ root.querySelectorAll(`.${className}`)
4120
+ )) {
4121
+ if (!el.style.textAlign) el.style.textAlign = value;
4122
+ el.classList.remove(className);
4123
+ if (((_a = el.getAttribute("class")) == null ? void 0 : _a.trim()) === "") el.removeAttribute("class");
4124
+ }
4125
+ }
4126
+ }
4127
+ function stripEditorOnlyNodes(root) {
4128
+ const chrome = root.querySelectorAll(
4129
+ ".aw-block-align-controls, .aw-resize-handle"
4130
+ );
4131
+ for (const el of Array.from(chrome)) el.remove();
4132
+ for (const el of Array.from(root.querySelectorAll("[contenteditable]"))) {
4133
+ el.removeAttribute("contenteditable");
4134
+ }
4135
+ }
3891
4136
  function stripEditorOnlyAttrs(root) {
3892
4137
  for (const el of Array.from(root.querySelectorAll("[data-block-kind]"))) {
3893
4138
  el.removeAttribute("data-block-kind");
@@ -3982,6 +4227,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3982
4227
  codeBlock: "codeBlock"
3983
4228
  };
3984
4229
  const DRAG_MIME = "application/x-aeriz-block";
4230
+ const MIN_BLOCK_WIDTH = 80;
3985
4231
  class AerizEditor {
3986
4232
  constructor(options) {
3987
4233
  __publicField(this, "root");
@@ -4140,20 +4386,37 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4140
4386
  this.sync();
4141
4387
  }
4142
4388
  /**
4143
- * 본문 HTML 을 반환한다. 외부 컨텍스트에서도 에디터에서 보이는 그대로
4144
- * 동일한 시각으로 렌더되도록, 호출 시점의 테마 변수(--aw-primary 등) 를
4145
- * 읽어 블록 단위 인라인 스타일에 베이크한다. 에디터 내부 DOM 자체는
4146
- * 변형되지 않으며(clone 트리에 적용), data-block-kind / data-block-label /
4147
- * data-block-focused 같은 에디터 전용 속성은 출력에서 제거된다.
4389
+ * 본문 HTML 을 반환한다.
4390
+ *
4391
+ * 두 가지 스타일 전략을 지원한다 (`options.styles`, 기본값은 생성 시
4392
+ * `EditorOptions.htmlStyles`, 그것도 없으면 `'inline'`).
4393
+ *
4394
+ * - `'inline'` — 호출 시점의 테마 변수(--aw-primary 등) 를 읽어 블록 단위
4395
+ * 인라인 스타일에 베이크한다. 스타일시트 없이 어디에 붙여도(이메일 본문,
4396
+ * 다른 페이지) 에디터에서 보이는 그대로 렌더된다.
4397
+ * - `'class'` — 테마 스타일을 베이크하지 않고 `aw-block-*` / `aw-align-*`
4398
+ * 클래스만 남긴다. `style` 속성을 지우는 서버 sanitizer 아래에서 서식을
4399
+ * 보존해야 할 때 쓴다. 출력은 `aeriz-wysiwyg.min.css` 를 로드한 페이지의
4400
+ * `.aeriz-wysiwyg > .aw-content` 안에 넣는 것을 전제로 한다.
4401
+ *
4402
+ * 어느 쪽이든 에디터 내부 DOM 은 변형되지 않으며(clone 트리에 적용),
4403
+ * data-block-kind / data-block-focused 같은 에디터 전용 속성과 리사이즈
4404
+ * 핸들 · 정렬 컨트롤 같은 편집 보조 마크업은 출력에서 제거된다.
4148
4405
  * data-block-id 는 보존.
4149
4406
  */
4150
- getHTML() {
4407
+ getHTML(options = {}) {
4151
4408
  const clone = this.content.cloneNode(true);
4152
- const theme = readBlockStyleTheme(this.root);
4153
- const mode = this.currentTheme();
4154
- if (mode) theme.mode = mode;
4155
- applyBlockInlineStyles(clone, theme);
4409
+ const styles = options.styles ?? this.options.htmlStyles ?? "inline";
4410
+ if (styles === "class") {
4411
+ applyBlockClassNames(clone);
4412
+ } else {
4413
+ const theme = readBlockStyleTheme(this.root);
4414
+ const mode = this.currentTheme();
4415
+ if (mode) theme.mode = mode;
4416
+ applyBlockInlineStyles(clone, theme);
4417
+ }
4156
4418
  stripEditorOnlyAttrs(clone);
4419
+ stripEditorOnlyNodes(clone);
4157
4420
  return clone.innerHTML;
4158
4421
  }
4159
4422
  /** 가공 없는 원본 innerHTML. 직렬화 검증/디버그 용도. */
@@ -4817,15 +5080,20 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4817
5080
  * 이미지 / 유튜브 wrapper 의 너비를 드래그로 조정. 높이는 자동 (이미지는
4818
5081
  * 종횡비 유지, 유튜브는 CSS aspect-ratio 16/9). mouseup 에서 cursor 복귀
4819
5082
  * + flush.
5083
+ *
5084
+ * 너비는 본문 안쪽 폭을 상한으로 한다. 상한이 없으면 오른쪽으로 계속 끌 때
5085
+ * wrapper 가 본문보다 넓어지고, 그만큼 에디터 전체가 가로로 늘어난다
5086
+ * (드래그를 멈춰도 늘어난 상태가 남는다).
4820
5087
  */
4821
5088
  startBlockResize(block, startX) {
4822
5089
  const initialWidth = block.getBoundingClientRect().width;
5090
+ const maxWidth = this.contentInnerWidth();
4823
5091
  document.body.style.cursor = "nwse-resize";
4824
5092
  block.classList.add("aw-resizing");
4825
5093
  const onMove = (ev) => {
4826
5094
  const delta = ev.clientX - startX;
4827
- const newWidth = Math.max(80, initialWidth + delta);
4828
- block.style.width = `${Math.round(newWidth)}px`;
5095
+ const wanted = Math.max(MIN_BLOCK_WIDTH, initialWidth + delta);
5096
+ block.style.width = `${Math.round(Math.min(wanted, maxWidth))}px`;
4829
5097
  };
4830
5098
  const onUp = () => {
4831
5099
  document.removeEventListener("mousemove", onMove);
@@ -4837,6 +5105,15 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4837
5105
  document.addEventListener("mousemove", onMove);
4838
5106
  document.addEventListener("mouseup", onUp);
4839
5107
  }
5108
+ /**
5109
+ * 본문의 padding 을 제외한 실제 컨텐츠 폭. 블록 리사이즈의 상한이다.
5110
+ * 에디터가 아주 좁을 때도 최소 폭은 확보되도록 하한을 둔다.
5111
+ */
5112
+ contentInnerWidth() {
5113
+ const cs = getComputedStyle(this.content);
5114
+ const inner = this.content.clientWidth - (Number.parseFloat(cs.paddingLeft) || 0) - (Number.parseFloat(cs.paddingRight) || 0);
5115
+ return Math.max(MIN_BLOCK_WIDTH, inner);
5116
+ }
4840
5117
  currentTheme() {
4841
5118
  const t = this.root.dataset.theme;
4842
5119
  return t === "dark" || t === "light" ? t : void 0;
@@ -5332,7 +5609,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5332
5609
  positionHandle(handle, blockEl) {
5333
5610
  const blockRect = blockEl.getBoundingClientRect();
5334
5611
  const wrapRect = this.contentWrap.getBoundingClientRect();
5335
- const HANDLE_HEIGHT = 22;
5612
+ const HANDLE_HEIGHT = 18;
5336
5613
  const verticalShift = blockRect.height < HANDLE_HEIGHT ? (blockRect.height - HANDLE_HEIGHT) / 2 : 0;
5337
5614
  const top = blockRect.top - wrapRect.top + this.contentWrap.scrollTop + verticalShift;
5338
5615
  handle.style.top = `${top}px`;
@@ -5753,7 +6030,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5753
6030
  return false;
5754
6031
  }
5755
6032
  }
5756
- const BLOCK_LEVEL_TAGS = /* @__PURE__ */ new Set([
6033
+ const BLOCK_LEVEL_TAGS$1 = /* @__PURE__ */ new Set([
5757
6034
  "P",
5758
6035
  "DIV",
5759
6036
  "SECTION",
@@ -5786,7 +6063,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5786
6063
  "ADDRESS"
5787
6064
  ]);
5788
6065
  function isBlockLevelTag(tagName) {
5789
- return BLOCK_LEVEL_TAGS.has(tagName.toUpperCase());
6066
+ return BLOCK_LEVEL_TAGS$1.has(tagName.toUpperCase());
5790
6067
  }
5791
6068
  class AerizViewer {
5792
6069
  constructor(options) {
@@ -5794,10 +6071,17 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5794
6071
  __publicField(this, "content");
5795
6072
  __publicField(this, "blockIds", new BlockIdRegistry());
5796
6073
  __publicField(this, "idFor", (el) => this.blockIds.ensure(el));
6074
+ /**
6075
+ * 생성 시 받은 options 객체를 그대로 들고 있는다. `htmlStyles` 를 여기서
6076
+ * 매번 다시 읽으므로, 호출자가 이 객체를 갱신하면 재생성 없이 출력 전략이
6077
+ * 바뀐다 (AerizEditor 와 같은 규약).
6078
+ */
6079
+ __publicField(this, "options");
5797
6080
  __publicField(this, "destroyed", false);
5798
6081
  if (!(options.element instanceof HTMLElement)) {
5799
6082
  throw new TypeError("AerizViewer: options.element must be an HTMLElement");
5800
6083
  }
6084
+ this.options = options;
5801
6085
  this.root = document.createElement("div");
5802
6086
  this.root.className = "aeriz-wysiwyg aeriz-wysiwyg-viewer";
5803
6087
  if (options.theme) {
@@ -5836,16 +6120,25 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5836
6120
  return findBlockFrom(node, this.content, this.idFor);
5837
6121
  }
5838
6122
  /**
5839
- * 외부 컨텍스트(이메일, 다른 페이지의 HTML 영역 등) 에서도 동일한 시각으로
5840
- * 보이도록 portable 인라인 스타일을 입힌 자급자족적 HTML 을 반환한다.
6123
+ * 본문 HTML 을 반환한다. 전략은 `AerizEditor.getHTML()` 과 동일하다.
6124
+ *
6125
+ * - `'inline'` (기본) — 외부 컨텍스트(이메일, 다른 페이지의 HTML 영역 등)
6126
+ * 에서도 동일한 시각으로 보이도록 portable 인라인 스타일을 베이크한다.
6127
+ * - `'class'` — 클래스만 남긴다. `style` 속성을 지우는 sanitizer 대응.
5841
6128
  */
5842
- getHTML() {
6129
+ getHTML(options = {}) {
5843
6130
  const clone = this.content.cloneNode(true);
5844
- const theme = readBlockStyleTheme(this.root);
5845
- const mode = this.root.dataset.theme;
5846
- if (mode === "light" || mode === "dark") theme.mode = mode;
5847
- applyBlockInlineStyles(clone, theme);
6131
+ const styles = options.styles ?? this.options.htmlStyles ?? "inline";
6132
+ if (styles === "class") {
6133
+ applyBlockClassNames(clone);
6134
+ } else {
6135
+ const theme = readBlockStyleTheme(this.root);
6136
+ const mode = this.root.dataset.theme;
6137
+ if (mode === "light" || mode === "dark") theme.mode = mode;
6138
+ applyBlockInlineStyles(clone, theme);
6139
+ }
5848
6140
  stripEditorOnlyAttrs(clone);
6141
+ stripEditorOnlyNodes(clone);
5849
6142
  return clone.innerHTML;
5850
6143
  }
5851
6144
  setTheme(theme) {
@@ -5860,6 +6153,716 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5860
6153
  return listBlocks(this.content, this.idFor);
5861
6154
  }
5862
6155
  }
6156
+ const VOID_TAGS = /* @__PURE__ */ new Set([
6157
+ "area",
6158
+ "base",
6159
+ "br",
6160
+ "col",
6161
+ "embed",
6162
+ "hr",
6163
+ "img",
6164
+ "input",
6165
+ "link",
6166
+ "meta",
6167
+ "param",
6168
+ "source",
6169
+ "track",
6170
+ "wbr"
6171
+ ]);
6172
+ const DROPPED_RAW_TAGS = /* @__PURE__ */ new Set(["script", "style"]);
6173
+ const IMPLIED_END = /* @__PURE__ */ new Map([
6174
+ ["li", /* @__PURE__ */ new Set(["li"])],
6175
+ ["p", /* @__PURE__ */ new Set(["p", "div", "ul", "ol", "blockquote", "pre", "table", "h1", "h2", "h3", "h4", "h5", "h6"])],
6176
+ ["td", /* @__PURE__ */ new Set(["td", "th", "tr"])],
6177
+ ["th", /* @__PURE__ */ new Set(["td", "th", "tr"])],
6178
+ ["tr", /* @__PURE__ */ new Set(["tr"])],
6179
+ ["option", /* @__PURE__ */ new Set(["option"])]
6180
+ ]);
6181
+ const NAMED_ENTITIES = {
6182
+ amp: "&",
6183
+ lt: "<",
6184
+ gt: ">",
6185
+ quot: '"',
6186
+ apos: "'",
6187
+ nbsp: " ",
6188
+ ensp: " ",
6189
+ emsp: " ",
6190
+ thinsp: " ",
6191
+ zwnj: "‌",
6192
+ zwj: "‍",
6193
+ hellip: "…",
6194
+ mdash: "—",
6195
+ ndash: "–",
6196
+ lsquo: "‘",
6197
+ rsquo: "’",
6198
+ ldquo: "“",
6199
+ rdquo: "”",
6200
+ middot: "·",
6201
+ bull: "•",
6202
+ copy: "©",
6203
+ reg: "®",
6204
+ trade: "™",
6205
+ deg: "°",
6206
+ plusmn: "±",
6207
+ times: "×",
6208
+ divide: "÷",
6209
+ laquo: "«",
6210
+ raquo: "»"
6211
+ };
6212
+ function decodeEntities(input) {
6213
+ if (!input.includes("&")) return input;
6214
+ return input.replace(
6215
+ /&(#[Xx][0-9A-Fa-f]+|#\d+|[A-Za-z][A-Za-z0-9]{1,31});/g,
6216
+ (whole, body) => {
6217
+ if (body.charCodeAt(0) === 35) {
6218
+ const isHex = body[1] === "x" || body[1] === "X";
6219
+ const digits = isHex ? body.slice(2) : body.slice(1);
6220
+ const code = Number.parseInt(digits, isHex ? 16 : 10);
6221
+ if (!Number.isFinite(code) || code < 0 || code > 1114111) return whole;
6222
+ if (code >= 55296 && code <= 57343) return whole;
6223
+ try {
6224
+ return String.fromCodePoint(code);
6225
+ } catch {
6226
+ return whole;
6227
+ }
6228
+ }
6229
+ const named = NAMED_ENTITIES[body.toLowerCase()];
6230
+ return named ?? whole;
6231
+ }
6232
+ );
6233
+ }
6234
+ function escapeHtml(input) {
6235
+ return input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
6236
+ }
6237
+ function escapeAttr(input) {
6238
+ return escapeHtml(input).replace(/"/g, "&quot;");
6239
+ }
6240
+ function parseAttrs(source) {
6241
+ const attrs = {};
6242
+ const re = /([^\s/=>"']+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g;
6243
+ let m;
6244
+ while ((m = re.exec(source)) !== null) {
6245
+ const name = m[1];
6246
+ if (!name) continue;
6247
+ const raw = m[2] ?? m[3] ?? m[4] ?? "";
6248
+ attrs[name.toLowerCase()] = decodeEntities(raw);
6249
+ }
6250
+ return attrs;
6251
+ }
6252
+ function parseHtmlFragment(html) {
6253
+ const root = [];
6254
+ const stack = [];
6255
+ const childrenOfTop = () => {
6256
+ const top = stack[stack.length - 1];
6257
+ return top ? top.children : root;
6258
+ };
6259
+ const pushText = (value) => {
6260
+ if (value.length === 0) return;
6261
+ const target = childrenOfTop();
6262
+ const last = target[target.length - 1];
6263
+ if (last && last.type === "text") last.value += value;
6264
+ else target.push({ type: "text", value });
6265
+ };
6266
+ const closeTag = (tag) => {
6267
+ var _a;
6268
+ for (let i = stack.length - 1; i >= 0; i--) {
6269
+ if (((_a = stack[i]) == null ? void 0 : _a.tag) === tag) {
6270
+ stack.length = i;
6271
+ return;
6272
+ }
6273
+ }
6274
+ };
6275
+ let cursor = 0;
6276
+ while (cursor < html.length) {
6277
+ const lt = html.indexOf("<", cursor);
6278
+ if (lt < 0) {
6279
+ pushText(decodeEntities(html.slice(cursor)));
6280
+ break;
6281
+ }
6282
+ if (lt > cursor) pushText(decodeEntities(html.slice(cursor, lt)));
6283
+ if (html.startsWith("<!--", lt)) {
6284
+ const end = html.indexOf("-->", lt + 4);
6285
+ cursor = end < 0 ? html.length : end + 3;
6286
+ continue;
6287
+ }
6288
+ if (html.startsWith("<!", lt) || html.startsWith("<?", lt)) {
6289
+ const end = html.indexOf(">", lt + 2);
6290
+ cursor = end < 0 ? html.length : end + 1;
6291
+ continue;
6292
+ }
6293
+ const gt = html.indexOf(">", lt + 1);
6294
+ if (gt < 0) {
6295
+ pushText(decodeEntities(html.slice(lt)));
6296
+ break;
6297
+ }
6298
+ const inner = html.slice(lt + 1, gt);
6299
+ cursor = gt + 1;
6300
+ if (inner.startsWith("/")) {
6301
+ const tag2 = inner.slice(1).trim().toLowerCase();
6302
+ if (tag2) closeTag(tag2);
6303
+ continue;
6304
+ }
6305
+ const nameMatch = /^([A-Za-z][^\s/>]*)/.exec(inner);
6306
+ if (!nameMatch || !nameMatch[1]) {
6307
+ pushText(decodeEntities(html.slice(lt, cursor)));
6308
+ continue;
6309
+ }
6310
+ const tag = nameMatch[1].toLowerCase();
6311
+ const rest = inner.slice(nameMatch[1].length);
6312
+ const selfClosing = /\/\s*$/.test(rest);
6313
+ const attrs = parseAttrs(selfClosing ? rest.replace(/\/\s*$/, "") : rest);
6314
+ if (DROPPED_RAW_TAGS.has(tag)) {
6315
+ const closeRe = new RegExp(`</${tag}\\s*>`, "i");
6316
+ const remainder = html.slice(cursor);
6317
+ const found = closeRe.exec(remainder);
6318
+ cursor = found ? cursor + found.index + found[0].length : html.length;
6319
+ continue;
6320
+ }
6321
+ const top = stack[stack.length - 1];
6322
+ if (top) {
6323
+ const closers = IMPLIED_END.get(top.tag);
6324
+ if (closers == null ? void 0 : closers.has(tag)) stack.pop();
6325
+ }
6326
+ const node = { type: "element", tag, attrs, children: [] };
6327
+ childrenOfTop().push(node);
6328
+ if (!selfClosing && !VOID_TAGS.has(tag)) stack.push(node);
6329
+ }
6330
+ return root;
6331
+ }
6332
+ function serializeNodes(nodes) {
6333
+ let out = "";
6334
+ for (const node of nodes) {
6335
+ if (node.type === "text") {
6336
+ out += escapeHtml(node.value);
6337
+ continue;
6338
+ }
6339
+ const attrs = Object.entries(node.attrs).map(([k, v]) => v === "" ? ` ${k}` : ` ${k}="${escapeAttr(v)}"`).join("");
6340
+ if (VOID_TAGS.has(node.tag)) {
6341
+ out += `<${node.tag}${attrs}>`;
6342
+ continue;
6343
+ }
6344
+ out += `<${node.tag}${attrs}>${serializeNodes(node.children)}</${node.tag}>`;
6345
+ }
6346
+ return out;
6347
+ }
6348
+ function textOf(nodes) {
6349
+ let out = "";
6350
+ for (const node of nodes) {
6351
+ if (node.type === "text") {
6352
+ out += node.value;
6353
+ continue;
6354
+ }
6355
+ if (node.tag === "br") {
6356
+ out += "\n";
6357
+ continue;
6358
+ }
6359
+ out += textOf(node.children);
6360
+ }
6361
+ return out;
6362
+ }
6363
+ const BLOCK_LEVEL_TAGS = /* @__PURE__ */ new Set([
6364
+ "address",
6365
+ "article",
6366
+ "aside",
6367
+ "blockquote",
6368
+ "div",
6369
+ "dd",
6370
+ "dl",
6371
+ "dt",
6372
+ "fieldset",
6373
+ "figcaption",
6374
+ "figure",
6375
+ "footer",
6376
+ "form",
6377
+ "h1",
6378
+ "h2",
6379
+ "h3",
6380
+ "h4",
6381
+ "h5",
6382
+ "h6",
6383
+ "header",
6384
+ "hr",
6385
+ "li",
6386
+ "main",
6387
+ "nav",
6388
+ "ol",
6389
+ "p",
6390
+ "pre",
6391
+ "section",
6392
+ "table",
6393
+ "tbody",
6394
+ "td",
6395
+ "tfoot",
6396
+ "th",
6397
+ "thead",
6398
+ "tr",
6399
+ "ul"
6400
+ ]);
6401
+ function textWithBlockBreaks(nodes) {
6402
+ let out = "";
6403
+ for (const node of nodes) {
6404
+ if (node.type === "text") {
6405
+ out += node.value;
6406
+ continue;
6407
+ }
6408
+ if (node.tag === "br") {
6409
+ out += "\n";
6410
+ continue;
6411
+ }
6412
+ const isBlock = BLOCK_LEVEL_TAGS.has(node.tag);
6413
+ if (isBlock && out !== "" && !out.endsWith("\n")) out += "\n";
6414
+ out += textWithBlockBreaks(node.children);
6415
+ if (isBlock && !out.endsWith("\n")) out += "\n";
6416
+ }
6417
+ return out;
6418
+ }
6419
+ function childElements(nodes, tag) {
6420
+ return nodes.filter(
6421
+ (n) => n.type === "element" && n.tag === tag
6422
+ );
6423
+ }
6424
+ function renderBlocksToHTML(snapshots, options = {}) {
6425
+ const withIds = options.blockIds !== false;
6426
+ const body = upgradeSnapshots(snapshots).map((snap) => renderNormalizedBlock(snap, { blockIds: withIds })).join("");
6427
+ if (options.wrapper !== "content") return body;
6428
+ const themeAttr = options.theme ? ` data-theme="${options.theme}"` : "";
6429
+ return `<div class="aeriz-wysiwyg aeriz-wysiwyg-viewer"${themeAttr}><div class="aw-content-wrap"><div class="aw-content">${body}</div></div></div>`;
6430
+ }
6431
+ function renderBlockToHTML(snapshot, options = {}) {
6432
+ return renderNormalizedBlock(upgradeSnapshot(snapshot), options);
6433
+ }
6434
+ function renderNormalizedBlock(snapshot, options = {}) {
6435
+ const idAttr = options.blockIds === false ? "" : ` ${BLOCK_ID_ATTR}="${escapeAttr(snapshot.id)}"`;
6436
+ const inner = snapshot.html;
6437
+ const styleAttr = snapshot.style ? ` style="${escapeAttr(snapshot.style)}"` : "";
6438
+ switch (snapshot.kind) {
6439
+ case "paragraph":
6440
+ return `<p${idAttr}${styleAttr}>${inner || "<br>"}</p>`;
6441
+ case "unknown":
6442
+ return `<div${idAttr}${styleAttr}>${inner || "<br>"}</div>`;
6443
+ case "heading": {
6444
+ const level = clampHeadingLevel(snapshot.attrs.level);
6445
+ return `<h${level}${idAttr}${styleAttr}>${inner || "<br>"}</h${level}>`;
6446
+ }
6447
+ case "bulletList":
6448
+ return `<ul${idAttr}${styleAttr}>${inner || "<li><br></li>"}</ul>`;
6449
+ case "orderedList":
6450
+ return `<ol${idAttr}${styleAttr}>${inner || "<li><br></li>"}</ol>`;
6451
+ case "blockquote":
6452
+ return `<blockquote${idAttr}${styleAttr}>${inner || "<p><br></p>"}</blockquote>`;
6453
+ case "codeBlock": {
6454
+ if (inner) return `<pre${idAttr}${styleAttr}>${inner}</pre>`;
6455
+ const lang = snapshot.attrs.language;
6456
+ const langAttr = lang ? ` data-language="${escapeAttr(lang)}"` : "";
6457
+ return `<pre${idAttr}${styleAttr}><code${langAttr}></code></pre>`;
6458
+ }
6459
+ case "table":
6460
+ return `<table${idAttr}${styleAttr}>${inner || emptyTableBody(snapshot.attrs.rows, snapshot.attrs.cols)}</table>`;
6461
+ case "hr":
6462
+ return `<hr${idAttr}${styleAttr}>`;
6463
+ case "image":
6464
+ return inner ? `<figure${idAttr}${styleAttr}>${stripChrome(inner)}</figure>` : renderImage(snapshot.attrs, idAttr, styleAttr);
6465
+ case "youtube": {
6466
+ if (!inner) return renderYoutube(snapshot.attrs, idAttr, styleAttr);
6467
+ const videoId = snapshot.attrs.videoId;
6468
+ const idDataAttr = videoId ? ` data-youtube-id="${escapeAttr(videoId)}"` : "";
6469
+ return `<div class="aw-youtube-embed"${idDataAttr}${idAttr}${styleAttr}>${stripChrome(inner)}</div>`;
6470
+ }
6471
+ case "linkCard":
6472
+ return renderLinkCard(snapshot.attrs, idAttr, styleAttr, inner);
6473
+ case "file":
6474
+ return renderFile(snapshot.attrs, idAttr, styleAttr, inner);
6475
+ case "importedHtml": {
6476
+ const style = snapshot.attrs.importedStyle;
6477
+ const cls = snapshot.attrs.importedClass;
6478
+ const importedStyle = snapshot.style ? styleAttr : style ? ` style="${escapeAttr(style)}"` : "";
6479
+ const classAttr = cls ? ` class="${escapeAttr(cls)}"` : "";
6480
+ return `<div data-imported-html="true"${classAttr}${importedStyle}${idAttr}>${inner}</div>`;
6481
+ }
6482
+ default: {
6483
+ return "";
6484
+ }
6485
+ }
6486
+ }
6487
+ const CHROME_CLASS = /aw-block-align-controls|aw-resize-handle/;
6488
+ function stripChrome(html) {
6489
+ if (!CHROME_CLASS.test(html)) return html;
6490
+ const kept = parseHtmlFragment(html).filter(
6491
+ (node) => !(node.type === "element" && CHROME_CLASS.test(node.attrs["class"] ?? ""))
6492
+ );
6493
+ return serializeNodes(kept);
6494
+ }
6495
+ function clampHeadingLevel(level) {
6496
+ if (level === void 0) return 1;
6497
+ if (level < 1) return 1;
6498
+ if (level > 6) return 6;
6499
+ return level;
6500
+ }
6501
+ function emptyTableBody(rows, cols) {
6502
+ const r = Math.max(1, Number.isFinite(rows) ? rows : 1);
6503
+ const c = Math.max(1, Number.isFinite(cols) ? cols : 1);
6504
+ const cells = "<td><br></td>".repeat(c);
6505
+ return `<tbody>${`<tr>${cells}</tr>`.repeat(r)}</tbody>`;
6506
+ }
6507
+ function renderImage(attrs, idAttr, blockStyleAttr) {
6508
+ const styleAttr = blockStyleAttr || (attrs.wrapperStyle ? ` style="${escapeAttr(attrs.wrapperStyle)}"` : "");
6509
+ const srcAttr = attrs.src ? ` src="${escapeAttr(attrs.src)}"` : "";
6510
+ const altAttr = ` alt="${escapeAttr(attrs.alt ?? "")}"`;
6511
+ return `<figure${idAttr}${styleAttr}><img${srcAttr}${altAttr}></figure>`;
6512
+ }
6513
+ function renderYoutube(attrs, idAttr, blockStyleAttr) {
6514
+ const videoId = attrs.videoId ?? "";
6515
+ const styleAttr = blockStyleAttr || (attrs.wrapperStyle ? ` style="${escapeAttr(attrs.wrapperStyle)}"` : "");
6516
+ const idDataAttr = videoId ? ` data-youtube-id="${escapeAttr(videoId)}"` : "";
6517
+ const srcAttr = videoId ? ` src="${escapeAttr(`${YOUTUBE_EMBED_BASE}${videoId}`)}"` : "";
6518
+ return `<div class="aw-youtube-embed"${idDataAttr}${idAttr}${styleAttr}><iframe${srcAttr} allow="${escapeAttr(YOUTUBE_IFRAME_ALLOW)}" allowfullscreen frameborder="0" referrerpolicy="strict-origin-when-cross-origin"></iframe></div>`;
6519
+ }
6520
+ function renderLinkCard(attrs, idAttr, styleAttr, inner) {
6521
+ const href = attrs.href ?? "#";
6522
+ const title = attrs.text && attrs.text.length > 0 ? attrs.text : hostnameFromUrl(href) || href;
6523
+ const description = attrs.description ?? "";
6524
+ const image = attrs.image ?? "";
6525
+ const favicon = attrs.favicon ?? defaultFaviconUrl(href);
6526
+ const classes = image ? "aw-link-card aw-link-card-with-image" : "aw-link-card";
6527
+ const open = `<a class="${classes}" href="${escapeAttr(href)}" target="_blank" rel="noopener"${idAttr}${styleAttr}>`;
6528
+ if (inner) return `${open}${stripChrome(inner)}</a>`;
6529
+ let out = open;
6530
+ if (image) {
6531
+ out += `<img class="aw-link-card-image" src="${escapeAttr(image)}" alt="" loading="lazy">`;
6532
+ }
6533
+ out += '<span class="aw-link-card-body">';
6534
+ out += `<span class="aw-link-card-title">${escapeHtml(title)}</span>`;
6535
+ if (description) {
6536
+ out += `<span class="aw-link-card-desc">${escapeHtml(description)}</span>`;
6537
+ }
6538
+ out += '<span class="aw-link-card-url">';
6539
+ if (favicon) {
6540
+ out += `<img class="aw-link-card-favicon" src="${escapeAttr(favicon)}" alt="" loading="lazy">`;
6541
+ }
6542
+ out += `<span class="aw-link-card-url-text">${escapeHtml(href)}</span>`;
6543
+ out += "</span></span></a>";
6544
+ return out;
6545
+ }
6546
+ function renderFile(attrs, idAttr, styleAttr, inner) {
6547
+ const hrefAttr = attrs.fileSrc ? ` href="${escapeAttr(attrs.fileSrc)}"` : "";
6548
+ const nameAttr = attrs.fileName ? ` data-file-name="${escapeAttr(attrs.fileName)}"` : "";
6549
+ const sizeAttr = attrs.fileSize !== void 0 ? ` data-file-size="${escapeAttr(String(attrs.fileSize))}"` : "";
6550
+ const typeAttr = attrs.fileType ? ` data-file-type="${escapeAttr(attrs.fileType)}"` : "";
6551
+ const downloadAttr = ` download="${escapeAttr(attrs.fileName ?? "")}"`;
6552
+ const infoParts = [];
6553
+ const ext = fileExtensionLabel(attrs.fileName, attrs.fileType);
6554
+ if (ext) infoParts.push(ext);
6555
+ if (attrs.fileSize !== void 0) infoParts.push(formatBytes(attrs.fileSize));
6556
+ const open = `<a class="aw-file-attachment" target="_blank" rel="noopener"${hrefAttr}${downloadAttr}${nameAttr}${sizeAttr}${typeAttr}${idAttr}${styleAttr}>`;
6557
+ if (inner) return `${open}${stripChrome(inner)}</a>`;
6558
+ let out = open;
6559
+ out += `<span class="aw-file-icon" aria-hidden="true">${FILE_ICON_SVG}</span>`;
6560
+ out += '<span class="aw-file-meta">';
6561
+ out += `<span class="aw-file-name">${escapeHtml(attrs.fileName ?? FILE_FALLBACK_NAME)}</span>`;
6562
+ if (infoParts.length > 0) {
6563
+ out += `<span class="aw-file-info">${escapeHtml(infoParts.join(" · "))}</span>`;
6564
+ }
6565
+ out += "</span>";
6566
+ out += `<span class="aw-file-action" aria-hidden="true">${FILE_ACTION_SVG}</span>`;
6567
+ out += "</a>";
6568
+ return out;
6569
+ }
6570
+ const INLINE_ESCAPE = /([\\`*_[\]<])/g;
6571
+ const LINE_START_ESCAPE = /^([#>|+-]|\d+\.)/;
6572
+ function blocksToMarkdown(snapshots, options = {}) {
6573
+ const bullet = options.bullet ?? "-";
6574
+ const gfmTables = options.gfmTables !== false;
6575
+ const chunks = [];
6576
+ for (const snap of upgradeSnapshots(snapshots)) {
6577
+ const chunk = blockToMarkdown(snap, bullet, gfmTables);
6578
+ if (chunk !== "") chunks.push(chunk);
6579
+ }
6580
+ return chunks.length > 0 ? `${chunks.join("\n\n")}
6581
+ ` : "";
6582
+ }
6583
+ function blockToMarkdown(snapshot, bullet, gfmTables) {
6584
+ var _a, _b, _c;
6585
+ const nodes = parseHtmlFragment(snapshot.html);
6586
+ switch (snapshot.kind) {
6587
+ case "paragraph":
6588
+ case "unknown": {
6589
+ const text = inlineToMarkdown(nodes).trim();
6590
+ return text === "" ? "" : escapeLineStart(text);
6591
+ }
6592
+ case "heading": {
6593
+ const level = Math.min(6, Math.max(1, snapshot.attrs.level ?? 1));
6594
+ const text = inlineToMarkdown(nodes).trim();
6595
+ return text === "" ? "" : `${"#".repeat(level)} ${text}`;
6596
+ }
6597
+ case "bulletList":
6598
+ return listToMarkdown(nodes, { ordered: false, bullet, depth: 0 });
6599
+ case "orderedList":
6600
+ return listToMarkdown(nodes, { ordered: true, bullet, depth: 0 });
6601
+ case "blockquote": {
6602
+ const paragraphs = childElements(nodes, "p");
6603
+ const source = paragraphs.length > 0 ? paragraphs.map((p) => inlineToMarkdown(p.children).trim()) : [inlineToMarkdown(nodes).trim()];
6604
+ const lines = source.filter((line) => line !== "").join("\n\n").split("\n").map((line) => line === "" ? ">" : `> ${line}`);
6605
+ return lines.length > 0 && lines[0] !== void 0 ? lines.join("\n") : "";
6606
+ }
6607
+ case "codeBlock": {
6608
+ const code = childElements(nodes, "code")[0];
6609
+ const body = textOf(code ? code.children : nodes).replace(/\n$/, "");
6610
+ const lang = snapshot.attrs.language ?? (code == null ? void 0 : code.attrs["data-language"]) ?? "";
6611
+ const fence = "`".repeat(Math.max(3, longestBacktickRun(body) + 1));
6612
+ return `${fence}${lang}
6613
+ ${body}
6614
+ ${fence}`;
6615
+ }
6616
+ case "image": {
6617
+ const src = snapshot.attrs.src ?? "";
6618
+ if (src === "") return "";
6619
+ return `![${escapeInline(snapshot.attrs.alt ?? "")}](${encodeUrl(src)})`;
6620
+ }
6621
+ case "table":
6622
+ return gfmTables ? tableToMarkdown(nodes) : `<table>${snapshot.html}</table>`;
6623
+ case "hr":
6624
+ return "---";
6625
+ case "linkCard": {
6626
+ const href = snapshot.attrs.href ?? "";
6627
+ if (href === "") return "";
6628
+ const label = ((_a = snapshot.attrs.text) == null ? void 0 : _a.trim()) || href;
6629
+ const line = `[${escapeInline(label)}](${encodeUrl(href)})`;
6630
+ const desc = (_b = snapshot.attrs.description) == null ? void 0 : _b.trim();
6631
+ return desc ? `${line}
6632
+
6633
+ ${escapeLineStart(escapeInline(desc))}` : line;
6634
+ }
6635
+ case "youtube": {
6636
+ const videoId = snapshot.attrs.videoId;
6637
+ if (!videoId) return "";
6638
+ return `[YouTube](${YOUTUBE_EMBED_BASE}${encodeURIComponent(videoId)})`;
6639
+ }
6640
+ case "file": {
6641
+ const src = snapshot.attrs.fileSrc ?? "";
6642
+ if (src === "") return "";
6643
+ const name = ((_c = snapshot.attrs.fileName) == null ? void 0 : _c.trim()) || src;
6644
+ return `[${escapeInline(name)}](${encodeUrl(src)})`;
6645
+ }
6646
+ case "importedHtml":
6647
+ return snapshot.html.trim();
6648
+ default: {
6649
+ return "";
6650
+ }
6651
+ }
6652
+ }
6653
+ function listToMarkdown(nodes, ctx) {
6654
+ const items = childElements(nodes, "li");
6655
+ if (items.length === 0) return "";
6656
+ const indent = " ".repeat(ctx.depth);
6657
+ const lines = [];
6658
+ items.forEach((li, index) => {
6659
+ const inline = [];
6660
+ const nested = [];
6661
+ for (const child of li.children) {
6662
+ if (child.type === "element" && (child.tag === "ul" || child.tag === "ol")) {
6663
+ nested.push(child);
6664
+ } else {
6665
+ inline.push(child);
6666
+ }
6667
+ }
6668
+ const marker = ctx.ordered ? `${index + 1}.` : ctx.bullet;
6669
+ const text = inlineToMarkdown(inline).trim();
6670
+ const continuation = `${indent}${" ".repeat(marker.length + 1)}`;
6671
+ const body = text.split("\n").join(`
6672
+ ${continuation}`);
6673
+ lines.push(`${indent}${marker} ${body}`);
6674
+ for (const sub of nested) {
6675
+ const subMd = listToMarkdown(sub.children, {
6676
+ ordered: sub.tag === "ol",
6677
+ bullet: ctx.bullet,
6678
+ depth: ctx.depth + 1
6679
+ });
6680
+ if (subMd !== "") lines.push(subMd);
6681
+ }
6682
+ });
6683
+ return lines.join("\n");
6684
+ }
6685
+ function tableToMarkdown(nodes) {
6686
+ const rows = [];
6687
+ const collectRows = (list) => {
6688
+ for (const node of list) {
6689
+ if (node.type !== "element") continue;
6690
+ if (node.tag === "tr") {
6691
+ rows.push(node);
6692
+ continue;
6693
+ }
6694
+ if (node.tag === "tbody" || node.tag === "thead" || node.tag === "tfoot") {
6695
+ collectRows(node.children);
6696
+ }
6697
+ }
6698
+ };
6699
+ collectRows(nodes);
6700
+ if (rows.length === 0) return "";
6701
+ const grid = rows.map(
6702
+ (tr) => tr.children.filter(
6703
+ (c) => c.type === "element" && (c.tag === "td" || c.tag === "th")
6704
+ ).map((cell) => cellToMarkdown(cell.children))
6705
+ );
6706
+ const width = grid.reduce((max, row) => Math.max(max, row.length), 0);
6707
+ if (width === 0) return "";
6708
+ const pad = (row) => row.length === width ? row : [...row, ...Array(width - row.length).fill("")];
6709
+ const [head, ...body] = grid.map(pad);
6710
+ const headRow = head ?? Array(width).fill("");
6711
+ const lines = [
6712
+ `| ${headRow.join(" | ")} |`,
6713
+ `| ${Array(width).fill("---").join(" | ")} |`,
6714
+ ...body.map((row) => `| ${row.join(" | ")} |`)
6715
+ ];
6716
+ return lines.join("\n");
6717
+ }
6718
+ function cellToMarkdown(nodes) {
6719
+ return inlineToMarkdown(nodes, { inTableCell: true }).replace(/\n+/g, "<br>").replace(/\|/g, "\\|").trim();
6720
+ }
6721
+ function inlineToMarkdown(nodes, ctx = {}) {
6722
+ let out = "";
6723
+ for (const node of nodes) {
6724
+ if (node.type === "text") {
6725
+ out += escapeInline(node.value);
6726
+ continue;
6727
+ }
6728
+ const children = () => inlineToMarkdown(node.children, ctx);
6729
+ switch (node.tag) {
6730
+ case "br":
6731
+ out += ctx.inTableCell ? "\n" : " \n";
6732
+ break;
6733
+ case "b":
6734
+ case "strong": {
6735
+ const inner = children();
6736
+ out += inner.trim() === "" ? inner : `**${inner}**`;
6737
+ break;
6738
+ }
6739
+ case "i":
6740
+ case "em": {
6741
+ const inner = children();
6742
+ out += inner.trim() === "" ? inner : `*${inner}*`;
6743
+ break;
6744
+ }
6745
+ case "s":
6746
+ case "del":
6747
+ case "strike": {
6748
+ const inner = children();
6749
+ out += inner.trim() === "" ? inner : `~~${inner}~~`;
6750
+ break;
6751
+ }
6752
+ case "code": {
6753
+ const raw = textOf(node.children);
6754
+ const fence = "`".repeat(longestBacktickRun(raw) + 1);
6755
+ const padding = raw.startsWith("`") || raw.endsWith("`") ? " " : "";
6756
+ out += `${fence}${padding}${raw}${padding}${fence}`;
6757
+ break;
6758
+ }
6759
+ case "a": {
6760
+ const href = node.attrs["href"];
6761
+ const inner = children();
6762
+ out += href ? `[${inner}](${encodeUrl(href)})` : inner;
6763
+ break;
6764
+ }
6765
+ case "img": {
6766
+ const src = node.attrs["src"];
6767
+ if (src) out += `![${escapeInline(node.attrs["alt"] ?? "")}](${encodeUrl(src)})`;
6768
+ break;
6769
+ }
6770
+ case "u":
6771
+ case "sub":
6772
+ case "sup":
6773
+ case "mark":
6774
+ out += `<${node.tag}>${children()}</${node.tag}>`;
6775
+ break;
6776
+ default:
6777
+ out += children();
6778
+ break;
6779
+ }
6780
+ }
6781
+ return out;
6782
+ }
6783
+ function escapeInline(text) {
6784
+ return text.replace(INLINE_ESCAPE, "\\$1");
6785
+ }
6786
+ function escapeLineStart(text) {
6787
+ return text.replace(LINE_START_ESCAPE, "\\$1");
6788
+ }
6789
+ function longestBacktickRun(text) {
6790
+ let longest = 0;
6791
+ let current = 0;
6792
+ for (const ch of text) {
6793
+ if (ch === "`") {
6794
+ current += 1;
6795
+ if (current > longest) longest = current;
6796
+ } else {
6797
+ current = 0;
6798
+ }
6799
+ }
6800
+ return longest;
6801
+ }
6802
+ function encodeUrl(url) {
6803
+ const trimmed = url.trim();
6804
+ if (/[\s()]/.test(trimmed)) {
6805
+ return `<${trimmed.replace(/[<>]/g, "")}>`;
6806
+ }
6807
+ return trimmed;
6808
+ }
6809
+ function blocksToPlainText(snapshots, options = {}) {
6810
+ const blockSeparator = options.blockSeparator ?? "\n\n";
6811
+ const cellSeparator = options.cellSeparator ?? " ";
6812
+ const chunks = [];
6813
+ for (const snap of upgradeSnapshots(snapshots)) {
6814
+ const text = blockToPlainText(snap, cellSeparator);
6815
+ if (text !== "") chunks.push(text);
6816
+ }
6817
+ return chunks.join(blockSeparator);
6818
+ }
6819
+ function blockToPlainText(snapshot, cellSeparator) {
6820
+ switch (snapshot.kind) {
6821
+ case "hr":
6822
+ case "youtube":
6823
+ return "";
6824
+ case "image":
6825
+ return normalize(snapshot.attrs.alt ?? "");
6826
+ case "linkCard":
6827
+ return normalize(snapshot.attrs.text ?? snapshot.attrs.href ?? "");
6828
+ case "file":
6829
+ return normalize(snapshot.attrs.fileName ?? "");
6830
+ case "bulletList":
6831
+ case "orderedList": {
6832
+ const items = childElements(parseHtmlFragment(snapshot.html), "li");
6833
+ return items.map((li) => normalize(textWithBlockBreaks(li.children))).filter((line) => line !== "").join("\n");
6834
+ }
6835
+ case "table":
6836
+ return tableToPlainText(parseHtmlFragment(snapshot.html), cellSeparator);
6837
+ default:
6838
+ return normalize(textWithBlockBreaks(parseHtmlFragment(snapshot.html)));
6839
+ }
6840
+ }
6841
+ function tableToPlainText(nodes, cellSeparator) {
6842
+ const rows = [];
6843
+ const collect = (list) => {
6844
+ for (const node of list) {
6845
+ if (node.type !== "element") continue;
6846
+ if (node.tag === "tr") rows.push(node);
6847
+ else if (["tbody", "thead", "tfoot"].includes(node.tag)) {
6848
+ collect(node.children);
6849
+ }
6850
+ }
6851
+ };
6852
+ collect(nodes);
6853
+ return rows.map(
6854
+ (tr) => tr.children.filter(
6855
+ (c) => c.type === "element" && (c.tag === "td" || c.tag === "th")
6856
+ ).map((cell) => normalize(textWithBlockBreaks(cell.children)).replace(/\n+/g, " ")).join(cellSeparator)
6857
+ ).filter((line) => line.replace(new RegExp(escapeRegExp(cellSeparator), "g"), "").trim() !== "").join("\n");
6858
+ }
6859
+ function normalize(text) {
6860
+ return text.replace(/[​-‍]/g, "").split("\n").map((line) => line.replace(/[^\S\n]+/g, " ").trim()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
6861
+ }
6862
+ function escapeRegExp(input) {
6863
+ return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6864
+ }
6865
+ exports.ALIGN_CLASS_BY_VALUE = ALIGN_CLASS_BY_VALUE;
5863
6866
  exports.ALL_INLINE_MARKS = ALL_INLINE_MARKS;
5864
6867
  exports.AerizEditor = AerizEditor;
5865
6868
  exports.AerizViewer = AerizViewer;
@@ -5868,9 +6871,16 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5868
6871
  exports.BLOCK_TYPE_BY_KIND = BLOCK_TYPE_BY_KIND;
5869
6872
  exports.BlockIdRegistry = BlockIdRegistry;
5870
6873
  exports.DEFAULT_TOOLBAR = DEFAULT_TOOLBAR;
6874
+ exports.SNAPSHOT_VERSION = SNAPSHOT_VERSION;
6875
+ exports.applyBlockClassNames = applyBlockClassNames;
5871
6876
  exports.applyBlockInlineStyles = applyBlockInlineStyles;
5872
6877
  exports.assignMissingIds = assignMissingIds;
6878
+ exports.blocksToMarkdown = blocksToMarkdown;
6879
+ exports.blocksToPlainText = blocksToPlainText;
5873
6880
  exports.classifyBlockElement = classifyBlockElement;
6881
+ exports.decodeEntities = decodeEntities;
6882
+ exports.escapeAttr = escapeAttr;
6883
+ exports.escapeHtml = escapeHtml;
5874
6884
  exports.extractInlineContent = extractInlineContent;
5875
6885
  exports.fillInlineContent = fillInlineContent;
5876
6886
  exports.findBlockFrom = findBlockFrom;
@@ -5878,13 +6888,24 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5878
6888
  exports.isElementBlank = isElementBlank;
5879
6889
  exports.listBlocks = listBlocks;
5880
6890
  exports.normalizeContent = normalizeContent;
6891
+ exports.parseHtmlFragment = parseHtmlFragment;
5881
6892
  exports.placeCaretAtStart = placeCaretAtStart;
5882
6893
  exports.readBlockStyleTheme = readBlockStyleTheme;
5883
6894
  exports.renderBlock = renderBlock;
6895
+ exports.renderBlockToHTML = renderBlockToHTML;
5884
6896
  exports.renderBlocks = renderBlocks;
6897
+ exports.renderBlocksToHTML = renderBlocksToHTML;
6898
+ exports.restoreAlignFromClasses = restoreAlignFromClasses;
5885
6899
  exports.serializeBlock = serializeBlock;
5886
6900
  exports.serializeBlocks = serializeBlocks;
6901
+ exports.serializeNodes = serializeNodes;
6902
+ exports.snapshotVersionOf = snapshotVersionOf;
5887
6903
  exports.stripEditorOnlyAttrs = stripEditorOnlyAttrs;
6904
+ exports.stripEditorOnlyNodes = stripEditorOnlyNodes;
6905
+ exports.textOf = textOf;
6906
+ exports.textWithBlockBreaks = textWithBlockBreaks;
6907
+ exports.upgradeSnapshot = upgradeSnapshot;
6908
+ exports.upgradeSnapshots = upgradeSnapshots;
5888
6909
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
5889
6910
  return exports;
5890
6911
  }({});