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