@particle-academy/fancy-code 0.4.7 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/index.cjs +229 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -1
- package/dist/index.d.ts +67 -1
- package/dist/index.js +227 -1
- package/dist/index.js.map +1 -1
- package/dist/styles.css +138 -0
- package/dist/styles.css.map +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @particle-academy/fancy-code
|
|
2
2
|
|
|
3
|
+
[](https://particle.academy)
|
|
4
|
+
|
|
3
5
|
Lightweight embedded code editor with syntax highlighting, custom toolbar buttons, and extensible language and theme registries. Part of the `@particle-academy` component ecosystem.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
@@ -83,3 +85,9 @@ import { CodeEditor } from "@particle-academy/fancy-code";
|
|
|
83
85
|
## License
|
|
84
86
|
|
|
85
87
|
MIT
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## ⭐ Star Fancy UI
|
|
92
|
+
|
|
93
|
+
If this package is useful to you, a quick ⭐ on the repo really helps us build a better kit. Thank you!
|
package/dist/index.cjs
CHANGED
|
@@ -1037,7 +1037,80 @@ var tokenizeGo = (source) => {
|
|
|
1037
1037
|
return tokens;
|
|
1038
1038
|
};
|
|
1039
1039
|
|
|
1040
|
+
// src/engine/tokenizer.ts
|
|
1041
|
+
function tok(type, s, e) {
|
|
1042
|
+
return { type, start: s, end: e };
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/engine/tokenizers/markdown.ts
|
|
1046
|
+
function tokenizeMarkdown(source) {
|
|
1047
|
+
const tokens = [];
|
|
1048
|
+
const lines = source.split("\n");
|
|
1049
|
+
let offset = 0;
|
|
1050
|
+
let inFence = false;
|
|
1051
|
+
for (const line of lines) {
|
|
1052
|
+
const base = offset;
|
|
1053
|
+
const len = line.length;
|
|
1054
|
+
offset += len + 1;
|
|
1055
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
1056
|
+
tokens.push(tok("string", base, base + len));
|
|
1057
|
+
inFence = !inFence;
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (inFence) {
|
|
1061
|
+
tokens.push(tok("string", base, base + len));
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
if (/^#{1,6}\s/.test(line)) {
|
|
1065
|
+
tokens.push(tok("keyword", base, base + len));
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
if (/^\s*>/.test(line)) {
|
|
1069
|
+
tokens.push(tok("comment", base, base + len));
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
const list = line.match(/^(\s*)([-*+]|\d+\.)\s/);
|
|
1073
|
+
if (list) {
|
|
1074
|
+
const start = base + list[1].length;
|
|
1075
|
+
tokens.push(tok("operator", start, start + list[2].length));
|
|
1076
|
+
}
|
|
1077
|
+
pushInline(line, base, tokens);
|
|
1078
|
+
}
|
|
1079
|
+
return tokens;
|
|
1080
|
+
}
|
|
1081
|
+
function pushInline(line, base, out) {
|
|
1082
|
+
if (line.length > 5e3) return;
|
|
1083
|
+
const spans = [];
|
|
1084
|
+
const add = (type, start, end) => {
|
|
1085
|
+
spans.push(tok(type, base + start, base + end));
|
|
1086
|
+
};
|
|
1087
|
+
let m;
|
|
1088
|
+
const code = /`[^`]+`/g;
|
|
1089
|
+
while (m = code.exec(line)) add("string", m.index, m.index + m[0].length);
|
|
1090
|
+
const emph = /(\*\*|__)(?=\S)([\s\S]*?\S)\1|(\*|_)(?=\S)([\s\S]*?\S)\3/g;
|
|
1091
|
+
while (m = emph.exec(line)) add("type", m.index, m.index + m[0].length);
|
|
1092
|
+
const link = /!?\[[^\]]*\]\(([^)]+)\)/g;
|
|
1093
|
+
while (m = link.exec(line)) {
|
|
1094
|
+
add("tag", m.index, m.index + m[0].length - m[1].length - 1);
|
|
1095
|
+
const urlStart = m.index + m[0].length - m[1].length - 1;
|
|
1096
|
+
add("attributeValue", urlStart, urlStart + m[1].length);
|
|
1097
|
+
}
|
|
1098
|
+
spans.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
1099
|
+
let lastEnd = -1;
|
|
1100
|
+
for (const s of spans) {
|
|
1101
|
+
if (s.start >= lastEnd) {
|
|
1102
|
+
out.push(s);
|
|
1103
|
+
lastEnd = s.end;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1040
1108
|
// src/languages/builtin.ts
|
|
1109
|
+
registerLanguage({
|
|
1110
|
+
name: "Markdown",
|
|
1111
|
+
aliases: ["md", "markdown", "mkd"],
|
|
1112
|
+
tokenize: tokenizeMarkdown
|
|
1113
|
+
});
|
|
1041
1114
|
registerLanguage({
|
|
1042
1115
|
name: "JavaScript",
|
|
1043
1116
|
aliases: ["js", "javascript", "jsx"],
|
|
@@ -1607,6 +1680,159 @@ var CodeEditor = Object.assign(CodeEditorRoot, {
|
|
|
1607
1680
|
Panel: CodeEditorPanel,
|
|
1608
1681
|
StatusBar: CodeEditorStatusBar
|
|
1609
1682
|
});
|
|
1683
|
+
|
|
1684
|
+
// src/engine/markdown-render.ts
|
|
1685
|
+
function escapeHtml2(s) {
|
|
1686
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1687
|
+
}
|
|
1688
|
+
function escapeAttr(s) {
|
|
1689
|
+
return s.replace(/"/g, """).replace(/'/g, "'");
|
|
1690
|
+
}
|
|
1691
|
+
function inline(text) {
|
|
1692
|
+
return text.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_m, alt, src, title) => `<img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}"${title ? ` title="${escapeAttr(title)}"` : ""}>`).replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_m, t, href, title) => `<a href="${escapeAttr(href)}"${title ? ` title="${escapeAttr(title)}"` : ""} rel="noopener">${t}</a>`).replace(/`([^`]+)`/g, (_m, c) => `<code>${c}</code>`).replace(/(\*\*|__)(?=\S)([\s\S]*?\S)\1/g, "<strong>$2</strong>").replace(/(\*|_)(?=\S)([\s\S]*?\S)\1/g, "<em>$2</em>");
|
|
1693
|
+
}
|
|
1694
|
+
function renderMarkdown(markdown) {
|
|
1695
|
+
const lines = markdown.replace(/\r\n?/g, "\n").split("\n");
|
|
1696
|
+
const out = [];
|
|
1697
|
+
let i = 0;
|
|
1698
|
+
let para = [];
|
|
1699
|
+
let listType = null;
|
|
1700
|
+
const flushPara = () => {
|
|
1701
|
+
if (para.length) {
|
|
1702
|
+
out.push(`<p>${inline(para.join(" "))}</p>`);
|
|
1703
|
+
para = [];
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
const closeList = () => {
|
|
1707
|
+
if (listType) {
|
|
1708
|
+
out.push(`</${listType}>`);
|
|
1709
|
+
listType = null;
|
|
1710
|
+
}
|
|
1711
|
+
};
|
|
1712
|
+
while (i < lines.length) {
|
|
1713
|
+
const line = lines[i];
|
|
1714
|
+
const fence = line.match(/^\s*(```|~~~)(.*)$/);
|
|
1715
|
+
if (fence) {
|
|
1716
|
+
flushPara();
|
|
1717
|
+
closeList();
|
|
1718
|
+
const lang = fence[2].trim();
|
|
1719
|
+
const body = [];
|
|
1720
|
+
i++;
|
|
1721
|
+
while (i < lines.length && !/^\s*(```|~~~)/.test(lines[i])) {
|
|
1722
|
+
body.push(lines[i]);
|
|
1723
|
+
i++;
|
|
1724
|
+
}
|
|
1725
|
+
i++;
|
|
1726
|
+
out.push(`<pre><code${lang ? ` class="language-${escapeAttr(escapeHtml2(lang))}"` : ""}>${escapeHtml2(body.join("\n"))}</code></pre>`);
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
const esc = escapeHtml2(line);
|
|
1730
|
+
if (/^\s*$/.test(line)) {
|
|
1731
|
+
flushPara();
|
|
1732
|
+
closeList();
|
|
1733
|
+
i++;
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
const heading = esc.match(/^(#{1,6})\s+(.*)$/);
|
|
1737
|
+
if (heading) {
|
|
1738
|
+
flushPara();
|
|
1739
|
+
closeList();
|
|
1740
|
+
const level = heading[1].length;
|
|
1741
|
+
out.push(`<h${level}>${inline(heading[2])}</h${level}>`);
|
|
1742
|
+
i++;
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) {
|
|
1746
|
+
flushPara();
|
|
1747
|
+
closeList();
|
|
1748
|
+
out.push("<hr>");
|
|
1749
|
+
i++;
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
const quote = esc.match(/^\s*>\s?(.*)$/);
|
|
1753
|
+
if (quote) {
|
|
1754
|
+
flushPara();
|
|
1755
|
+
closeList();
|
|
1756
|
+
out.push(`<blockquote>${inline(quote[1])}</blockquote>`);
|
|
1757
|
+
i++;
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
const ol = esc.match(/^\s*\d+\.\s+(.*)$/);
|
|
1761
|
+
const ul = esc.match(/^\s*[-*+]\s+(.*)$/);
|
|
1762
|
+
if (ol || ul) {
|
|
1763
|
+
flushPara();
|
|
1764
|
+
const type = ol ? "ol" : "ul";
|
|
1765
|
+
if (listType !== type) {
|
|
1766
|
+
closeList();
|
|
1767
|
+
out.push(`<${type}>`);
|
|
1768
|
+
listType = type;
|
|
1769
|
+
}
|
|
1770
|
+
out.push(`<li>${inline((ol ?? ul)[1])}</li>`);
|
|
1771
|
+
i++;
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
closeList();
|
|
1775
|
+
para.push(esc);
|
|
1776
|
+
i++;
|
|
1777
|
+
}
|
|
1778
|
+
flushPara();
|
|
1779
|
+
closeList();
|
|
1780
|
+
return out.join("\n");
|
|
1781
|
+
}
|
|
1782
|
+
function MarkdownEditor({
|
|
1783
|
+
value,
|
|
1784
|
+
defaultValue = "",
|
|
1785
|
+
onValueChange,
|
|
1786
|
+
mode = "split",
|
|
1787
|
+
theme = "auto",
|
|
1788
|
+
readOnly = false,
|
|
1789
|
+
lineNumbers = false,
|
|
1790
|
+
wordWrap = true,
|
|
1791
|
+
placeholder,
|
|
1792
|
+
minHeight,
|
|
1793
|
+
maxHeight,
|
|
1794
|
+
className,
|
|
1795
|
+
renderPreview = renderMarkdown
|
|
1796
|
+
}) {
|
|
1797
|
+
const [content, setContent] = reactFancy.useControllableState(value, defaultValue, onValueChange);
|
|
1798
|
+
const showEditor = mode !== "preview";
|
|
1799
|
+
const showPreview = mode !== "edit";
|
|
1800
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1801
|
+
"div",
|
|
1802
|
+
{
|
|
1803
|
+
"data-fancy-markdown-editor": "",
|
|
1804
|
+
"data-mode": mode,
|
|
1805
|
+
className: reactFancy.cn("fancy-md-editor", className),
|
|
1806
|
+
children: [
|
|
1807
|
+
showEditor && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fancy-md-editor-pane", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1808
|
+
CodeEditor,
|
|
1809
|
+
{
|
|
1810
|
+
value: content,
|
|
1811
|
+
onChange: setContent,
|
|
1812
|
+
language: "markdown",
|
|
1813
|
+
theme,
|
|
1814
|
+
readOnly,
|
|
1815
|
+
lineNumbers,
|
|
1816
|
+
wordWrap,
|
|
1817
|
+
placeholder,
|
|
1818
|
+
minHeight,
|
|
1819
|
+
maxHeight,
|
|
1820
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CodeEditor.Panel, {})
|
|
1821
|
+
}
|
|
1822
|
+
) }),
|
|
1823
|
+
showPreview && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1824
|
+
"div",
|
|
1825
|
+
{
|
|
1826
|
+
"data-fancy-markdown-preview": "",
|
|
1827
|
+
className: "fancy-md-preview",
|
|
1828
|
+
dangerouslySetInnerHTML: { __html: renderPreview(content) }
|
|
1829
|
+
}
|
|
1830
|
+
)
|
|
1831
|
+
]
|
|
1832
|
+
}
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
MarkdownEditor.displayName = "MarkdownEditor";
|
|
1610
1836
|
function useCodeEditorInputTagAdapter() {
|
|
1611
1837
|
const ctx = useCodeEditor();
|
|
1612
1838
|
return react.useMemo(() => reactFancy.textareaAdapter(ctx.textareaRef), [ctx.textareaRef]);
|
|
@@ -1618,12 +1844,15 @@ function CodeEditorInputTag(props) {
|
|
|
1618
1844
|
|
|
1619
1845
|
exports.CodeEditor = CodeEditor;
|
|
1620
1846
|
exports.CodeEditorInputTag = CodeEditorInputTag;
|
|
1847
|
+
exports.MarkdownEditor = MarkdownEditor;
|
|
1621
1848
|
exports.getLanguage = getLanguage;
|
|
1622
1849
|
exports.getRegisteredLanguages = getRegisteredLanguages;
|
|
1623
1850
|
exports.getRegisteredThemes = getRegisteredThemes;
|
|
1624
1851
|
exports.getTheme = getTheme;
|
|
1625
1852
|
exports.registerLanguage = registerLanguage;
|
|
1626
1853
|
exports.registerTheme = registerTheme;
|
|
1854
|
+
exports.renderMarkdown = renderMarkdown;
|
|
1855
|
+
exports.tokenizeMarkdown = tokenizeMarkdown;
|
|
1627
1856
|
exports.useCodeEditor = useCodeEditor;
|
|
1628
1857
|
exports.useCodeEditorInputTagAdapter = useCodeEditorInputTagAdapter;
|
|
1629
1858
|
//# sourceMappingURL=index.cjs.map
|