@djangocfg/ui-tools 2.1.241 → 2.1.242

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
@@ -28,7 +28,7 @@ This package contains heavy components that are loaded lazily to keep your initi
28
28
  | `@djangocfg/ui-tools` | Heavy tools with lazy loading |
29
29
  | `@djangocfg/ui-nextjs` | Next.js apps (extends ui-core) |
30
30
 
31
- ## Tools (13)
31
+ ## Tools (14)
32
32
 
33
33
  | Tool | Bundle Size | Description |
34
34
  |------|-------------|-------------|
@@ -38,6 +38,7 @@ This package contains heavy components that are loaded lazily to keep your initi
38
38
  | `PrettyCode` | ~500KB | Code syntax highlighting (read-only) |
39
39
  | `OpenapiViewer` | ~400KB | OpenAPI schema viewer & playground |
40
40
  | `JsonForm` | ~300KB | JSON Schema form generator |
41
+ | `MarkdownEditor` | ~200KB | WYSIWYG markdown editor with Tiptap |
41
42
  | `LottiePlayer` | ~200KB | Lottie animation player |
42
43
  | `AudioPlayer` | ~200KB | Audio player with WaveSurfer.js |
43
44
  | `VideoPlayer` | ~150KB | Professional video player with Vidstack |
package/dist/index.cjs CHANGED
@@ -21,6 +21,12 @@ var remarkGfm = require('remark-gfm');
21
21
  var components = require('@djangocfg/ui-core/components');
22
22
  var hooks = require('@djangocfg/ui-core/hooks');
23
23
  require('@djangocfg/ui-core/styles/palette');
24
+ var react = require('@tiptap/react');
25
+ var StarterKit = require('@tiptap/starter-kit');
26
+ var Placeholder = require('@tiptap/extension-placeholder');
27
+ var Mention = require('@tiptap/extension-mention');
28
+ var markdown = require('@tiptap/markdown');
29
+ var lucideReact = require('lucide-react');
24
30
 
25
31
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
26
32
 
@@ -45,6 +51,9 @@ function _interopNamespace(e) {
45
51
  var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
46
52
  var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
47
53
  var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
54
+ var StarterKit__default = /*#__PURE__*/_interopDefault(StarterKit);
55
+ var Placeholder__default = /*#__PURE__*/_interopDefault(Placeholder);
56
+ var Mention__default = /*#__PURE__*/_interopDefault(Mention);
48
57
 
49
58
  function Spinner({ className }) {
50
59
  const t = i18n.useAppT();
@@ -1628,6 +1637,217 @@ function useLanguage(filename) {
1628
1637
  }, [filename]);
1629
1638
  }
1630
1639
  chunkWGEGR3DF_cjs.__name(useLanguage, "useLanguage");
1640
+ var MentionList = React3.forwardRef(
1641
+ ({ items, command }, ref) => {
1642
+ const [selectedIndex, setSelectedIndex] = React3.useState(0);
1643
+ React3.useEffect(() => setSelectedIndex(0), [items]);
1644
+ const select = React3.useCallback(
1645
+ (index) => {
1646
+ const item = items[index];
1647
+ if (item) command(item);
1648
+ },
1649
+ [items, command]
1650
+ );
1651
+ React3.useImperativeHandle(ref, () => ({
1652
+ onKeyDown: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((event) => {
1653
+ if (event.key === "ArrowUp") {
1654
+ setSelectedIndex((i) => (i + items.length - 1) % items.length);
1655
+ return true;
1656
+ }
1657
+ if (event.key === "ArrowDown") {
1658
+ setSelectedIndex((i) => (i + 1) % items.length);
1659
+ return true;
1660
+ }
1661
+ if (event.key === "Enter") {
1662
+ select(selectedIndex);
1663
+ return true;
1664
+ }
1665
+ return false;
1666
+ }, "onKeyDown")
1667
+ }));
1668
+ if (items.length === 0) return null;
1669
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "markdown-mention-list", children: items.map((item, i) => {
1670
+ const isSelected = i === selectedIndex;
1671
+ const cls = `markdown-mention-item ${isSelected ? "selected" : ""}`;
1672
+ return /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: cls, onClick: () => select(i), children: [
1673
+ item.thumbnail && /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.thumbnail, alt: "", className: "markdown-mention-avatar" }),
1674
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "markdown-mention-info", children: [
1675
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "markdown-mention-name", children: item.label }),
1676
+ item.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "markdown-mention-desc", children: item.description })
1677
+ ] })
1678
+ ] }, item.id);
1679
+ }) });
1680
+ }
1681
+ );
1682
+ MentionList.displayName = "MentionList";
1683
+
1684
+ // src/tools/MarkdownEditor/createMentionSuggestion.ts
1685
+ function createMentionSuggestion(config) {
1686
+ const { maxItems = 5, trigger = "@" } = config;
1687
+ return {
1688
+ char: trigger,
1689
+ items: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({ query }) => {
1690
+ const q = query.toLowerCase();
1691
+ return config.items.filter((item) => item.label.toLowerCase().includes(q)).slice(0, maxItems);
1692
+ }, "items"),
1693
+ render: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => {
1694
+ let component = null;
1695
+ let popup = null;
1696
+ return {
1697
+ onStart: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((props) => {
1698
+ component = new react.ReactRenderer(MentionList, {
1699
+ props: {
1700
+ items: props.items,
1701
+ command: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((item) => {
1702
+ props.command({ id: item.id, label: item.label });
1703
+ }, "command")
1704
+ },
1705
+ editor: props.editor
1706
+ });
1707
+ popup = document.createElement("div");
1708
+ popup.style.cssText = "position: absolute; z-index: 99999;";
1709
+ popup.appendChild(component.element);
1710
+ const rect = props.clientRect?.();
1711
+ if (rect) {
1712
+ popup.style.top = `${rect.bottom + window.scrollY + 4}px`;
1713
+ popup.style.left = `${rect.left + window.scrollX}px`;
1714
+ }
1715
+ document.body.appendChild(popup);
1716
+ }, "onStart"),
1717
+ onUpdate: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((props) => {
1718
+ component?.updateProps({
1719
+ items: props.items,
1720
+ command: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((item) => {
1721
+ props.command({ id: item.id, label: item.label });
1722
+ }, "command")
1723
+ });
1724
+ const rect = props.clientRect?.();
1725
+ if (rect && popup) {
1726
+ popup.style.top = `${rect.bottom + window.scrollY + 4}px`;
1727
+ popup.style.left = `${rect.left + window.scrollX}px`;
1728
+ }
1729
+ }, "onUpdate"),
1730
+ onKeyDown: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((props) => {
1731
+ if (props.event.key === "Escape") {
1732
+ popup?.remove();
1733
+ component?.destroy();
1734
+ return true;
1735
+ }
1736
+ return component?.ref?.onKeyDown(props.event) ?? false;
1737
+ }, "onKeyDown"),
1738
+ onExit: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => {
1739
+ popup?.remove();
1740
+ component?.destroy();
1741
+ }, "onExit")
1742
+ };
1743
+ }, "render")
1744
+ };
1745
+ }
1746
+ chunkWGEGR3DF_cjs.__name(createMentionSuggestion, "createMentionSuggestion");
1747
+ function getMarkdown(editor) {
1748
+ const storage = editor.storage.markdown;
1749
+ if (!storage?.manager) return editor.getText();
1750
+ return storage.manager.serialize(editor.getJSON());
1751
+ }
1752
+ chunkWGEGR3DF_cjs.__name(getMarkdown, "getMarkdown");
1753
+ function extractMentionIds(editor) {
1754
+ const ids = [];
1755
+ editor.state.doc.descendants((node) => {
1756
+ if (node.type.name === "mention" && node.attrs.id) {
1757
+ ids.push(node.attrs.id);
1758
+ }
1759
+ });
1760
+ return [...new Set(ids)];
1761
+ }
1762
+ chunkWGEGR3DF_cjs.__name(extractMentionIds, "extractMentionIds");
1763
+ function MarkdownEditor({
1764
+ value,
1765
+ onChange,
1766
+ placeholder = "Write markdown...",
1767
+ minHeight = 120,
1768
+ className = "",
1769
+ disabled = false,
1770
+ showToolbar = true,
1771
+ mentions,
1772
+ onMentionIdsChange
1773
+ }) {
1774
+ const isExternalUpdate = React3.useRef(false);
1775
+ const extensions = React3.useMemo(() => {
1776
+ const exts = [
1777
+ StarterKit__default.default.configure({ heading: { levels: [1, 2, 3] } }),
1778
+ Placeholder__default.default.configure({ placeholder }),
1779
+ markdown.Markdown
1780
+ ];
1781
+ if (mentions) {
1782
+ exts.push(
1783
+ Mention__default.default.configure({
1784
+ HTMLAttributes: { class: "markdown-mention" },
1785
+ suggestion: createMentionSuggestion(mentions),
1786
+ renderText: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({ node }) => `@${node.attrs.label}`, "renderText")
1787
+ })
1788
+ );
1789
+ }
1790
+ return exts;
1791
+ }, [placeholder, mentions]);
1792
+ const editor = react.useEditor({
1793
+ immediatelyRender: false,
1794
+ editable: !disabled,
1795
+ extensions,
1796
+ content: value,
1797
+ onUpdate: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({ editor: editor2 }) => {
1798
+ if (isExternalUpdate.current) return;
1799
+ onChange(getMarkdown(editor2));
1800
+ if (onMentionIdsChange) {
1801
+ onMentionIdsChange(extractMentionIds(editor2));
1802
+ }
1803
+ }, "onUpdate"),
1804
+ editorProps: {
1805
+ attributes: {
1806
+ class: "markdown-editor-content focus:outline-none text-sm",
1807
+ style: `min-height: ${minHeight}px`
1808
+ }
1809
+ }
1810
+ });
1811
+ React3.useEffect(() => {
1812
+ if (!editor) return;
1813
+ const current = getMarkdown(editor);
1814
+ if (current !== value) {
1815
+ isExternalUpdate.current = true;
1816
+ editor.commands.setContent(value);
1817
+ isExternalUpdate.current = false;
1818
+ }
1819
+ }, [value, editor]);
1820
+ const wrapperClass = `markdown-editor rounded-md border border-input bg-background ${disabled ? "opacity-60" : ""} ${className}`.trim();
1821
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClass, children: [
1822
+ showToolbar && editor && /* @__PURE__ */ jsxRuntime.jsx(MarkdownToolbar, { editor }),
1823
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(react.EditorContent, { editor }) })
1824
+ ] });
1825
+ }
1826
+ chunkWGEGR3DF_cjs.__name(MarkdownEditor, "MarkdownEditor");
1827
+ function MarkdownToolbar({ editor }) {
1828
+ const items = React3.useMemo(() => [
1829
+ { icon: lucideReact.Bold, title: "Bold", action: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => editor.chain().focus().toggleBold().run(), "action"), active: editor.isActive("bold") },
1830
+ { icon: lucideReact.Italic, title: "Italic", action: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => editor.chain().focus().toggleItalic().run(), "action"), active: editor.isActive("italic") },
1831
+ { icon: lucideReact.Strikethrough, title: "Strike", action: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => editor.chain().focus().toggleStrike().run(), "action"), active: editor.isActive("strike") },
1832
+ { icon: lucideReact.Code, title: "Code", action: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => editor.chain().focus().toggleCode().run(), "action"), active: editor.isActive("code") },
1833
+ null,
1834
+ { icon: lucideReact.Heading1, title: "H1", action: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => editor.chain().focus().toggleHeading({ level: 1 }).run(), "action"), active: editor.isActive("heading", { level: 1 }) },
1835
+ { icon: lucideReact.Heading2, title: "H2", action: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => editor.chain().focus().toggleHeading({ level: 2 }).run(), "action"), active: editor.isActive("heading", { level: 2 }) },
1836
+ { icon: lucideReact.Heading3, title: "H3", action: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => editor.chain().focus().toggleHeading({ level: 3 }).run(), "action"), active: editor.isActive("heading", { level: 3 }) },
1837
+ null,
1838
+ { icon: lucideReact.List, title: "Bullet list", action: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => editor.chain().focus().toggleBulletList().run(), "action"), active: editor.isActive("bulletList") },
1839
+ { icon: lucideReact.ListOrdered, title: "Ordered list", action: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => editor.chain().focus().toggleOrderedList().run(), "action"), active: editor.isActive("orderedList") },
1840
+ { icon: lucideReact.Quote, title: "Quote", action: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => editor.chain().focus().toggleBlockquote().run(), "action"), active: editor.isActive("blockquote") },
1841
+ { icon: lucideReact.Minus, title: "Divider", action: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => editor.chain().focus().setHorizontalRule().run(), "action"), active: false }
1842
+ ], [editor]);
1843
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-0.5 px-2 py-1.5 border-b border-border", children: items.map((item, i) => {
1844
+ if (!item) return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-4 bg-border mx-1" }, i);
1845
+ const Icon = item.icon;
1846
+ const btnClass = `markdown-toolbar-btn ${item.active ? "active" : ""}`;
1847
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: item.action, title: item.title, className: btnClass, children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { style: { width: 14, height: 14 } }) }, i);
1848
+ }) });
1849
+ }
1850
+ chunkWGEGR3DF_cjs.__name(MarkdownToolbar, "MarkdownToolbar");
1631
1851
 
1632
1852
  Object.defineProperty(exports, "AudioReactiveCover", {
1633
1853
  enumerable: true,
@@ -2052,6 +2272,7 @@ exports.LazyWrapper = LazyWrapper;
2052
2272
  exports.LoadingFallback = LoadingFallback;
2053
2273
  exports.LottiePlayer = LottiePlayer;
2054
2274
  exports.MapLoadingFallback = MapLoadingFallback;
2275
+ exports.MarkdownEditor = MarkdownEditor;
2055
2276
  exports.MarkdownMessage = MarkdownMessage;
2056
2277
  exports.Mermaid = Mermaid_default;
2057
2278
  exports.OpenapiViewer = OpenapiViewer_default;