@particle-academy/fancy-code 0.4.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @particle-academy/fancy-code
2
2
 
3
+ [![Fancified](art/fancified.svg)](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
package/dist/index.cjs CHANGED
@@ -1037,7 +1037,79 @@ 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
+ const spans = [];
1083
+ const add = (type, start, end) => {
1084
+ spans.push(tok(type, base + start, base + end));
1085
+ };
1086
+ let m;
1087
+ const code = /`[^`]+`/g;
1088
+ while (m = code.exec(line)) add("string", m.index, m.index + m[0].length);
1089
+ const emph = /(\*\*|__)(?=\S)([\s\S]*?\S)\1|(\*|_)(?=\S)([\s\S]*?\S)\3/g;
1090
+ while (m = emph.exec(line)) add("type", m.index, m.index + m[0].length);
1091
+ const link = /!?\[[^\]]*\]\(([^)]+)\)/g;
1092
+ while (m = link.exec(line)) {
1093
+ add("tag", m.index, m.index + m[0].length - m[1].length - 1);
1094
+ const urlStart = m.index + m[0].length - m[1].length - 1;
1095
+ add("attributeValue", urlStart, urlStart + m[1].length);
1096
+ }
1097
+ spans.sort((a, b) => a.start - b.start || b.end - a.end);
1098
+ let lastEnd = -1;
1099
+ for (const s of spans) {
1100
+ if (s.start >= lastEnd) {
1101
+ out.push(s);
1102
+ lastEnd = s.end;
1103
+ }
1104
+ }
1105
+ }
1106
+
1040
1107
  // src/languages/builtin.ts
1108
+ registerLanguage({
1109
+ name: "Markdown",
1110
+ aliases: ["md", "markdown", "mkd"],
1111
+ tokenize: tokenizeMarkdown
1112
+ });
1041
1113
  registerLanguage({
1042
1114
  name: "JavaScript",
1043
1115
  aliases: ["js", "javascript", "jsx"],
@@ -1578,6 +1650,7 @@ function CodeEditorRoot({
1578
1650
  },
1579
1651
  cursorPosition,
1580
1652
  selectionLength,
1653
+ textareaRef: engineReturn.textareaRef,
1581
1654
  placeholder,
1582
1655
  _engineReturn: engineReturn,
1583
1656
  _minHeight: minHeight,
@@ -1607,13 +1680,176 @@ var CodeEditor = Object.assign(CodeEditorRoot, {
1607
1680
  StatusBar: CodeEditorStatusBar
1608
1681
  });
1609
1682
 
1683
+ // src/engine/markdown-render.ts
1684
+ function escapeHtml2(s) {
1685
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1686
+ }
1687
+ function inline(text) {
1688
+ return text.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_m, alt, src, title) => `<img src="${src}" alt="${alt}"${title ? ` title="${title}"` : ""}>`).replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_m, t, href, title) => `<a href="${href}"${title ? ` title="${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>");
1689
+ }
1690
+ function renderMarkdown(markdown) {
1691
+ const lines = markdown.replace(/\r\n?/g, "\n").split("\n");
1692
+ const out = [];
1693
+ let i = 0;
1694
+ let para = [];
1695
+ let listType = null;
1696
+ const flushPara = () => {
1697
+ if (para.length) {
1698
+ out.push(`<p>${inline(para.join(" "))}</p>`);
1699
+ para = [];
1700
+ }
1701
+ };
1702
+ const closeList = () => {
1703
+ if (listType) {
1704
+ out.push(`</${listType}>`);
1705
+ listType = null;
1706
+ }
1707
+ };
1708
+ while (i < lines.length) {
1709
+ const line = lines[i];
1710
+ const fence = line.match(/^\s*(```|~~~)(.*)$/);
1711
+ if (fence) {
1712
+ flushPara();
1713
+ closeList();
1714
+ const lang = fence[2].trim();
1715
+ const body = [];
1716
+ i++;
1717
+ while (i < lines.length && !/^\s*(```|~~~)/.test(lines[i])) {
1718
+ body.push(lines[i]);
1719
+ i++;
1720
+ }
1721
+ i++;
1722
+ out.push(`<pre><code${lang ? ` class="language-${escapeHtml2(lang)}"` : ""}>${escapeHtml2(body.join("\n"))}</code></pre>`);
1723
+ continue;
1724
+ }
1725
+ const esc = escapeHtml2(line);
1726
+ if (/^\s*$/.test(line)) {
1727
+ flushPara();
1728
+ closeList();
1729
+ i++;
1730
+ continue;
1731
+ }
1732
+ const heading = esc.match(/^(#{1,6})\s+(.*)$/);
1733
+ if (heading) {
1734
+ flushPara();
1735
+ closeList();
1736
+ const level = heading[1].length;
1737
+ out.push(`<h${level}>${inline(heading[2])}</h${level}>`);
1738
+ i++;
1739
+ continue;
1740
+ }
1741
+ if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) {
1742
+ flushPara();
1743
+ closeList();
1744
+ out.push("<hr>");
1745
+ i++;
1746
+ continue;
1747
+ }
1748
+ const quote = esc.match(/^\s*&gt;\s?(.*)$/);
1749
+ if (quote) {
1750
+ flushPara();
1751
+ closeList();
1752
+ out.push(`<blockquote>${inline(quote[1])}</blockquote>`);
1753
+ i++;
1754
+ continue;
1755
+ }
1756
+ const ol = esc.match(/^\s*\d+\.\s+(.*)$/);
1757
+ const ul = esc.match(/^\s*[-*+]\s+(.*)$/);
1758
+ if (ol || ul) {
1759
+ flushPara();
1760
+ const type = ol ? "ol" : "ul";
1761
+ if (listType !== type) {
1762
+ closeList();
1763
+ out.push(`<${type}>`);
1764
+ listType = type;
1765
+ }
1766
+ out.push(`<li>${inline((ol ?? ul)[1])}</li>`);
1767
+ i++;
1768
+ continue;
1769
+ }
1770
+ closeList();
1771
+ para.push(esc);
1772
+ i++;
1773
+ }
1774
+ flushPara();
1775
+ closeList();
1776
+ return out.join("\n");
1777
+ }
1778
+ function MarkdownEditor({
1779
+ value,
1780
+ defaultValue = "",
1781
+ onValueChange,
1782
+ mode = "split",
1783
+ theme = "auto",
1784
+ readOnly = false,
1785
+ lineNumbers = false,
1786
+ wordWrap = true,
1787
+ placeholder,
1788
+ minHeight,
1789
+ maxHeight,
1790
+ className,
1791
+ renderPreview = renderMarkdown
1792
+ }) {
1793
+ const [content, setContent] = reactFancy.useControllableState(value, defaultValue, onValueChange);
1794
+ const showEditor = mode !== "preview";
1795
+ const showPreview = mode !== "edit";
1796
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1797
+ "div",
1798
+ {
1799
+ "data-fancy-markdown-editor": "",
1800
+ "data-mode": mode,
1801
+ className: reactFancy.cn("fancy-md-editor", className),
1802
+ children: [
1803
+ showEditor && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fancy-md-editor-pane", children: /* @__PURE__ */ jsxRuntime.jsx(
1804
+ CodeEditor,
1805
+ {
1806
+ value: content,
1807
+ onChange: setContent,
1808
+ language: "markdown",
1809
+ theme,
1810
+ readOnly,
1811
+ lineNumbers,
1812
+ wordWrap,
1813
+ placeholder,
1814
+ minHeight,
1815
+ maxHeight,
1816
+ children: /* @__PURE__ */ jsxRuntime.jsx(CodeEditor.Panel, {})
1817
+ }
1818
+ ) }),
1819
+ showPreview && /* @__PURE__ */ jsxRuntime.jsx(
1820
+ "div",
1821
+ {
1822
+ "data-fancy-markdown-preview": "",
1823
+ className: "fancy-md-preview",
1824
+ dangerouslySetInnerHTML: { __html: renderPreview(content) }
1825
+ }
1826
+ )
1827
+ ]
1828
+ }
1829
+ );
1830
+ }
1831
+ MarkdownEditor.displayName = "MarkdownEditor";
1832
+ function useCodeEditorInputTagAdapter() {
1833
+ const ctx = useCodeEditor();
1834
+ return react.useMemo(() => reactFancy.textareaAdapter(ctx.textareaRef), [ctx.textareaRef]);
1835
+ }
1836
+ function CodeEditorInputTag(props) {
1837
+ const adapter = useCodeEditorInputTagAdapter();
1838
+ return /* @__PURE__ */ jsxRuntime.jsx(reactFancy.InputTag, { adapter, ...props });
1839
+ }
1840
+
1610
1841
  exports.CodeEditor = CodeEditor;
1842
+ exports.CodeEditorInputTag = CodeEditorInputTag;
1843
+ exports.MarkdownEditor = MarkdownEditor;
1611
1844
  exports.getLanguage = getLanguage;
1612
1845
  exports.getRegisteredLanguages = getRegisteredLanguages;
1613
1846
  exports.getRegisteredThemes = getRegisteredThemes;
1614
1847
  exports.getTheme = getTheme;
1615
1848
  exports.registerLanguage = registerLanguage;
1616
1849
  exports.registerTheme = registerTheme;
1850
+ exports.renderMarkdown = renderMarkdown;
1851
+ exports.tokenizeMarkdown = tokenizeMarkdown;
1617
1852
  exports.useCodeEditor = useCodeEditor;
1853
+ exports.useCodeEditorInputTagAdapter = useCodeEditorInputTagAdapter;
1618
1854
  //# sourceMappingURL=index.cjs.map
1619
1855
  //# sourceMappingURL=index.cjs.map