@react-email/editor 0.0.0-experimental.6 → 0.0.0-experimental.7
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/dist/index.d.mts +340 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +339 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +726 -1
- package/dist/index.mjs +700 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -3
package/dist/index.js
CHANGED
|
@@ -39,6 +39,16 @@ let prismjs = require("prismjs");
|
|
|
39
39
|
prismjs = __toESM(prismjs);
|
|
40
40
|
let __tiptap_extension_placeholder = require("@tiptap/extension-placeholder");
|
|
41
41
|
__tiptap_extension_placeholder = __toESM(__tiptap_extension_placeholder);
|
|
42
|
+
let __tiptap_react = require("@tiptap/react");
|
|
43
|
+
__tiptap_react = __toESM(__tiptap_react);
|
|
44
|
+
let lucide_react = require("lucide-react");
|
|
45
|
+
lucide_react = __toESM(lucide_react);
|
|
46
|
+
let react = require("react");
|
|
47
|
+
react = __toESM(react);
|
|
48
|
+
let __radix_ui_react_popover = require("@radix-ui/react-popover");
|
|
49
|
+
__radix_ui_react_popover = __toESM(__radix_ui_react_popover);
|
|
50
|
+
let __tiptap_react_menus = require("@tiptap/react/menus");
|
|
51
|
+
__tiptap_react_menus = __toESM(__tiptap_react_menus);
|
|
42
52
|
|
|
43
53
|
//#region src/core/email-node.ts
|
|
44
54
|
var EmailNode = class EmailNode extends __tiptap_core.Node {
|
|
@@ -70,6 +80,48 @@ var EmailNode = class EmailNode extends __tiptap_core.Node {
|
|
|
70
80
|
}
|
|
71
81
|
};
|
|
72
82
|
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/core/event-bus.ts
|
|
85
|
+
const EVENT_PREFIX = "@react-email/editor:";
|
|
86
|
+
var EditorEventBus = class {
|
|
87
|
+
prefixEventName(eventName) {
|
|
88
|
+
return `${EVENT_PREFIX}${String(eventName)}`;
|
|
89
|
+
}
|
|
90
|
+
dispatch(eventName, payload, options) {
|
|
91
|
+
const target = options?.target ?? window;
|
|
92
|
+
const prefixedEventName = this.prefixEventName(eventName);
|
|
93
|
+
const event = new CustomEvent(prefixedEventName, {
|
|
94
|
+
detail: payload,
|
|
95
|
+
bubbles: false,
|
|
96
|
+
cancelable: false
|
|
97
|
+
});
|
|
98
|
+
target.dispatchEvent(event);
|
|
99
|
+
}
|
|
100
|
+
on(eventName, handler, options) {
|
|
101
|
+
const target = options?.target ?? window;
|
|
102
|
+
const prefixedEventName = this.prefixEventName(eventName);
|
|
103
|
+
const abortController = new AbortController();
|
|
104
|
+
const wrappedHandler = (event) => {
|
|
105
|
+
const customEvent = event;
|
|
106
|
+
const result = handler(customEvent.detail);
|
|
107
|
+
if (result instanceof Promise) result.catch((error) => {
|
|
108
|
+
console.error(`Error in async event handler for ${prefixedEventName}:`, {
|
|
109
|
+
event: customEvent.detail,
|
|
110
|
+
error
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
target.addEventListener(prefixedEventName, wrappedHandler, {
|
|
115
|
+
...options,
|
|
116
|
+
signal: abortController.signal
|
|
117
|
+
});
|
|
118
|
+
return { unsubscribe: () => {
|
|
119
|
+
abortController.abort();
|
|
120
|
+
} };
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const editorEventBus = new EditorEventBus();
|
|
124
|
+
|
|
73
125
|
//#endregion
|
|
74
126
|
//#region src/extensions/alignment-attribute.tsx
|
|
75
127
|
const AlignmentAttribute = __tiptap_core.Extension.create({
|
|
@@ -803,6 +855,25 @@ const CodeBlockPrism = EmailNode.from(__tiptap_extension_code_block.default.exte
|
|
|
803
855
|
]
|
|
804
856
|
];
|
|
805
857
|
},
|
|
858
|
+
addKeyboardShortcuts() {
|
|
859
|
+
return {
|
|
860
|
+
...this.parent?.(),
|
|
861
|
+
"Mod-a": ({ editor }) => {
|
|
862
|
+
const { state } = editor;
|
|
863
|
+
const { selection } = state;
|
|
864
|
+
const { $from } = selection;
|
|
865
|
+
for (let depth = $from.depth; depth >= 1; depth--) if ($from.node(depth).type.name === this.name) {
|
|
866
|
+
const blockStart = $from.start(depth);
|
|
867
|
+
const blockEnd = $from.end(depth);
|
|
868
|
+
if (selection.from === blockStart && selection.to === blockEnd) return false;
|
|
869
|
+
const tr = state.tr.setSelection(__tiptap_pm_state.TextSelection.create(state.doc, blockStart, blockEnd));
|
|
870
|
+
editor.view.dispatch(tr);
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
},
|
|
806
877
|
addProseMirrorPlugins() {
|
|
807
878
|
return [...this.parent?.() || [], PrismPlugin({
|
|
808
879
|
name: this.name,
|
|
@@ -1583,10 +1654,658 @@ const TableHeader = __tiptap_core.Node.create({
|
|
|
1583
1654
|
}
|
|
1584
1655
|
});
|
|
1585
1656
|
|
|
1657
|
+
//#endregion
|
|
1658
|
+
//#region src/extensions/uppercase.ts
|
|
1659
|
+
const Uppercase = __tiptap_core.Mark.create({
|
|
1660
|
+
name: "uppercase",
|
|
1661
|
+
addOptions() {
|
|
1662
|
+
return { HTMLAttributes: {} };
|
|
1663
|
+
},
|
|
1664
|
+
parseHTML() {
|
|
1665
|
+
return [{
|
|
1666
|
+
tag: "span",
|
|
1667
|
+
getAttrs: (node) => {
|
|
1668
|
+
if (node.style.textTransform === "uppercase") return {};
|
|
1669
|
+
return false;
|
|
1670
|
+
}
|
|
1671
|
+
}];
|
|
1672
|
+
},
|
|
1673
|
+
renderHTML({ HTMLAttributes }) {
|
|
1674
|
+
return [
|
|
1675
|
+
"span",
|
|
1676
|
+
(0, __tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes, { style: "text-transform: uppercase" }),
|
|
1677
|
+
0
|
|
1678
|
+
];
|
|
1679
|
+
},
|
|
1680
|
+
addCommands() {
|
|
1681
|
+
return {
|
|
1682
|
+
setUppercase: () => ({ commands }) => {
|
|
1683
|
+
return commands.setMark(this.name);
|
|
1684
|
+
},
|
|
1685
|
+
toggleUppercase: () => ({ commands }) => {
|
|
1686
|
+
return commands.toggleMark(this.name);
|
|
1687
|
+
},
|
|
1688
|
+
unsetUppercase: () => ({ commands }) => {
|
|
1689
|
+
return commands.unsetMark(this.name);
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
//#endregion
|
|
1696
|
+
//#region src/utils/set-text-alignment.ts
|
|
1697
|
+
function setTextAlignment(editor, alignment) {
|
|
1698
|
+
const { from, to } = editor.state.selection;
|
|
1699
|
+
const tr = editor.state.tr;
|
|
1700
|
+
editor.state.doc.nodesBetween(from, to, (node, pos) => {
|
|
1701
|
+
if (node.isTextblock) {
|
|
1702
|
+
const prop = "align" in node.attrs ? "align" : "alignment";
|
|
1703
|
+
tr.setNodeMarkup(pos, null, {
|
|
1704
|
+
...node.attrs,
|
|
1705
|
+
[prop]: alignment
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
editor.view.dispatch(tr);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
//#endregion
|
|
1713
|
+
//#region src/ui/bubble-menu/context.tsx
|
|
1714
|
+
const BubbleMenuContext = react.createContext(null);
|
|
1715
|
+
function useBubbleMenuContext() {
|
|
1716
|
+
const context = react.useContext(BubbleMenuContext);
|
|
1717
|
+
if (!context) throw new Error("BubbleMenu compound components must be used within <BubbleMenu.Root>");
|
|
1718
|
+
return context;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
//#endregion
|
|
1722
|
+
//#region src/ui/bubble-menu/item.tsx
|
|
1723
|
+
function BubbleMenuItem({ name, isActive, onCommand, className, children,...rest }) {
|
|
1724
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1725
|
+
type: "button",
|
|
1726
|
+
"aria-label": name,
|
|
1727
|
+
"aria-pressed": isActive,
|
|
1728
|
+
className,
|
|
1729
|
+
"data-re-bubble-menu-item": "",
|
|
1730
|
+
"data-item": name,
|
|
1731
|
+
...isActive ? { "data-active": "" } : {},
|
|
1732
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1733
|
+
onClick: onCommand,
|
|
1734
|
+
...rest,
|
|
1735
|
+
children
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
//#endregion
|
|
1740
|
+
//#region src/ui/bubble-menu/align-center.tsx
|
|
1741
|
+
function BubbleMenuAlignCenter({ className, children }) {
|
|
1742
|
+
const { editor } = useBubbleMenuContext();
|
|
1743
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
|
|
1744
|
+
name: "align-center",
|
|
1745
|
+
isActive: (0, __tiptap_react.useEditorState)({
|
|
1746
|
+
editor,
|
|
1747
|
+
selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "center" }) ?? false
|
|
1748
|
+
}),
|
|
1749
|
+
onCommand: () => setTextAlignment(editor, "center"),
|
|
1750
|
+
className,
|
|
1751
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignCenterIcon, {})
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
//#endregion
|
|
1756
|
+
//#region src/ui/bubble-menu/align-left.tsx
|
|
1757
|
+
function BubbleMenuAlignLeft({ className, children }) {
|
|
1758
|
+
const { editor } = useBubbleMenuContext();
|
|
1759
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
|
|
1760
|
+
name: "align-left",
|
|
1761
|
+
isActive: (0, __tiptap_react.useEditorState)({
|
|
1762
|
+
editor,
|
|
1763
|
+
selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "left" }) ?? false
|
|
1764
|
+
}),
|
|
1765
|
+
onCommand: () => setTextAlignment(editor, "left"),
|
|
1766
|
+
className,
|
|
1767
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignLeftIcon, {})
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
//#endregion
|
|
1772
|
+
//#region src/ui/bubble-menu/align-right.tsx
|
|
1773
|
+
function BubbleMenuAlignRight({ className, children }) {
|
|
1774
|
+
const { editor } = useBubbleMenuContext();
|
|
1775
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
|
|
1776
|
+
name: "align-right",
|
|
1777
|
+
isActive: (0, __tiptap_react.useEditorState)({
|
|
1778
|
+
editor,
|
|
1779
|
+
selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "right" }) ?? false
|
|
1780
|
+
}),
|
|
1781
|
+
onCommand: () => setTextAlignment(editor, "right"),
|
|
1782
|
+
className,
|
|
1783
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignRightIcon, {})
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
//#endregion
|
|
1788
|
+
//#region src/ui/bubble-menu/create-mark-bubble-item.tsx
|
|
1789
|
+
function createMarkBubbleItem(config) {
|
|
1790
|
+
function MarkBubbleItem({ className, children }) {
|
|
1791
|
+
const { editor } = useBubbleMenuContext();
|
|
1792
|
+
const isActive = (0, __tiptap_react.useEditorState)({
|
|
1793
|
+
editor,
|
|
1794
|
+
selector: ({ editor: editor$1 }) => {
|
|
1795
|
+
if (config.activeParams) return editor$1?.isActive(config.activeName, config.activeParams) ?? false;
|
|
1796
|
+
return editor$1?.isActive(config.activeName) ?? false;
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
const handleCommand = () => {
|
|
1800
|
+
const chain = editor.chain().focus();
|
|
1801
|
+
const method = chain[config.command];
|
|
1802
|
+
if (method) method.call(chain).run();
|
|
1803
|
+
};
|
|
1804
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
|
|
1805
|
+
name: config.name,
|
|
1806
|
+
isActive,
|
|
1807
|
+
onCommand: handleCommand,
|
|
1808
|
+
className,
|
|
1809
|
+
children: children ?? config.icon
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
MarkBubbleItem.displayName = `BubbleMenu${config.name.charAt(0).toUpperCase() + config.name.slice(1)}`;
|
|
1813
|
+
return MarkBubbleItem;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
//#endregion
|
|
1817
|
+
//#region src/ui/bubble-menu/bold.tsx
|
|
1818
|
+
const BubbleMenuBold = createMarkBubbleItem({
|
|
1819
|
+
name: "bold",
|
|
1820
|
+
activeName: "bold",
|
|
1821
|
+
command: "toggleBold",
|
|
1822
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.BoldIcon, {})
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
//#endregion
|
|
1826
|
+
//#region src/ui/bubble-menu/code.tsx
|
|
1827
|
+
const BubbleMenuCode = createMarkBubbleItem({
|
|
1828
|
+
name: "code",
|
|
1829
|
+
activeName: "code",
|
|
1830
|
+
command: "toggleCode",
|
|
1831
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CodeIcon, {})
|
|
1832
|
+
});
|
|
1833
|
+
|
|
1834
|
+
//#endregion
|
|
1835
|
+
//#region src/ui/bubble-menu/group.tsx
|
|
1836
|
+
function BubbleMenuItemGroup({ className, children }) {
|
|
1837
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("fieldset", {
|
|
1838
|
+
className,
|
|
1839
|
+
"data-re-bubble-menu-group": "",
|
|
1840
|
+
children
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
//#endregion
|
|
1845
|
+
//#region src/ui/bubble-menu/italic.tsx
|
|
1846
|
+
const BubbleMenuItalic = createMarkBubbleItem({
|
|
1847
|
+
name: "italic",
|
|
1848
|
+
activeName: "italic",
|
|
1849
|
+
command: "toggleItalic",
|
|
1850
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ItalicIcon, {})
|
|
1851
|
+
});
|
|
1852
|
+
|
|
1853
|
+
//#endregion
|
|
1854
|
+
//#region src/ui/bubble-menu/utils.ts
|
|
1855
|
+
const SAFE_PROTOCOLS = new Set([
|
|
1856
|
+
"http:",
|
|
1857
|
+
"https:",
|
|
1858
|
+
"mailto:",
|
|
1859
|
+
"tel:"
|
|
1860
|
+
]);
|
|
1861
|
+
/**
|
|
1862
|
+
* Basic URL validation and auto-prefixing.
|
|
1863
|
+
* Rejects dangerous schemes (javascript:, data:, vbscript:, etc.).
|
|
1864
|
+
* Returns the valid URL string or null.
|
|
1865
|
+
*/
|
|
1866
|
+
function getUrlFromString(str) {
|
|
1867
|
+
if (str === "#") return str;
|
|
1868
|
+
try {
|
|
1869
|
+
const url = new URL(str);
|
|
1870
|
+
if (SAFE_PROTOCOLS.has(url.protocol)) return str;
|
|
1871
|
+
return null;
|
|
1872
|
+
} catch {}
|
|
1873
|
+
try {
|
|
1874
|
+
if (str.includes(".") && !str.includes(" ")) return new URL(`https://${str}`).toString();
|
|
1875
|
+
} catch {}
|
|
1876
|
+
return null;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
//#endregion
|
|
1880
|
+
//#region src/ui/bubble-menu/link-selector.tsx
|
|
1881
|
+
function BubbleMenuLinkSelector({ className, showToggle = true, validateUrl, onLinkApply, onLinkRemove, children }) {
|
|
1882
|
+
const { editor } = useBubbleMenuContext();
|
|
1883
|
+
const [isOpen, setIsOpen] = react.useState(false);
|
|
1884
|
+
const editorState = (0, __tiptap_react.useEditorState)({
|
|
1885
|
+
editor,
|
|
1886
|
+
selector: ({ editor: editor$1 }) => ({
|
|
1887
|
+
isLinkActive: editor$1?.isActive("link") ?? false,
|
|
1888
|
+
hasLink: Boolean(editor$1?.getAttributes("link").href),
|
|
1889
|
+
currentHref: editor$1?.getAttributes("link").href || ""
|
|
1890
|
+
})
|
|
1891
|
+
});
|
|
1892
|
+
react.useEffect(() => {
|
|
1893
|
+
const subscription = editorEventBus.on("bubble-menu:add-link", () => {
|
|
1894
|
+
setIsOpen(true);
|
|
1895
|
+
});
|
|
1896
|
+
return () => {
|
|
1897
|
+
setIsOpen(false);
|
|
1898
|
+
subscription.unsubscribe();
|
|
1899
|
+
};
|
|
1900
|
+
}, []);
|
|
1901
|
+
if (!editorState) return null;
|
|
1902
|
+
const handleOpenLink = () => {
|
|
1903
|
+
setIsOpen(!isOpen);
|
|
1904
|
+
};
|
|
1905
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1906
|
+
"data-re-link-selector": "",
|
|
1907
|
+
...isOpen ? { "data-open": "" } : {},
|
|
1908
|
+
...editorState.hasLink ? { "data-has-link": "" } : {},
|
|
1909
|
+
className,
|
|
1910
|
+
children: [showToggle && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1911
|
+
type: "button",
|
|
1912
|
+
"aria-expanded": isOpen,
|
|
1913
|
+
"aria-haspopup": "true",
|
|
1914
|
+
"aria-label": "Add link",
|
|
1915
|
+
"aria-pressed": editorState.isLinkActive && editorState.hasLink,
|
|
1916
|
+
"data-re-link-selector-trigger": "",
|
|
1917
|
+
onClick: handleOpenLink,
|
|
1918
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.LinkIcon, {})
|
|
1919
|
+
}), isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkForm, {
|
|
1920
|
+
editor,
|
|
1921
|
+
currentHref: editorState.currentHref,
|
|
1922
|
+
validateUrl,
|
|
1923
|
+
onLinkApply,
|
|
1924
|
+
onLinkRemove,
|
|
1925
|
+
setIsOpen,
|
|
1926
|
+
children
|
|
1927
|
+
})]
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove, setIsOpen, children }) {
|
|
1931
|
+
const inputRef = react.useRef(null);
|
|
1932
|
+
const formRef = react.useRef(null);
|
|
1933
|
+
const displayHref = currentHref === "#" ? "" : currentHref;
|
|
1934
|
+
const [inputValue, setInputValue] = react.useState(displayHref);
|
|
1935
|
+
react.useEffect(() => {
|
|
1936
|
+
const timeoutId = setTimeout(() => {
|
|
1937
|
+
inputRef.current?.focus();
|
|
1938
|
+
}, 0);
|
|
1939
|
+
return () => clearTimeout(timeoutId);
|
|
1940
|
+
}, []);
|
|
1941
|
+
react.useEffect(() => {
|
|
1942
|
+
const handleKeyDown = (event) => {
|
|
1943
|
+
if (event.key === "Escape") {
|
|
1944
|
+
if (editor.getAttributes("link").href === "#") editor.chain().unsetLink().run();
|
|
1945
|
+
setIsOpen(false);
|
|
1946
|
+
}
|
|
1947
|
+
};
|
|
1948
|
+
const handleClickOutside = (event) => {
|
|
1949
|
+
if (formRef.current && !formRef.current.contains(event.target)) {
|
|
1950
|
+
const form = formRef.current;
|
|
1951
|
+
const submitEvent = new Event("submit", {
|
|
1952
|
+
bubbles: true,
|
|
1953
|
+
cancelable: true
|
|
1954
|
+
});
|
|
1955
|
+
form.dispatchEvent(submitEvent);
|
|
1956
|
+
setIsOpen(false);
|
|
1957
|
+
}
|
|
1958
|
+
};
|
|
1959
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
1960
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
1961
|
+
return () => {
|
|
1962
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
1963
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
1964
|
+
};
|
|
1965
|
+
}, [editor, setIsOpen]);
|
|
1966
|
+
function handleSubmit(e) {
|
|
1967
|
+
e.preventDefault();
|
|
1968
|
+
const value = inputValue.trim();
|
|
1969
|
+
if (value === "") {
|
|
1970
|
+
setLinkHref(editor, "");
|
|
1971
|
+
setIsOpen(false);
|
|
1972
|
+
focusEditor(editor);
|
|
1973
|
+
onLinkRemove?.();
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
const finalValue = (validateUrl ?? getUrlFromString)(value);
|
|
1977
|
+
if (!finalValue) {
|
|
1978
|
+
setLinkHref(editor, "");
|
|
1979
|
+
setIsOpen(false);
|
|
1980
|
+
focusEditor(editor);
|
|
1981
|
+
onLinkRemove?.();
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
setLinkHref(editor, finalValue);
|
|
1985
|
+
setIsOpen(false);
|
|
1986
|
+
focusEditor(editor);
|
|
1987
|
+
onLinkApply?.(finalValue);
|
|
1988
|
+
}
|
|
1989
|
+
function handleUnlink(e) {
|
|
1990
|
+
e.stopPropagation();
|
|
1991
|
+
setLinkHref(editor, "");
|
|
1992
|
+
setIsOpen(false);
|
|
1993
|
+
focusEditor(editor);
|
|
1994
|
+
onLinkRemove?.();
|
|
1995
|
+
}
|
|
1996
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1997
|
+
ref: formRef,
|
|
1998
|
+
"data-re-link-selector-form": "",
|
|
1999
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
2000
|
+
onClick: (e) => e.stopPropagation(),
|
|
2001
|
+
onKeyDown: (e) => e.stopPropagation(),
|
|
2002
|
+
onSubmit: handleSubmit,
|
|
2003
|
+
children: [
|
|
2004
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2005
|
+
ref: inputRef,
|
|
2006
|
+
"data-re-link-selector-input": "",
|
|
2007
|
+
value: inputValue,
|
|
2008
|
+
onFocus: (e) => e.stopPropagation(),
|
|
2009
|
+
onChange: (e) => setInputValue(e.target.value),
|
|
2010
|
+
placeholder: "Paste a link",
|
|
2011
|
+
type: "text"
|
|
2012
|
+
}),
|
|
2013
|
+
children,
|
|
2014
|
+
displayHref ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2015
|
+
type: "button",
|
|
2016
|
+
"aria-label": "Remove link",
|
|
2017
|
+
"data-re-link-selector-unlink": "",
|
|
2018
|
+
onClick: handleUnlink,
|
|
2019
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnlinkIcon, {})
|
|
2020
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2021
|
+
type: "submit",
|
|
2022
|
+
"aria-label": "Apply link",
|
|
2023
|
+
"data-re-link-selector-apply": "",
|
|
2024
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
2025
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {})
|
|
2026
|
+
})
|
|
2027
|
+
]
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
function setLinkHref(editor, href) {
|
|
2031
|
+
if (href.length === 0) {
|
|
2032
|
+
editor.chain().unsetLink().run();
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const { from, to } = editor.state.selection;
|
|
2036
|
+
if (from === to) {
|
|
2037
|
+
editor.chain().extendMarkRange("link").setLink({ href }).setTextSelection({
|
|
2038
|
+
from,
|
|
2039
|
+
to
|
|
2040
|
+
}).run();
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2043
|
+
editor.chain().setLink({ href }).run();
|
|
2044
|
+
}
|
|
2045
|
+
function focusEditor(editor) {
|
|
2046
|
+
setTimeout(() => {
|
|
2047
|
+
editor.commands.focus();
|
|
2048
|
+
}, 0);
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
//#endregion
|
|
2052
|
+
//#region src/ui/bubble-menu/node-selector.tsx
|
|
2053
|
+
const NodeSelectorContext = react.createContext(null);
|
|
2054
|
+
function useNodeSelectorContext() {
|
|
2055
|
+
const context = react.useContext(NodeSelectorContext);
|
|
2056
|
+
if (!context) throw new Error("NodeSelector compound components must be used within <NodeSelector.Root>");
|
|
2057
|
+
return context;
|
|
2058
|
+
}
|
|
2059
|
+
function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, className, children }) {
|
|
2060
|
+
const { editor } = useBubbleMenuContext();
|
|
2061
|
+
const [uncontrolledOpen, setUncontrolledOpen] = react.useState(false);
|
|
2062
|
+
const isControlled = controlledOpen !== void 0;
|
|
2063
|
+
const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
|
|
2064
|
+
const setIsOpen = react.useCallback((value) => {
|
|
2065
|
+
if (!isControlled) setUncontrolledOpen(value);
|
|
2066
|
+
onOpenChange?.(value);
|
|
2067
|
+
}, [isControlled, onOpenChange]);
|
|
2068
|
+
const editorState = (0, __tiptap_react.useEditorState)({
|
|
2069
|
+
editor,
|
|
2070
|
+
selector: ({ editor: editor$1 }) => ({
|
|
2071
|
+
isParagraphActive: (editor$1?.isActive("paragraph") ?? false) && !editor$1?.isActive("bulletList") && !editor$1?.isActive("orderedList"),
|
|
2072
|
+
isHeading1Active: editor$1?.isActive("heading", { level: 1 }) ?? false,
|
|
2073
|
+
isHeading2Active: editor$1?.isActive("heading", { level: 2 }) ?? false,
|
|
2074
|
+
isHeading3Active: editor$1?.isActive("heading", { level: 3 }) ?? false,
|
|
2075
|
+
isBulletListActive: editor$1?.isActive("bulletList") ?? false,
|
|
2076
|
+
isOrderedListActive: editor$1?.isActive("orderedList") ?? false,
|
|
2077
|
+
isBlockquoteActive: editor$1?.isActive("blockquote") ?? false,
|
|
2078
|
+
isCodeBlockActive: editor$1?.isActive("codeBlock") ?? false
|
|
2079
|
+
})
|
|
2080
|
+
});
|
|
2081
|
+
const allItems = react.useMemo(() => [
|
|
2082
|
+
{
|
|
2083
|
+
name: "Text",
|
|
2084
|
+
icon: lucide_react.TextIcon,
|
|
2085
|
+
command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").run(),
|
|
2086
|
+
isActive: editorState?.isParagraphActive ?? false
|
|
2087
|
+
},
|
|
2088
|
+
{
|
|
2089
|
+
name: "Title",
|
|
2090
|
+
icon: lucide_react.Heading1,
|
|
2091
|
+
command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
|
|
2092
|
+
isActive: editorState?.isHeading1Active ?? false
|
|
2093
|
+
},
|
|
2094
|
+
{
|
|
2095
|
+
name: "Subtitle",
|
|
2096
|
+
icon: lucide_react.Heading2,
|
|
2097
|
+
command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
|
|
2098
|
+
isActive: editorState?.isHeading2Active ?? false
|
|
2099
|
+
},
|
|
2100
|
+
{
|
|
2101
|
+
name: "Heading",
|
|
2102
|
+
icon: lucide_react.Heading3,
|
|
2103
|
+
command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
|
|
2104
|
+
isActive: editorState?.isHeading3Active ?? false
|
|
2105
|
+
},
|
|
2106
|
+
{
|
|
2107
|
+
name: "Bullet List",
|
|
2108
|
+
icon: lucide_react.List,
|
|
2109
|
+
command: () => editor.chain().focus().clearNodes().toggleBulletList().run(),
|
|
2110
|
+
isActive: editorState?.isBulletListActive ?? false
|
|
2111
|
+
},
|
|
2112
|
+
{
|
|
2113
|
+
name: "Numbered List",
|
|
2114
|
+
icon: lucide_react.ListOrdered,
|
|
2115
|
+
command: () => editor.chain().focus().clearNodes().toggleOrderedList().run(),
|
|
2116
|
+
isActive: editorState?.isOrderedListActive ?? false
|
|
2117
|
+
},
|
|
2118
|
+
{
|
|
2119
|
+
name: "Quote",
|
|
2120
|
+
icon: lucide_react.TextQuote,
|
|
2121
|
+
command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
|
|
2122
|
+
isActive: editorState?.isBlockquoteActive ?? false
|
|
2123
|
+
},
|
|
2124
|
+
{
|
|
2125
|
+
name: "Code",
|
|
2126
|
+
icon: lucide_react.Code,
|
|
2127
|
+
command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
|
|
2128
|
+
isActive: editorState?.isCodeBlockActive ?? false
|
|
2129
|
+
}
|
|
2130
|
+
], [editor, editorState]);
|
|
2131
|
+
const items = react.useMemo(() => allItems.filter((item) => !omit.includes(item.name)), [allItems, omit]);
|
|
2132
|
+
const activeItem = react.useMemo(() => items.find((item) => item.isActive) ?? { name: "Multiple" }, [items]);
|
|
2133
|
+
const contextValue = react.useMemo(() => ({
|
|
2134
|
+
items,
|
|
2135
|
+
activeItem,
|
|
2136
|
+
isOpen,
|
|
2137
|
+
setIsOpen
|
|
2138
|
+
}), [
|
|
2139
|
+
items,
|
|
2140
|
+
activeItem,
|
|
2141
|
+
isOpen,
|
|
2142
|
+
setIsOpen
|
|
2143
|
+
]);
|
|
2144
|
+
if (!editorState || items.length === 0) return null;
|
|
2145
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorContext.Provider, {
|
|
2146
|
+
value: contextValue,
|
|
2147
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__radix_ui_react_popover.Root, {
|
|
2148
|
+
open: isOpen,
|
|
2149
|
+
onOpenChange: setIsOpen,
|
|
2150
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2151
|
+
"data-re-node-selector": "",
|
|
2152
|
+
...isOpen ? { "data-open": "" } : {},
|
|
2153
|
+
className,
|
|
2154
|
+
children
|
|
2155
|
+
})
|
|
2156
|
+
})
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
function NodeSelectorTrigger({ className, children }) {
|
|
2160
|
+
const { activeItem, isOpen, setIsOpen } = useNodeSelectorContext();
|
|
2161
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__radix_ui_react_popover.Trigger, {
|
|
2162
|
+
"data-re-node-selector-trigger": "",
|
|
2163
|
+
className,
|
|
2164
|
+
onClick: () => setIsOpen(!isOpen),
|
|
2165
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: activeItem.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {})] })
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2168
|
+
function NodeSelectorContent({ className, align = "start", children }) {
|
|
2169
|
+
const { items, setIsOpen } = useNodeSelectorContext();
|
|
2170
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__radix_ui_react_popover.Content, {
|
|
2171
|
+
align,
|
|
2172
|
+
"data-re-node-selector-content": "",
|
|
2173
|
+
className,
|
|
2174
|
+
children: children ? children(items, () => setIsOpen(false)) : items.map((item) => {
|
|
2175
|
+
const Icon = item.icon;
|
|
2176
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2177
|
+
type: "button",
|
|
2178
|
+
"data-re-node-selector-item": "",
|
|
2179
|
+
...item.isActive ? { "data-active": "" } : {},
|
|
2180
|
+
onClick: () => {
|
|
2181
|
+
item.command();
|
|
2182
|
+
setIsOpen(false);
|
|
2183
|
+
},
|
|
2184
|
+
children: [
|
|
2185
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {}),
|
|
2186
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.name }),
|
|
2187
|
+
item.isActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {})
|
|
2188
|
+
]
|
|
2189
|
+
}, item.name);
|
|
2190
|
+
})
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2193
|
+
function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, onOpenChange }) {
|
|
2194
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(NodeSelectorRoot, {
|
|
2195
|
+
omit,
|
|
2196
|
+
open,
|
|
2197
|
+
onOpenChange,
|
|
2198
|
+
className,
|
|
2199
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorTrigger, { children: triggerContent }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorContent, {})]
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
//#endregion
|
|
2204
|
+
//#region src/ui/bubble-menu/root.tsx
|
|
2205
|
+
function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset = 8, onHide, className, children }) {
|
|
2206
|
+
const { editor } = (0, __tiptap_react.useCurrentEditor)();
|
|
2207
|
+
if (!editor) return null;
|
|
2208
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__tiptap_react_menus.BubbleMenu, {
|
|
2209
|
+
editor,
|
|
2210
|
+
"data-re-bubble-menu": "",
|
|
2211
|
+
shouldShow: ({ editor: editor$1, view }) => {
|
|
2212
|
+
for (const node of excludeNodes) if (editor$1.isActive(node)) return false;
|
|
2213
|
+
if (view.dom.classList.contains("dragging")) return false;
|
|
2214
|
+
return editor$1.view.state.selection.content().size > 0;
|
|
2215
|
+
},
|
|
2216
|
+
options: {
|
|
2217
|
+
placement,
|
|
2218
|
+
offset,
|
|
2219
|
+
onHide
|
|
2220
|
+
},
|
|
2221
|
+
className,
|
|
2222
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuContext.Provider, {
|
|
2223
|
+
value: { editor },
|
|
2224
|
+
children
|
|
2225
|
+
})
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
//#endregion
|
|
2230
|
+
//#region src/ui/bubble-menu/separator.tsx
|
|
2231
|
+
function BubbleMenuSeparator({ className }) {
|
|
2232
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", {
|
|
2233
|
+
className,
|
|
2234
|
+
"data-re-bubble-menu-separator": ""
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
//#endregion
|
|
2239
|
+
//#region src/ui/bubble-menu/strike.tsx
|
|
2240
|
+
const BubbleMenuStrike = createMarkBubbleItem({
|
|
2241
|
+
name: "strike",
|
|
2242
|
+
activeName: "strike",
|
|
2243
|
+
command: "toggleStrike",
|
|
2244
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.StrikethroughIcon, {})
|
|
2245
|
+
});
|
|
2246
|
+
|
|
2247
|
+
//#endregion
|
|
2248
|
+
//#region src/ui/bubble-menu/underline.tsx
|
|
2249
|
+
const BubbleMenuUnderline = createMarkBubbleItem({
|
|
2250
|
+
name: "underline",
|
|
2251
|
+
activeName: "underline",
|
|
2252
|
+
command: "toggleUnderline",
|
|
2253
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnderlineIcon, {})
|
|
2254
|
+
});
|
|
2255
|
+
|
|
2256
|
+
//#endregion
|
|
2257
|
+
//#region src/ui/bubble-menu/uppercase.tsx
|
|
2258
|
+
const BubbleMenuUppercase = createMarkBubbleItem({
|
|
2259
|
+
name: "uppercase",
|
|
2260
|
+
activeName: "uppercase",
|
|
2261
|
+
command: "toggleUppercase",
|
|
2262
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CaseUpperIcon, {})
|
|
2263
|
+
});
|
|
2264
|
+
|
|
2265
|
+
//#endregion
|
|
2266
|
+
//#region src/ui/bubble-menu/index.ts
|
|
2267
|
+
const BubbleMenu = {
|
|
2268
|
+
Root: BubbleMenuRoot,
|
|
2269
|
+
ItemGroup: BubbleMenuItemGroup,
|
|
2270
|
+
Separator: BubbleMenuSeparator,
|
|
2271
|
+
Item: BubbleMenuItem,
|
|
2272
|
+
Bold: BubbleMenuBold,
|
|
2273
|
+
Italic: BubbleMenuItalic,
|
|
2274
|
+
Underline: BubbleMenuUnderline,
|
|
2275
|
+
Strike: BubbleMenuStrike,
|
|
2276
|
+
Code: BubbleMenuCode,
|
|
2277
|
+
Uppercase: BubbleMenuUppercase,
|
|
2278
|
+
AlignLeft: BubbleMenuAlignLeft,
|
|
2279
|
+
AlignCenter: BubbleMenuAlignCenter,
|
|
2280
|
+
AlignRight: BubbleMenuAlignRight,
|
|
2281
|
+
NodeSelector: Object.assign(BubbleMenuNodeSelector, {
|
|
2282
|
+
Root: NodeSelectorRoot,
|
|
2283
|
+
Trigger: NodeSelectorTrigger,
|
|
2284
|
+
Content: NodeSelectorContent
|
|
2285
|
+
}),
|
|
2286
|
+
LinkSelector: BubbleMenuLinkSelector
|
|
2287
|
+
};
|
|
2288
|
+
|
|
1586
2289
|
//#endregion
|
|
1587
2290
|
exports.AlignmentAttribute = AlignmentAttribute;
|
|
1588
2291
|
exports.Body = Body;
|
|
1589
2292
|
exports.Bold = Bold;
|
|
2293
|
+
exports.BubbleMenu = BubbleMenu;
|
|
2294
|
+
exports.BubbleMenuAlignCenter = BubbleMenuAlignCenter;
|
|
2295
|
+
exports.BubbleMenuAlignLeft = BubbleMenuAlignLeft;
|
|
2296
|
+
exports.BubbleMenuAlignRight = BubbleMenuAlignRight;
|
|
2297
|
+
exports.BubbleMenuBold = BubbleMenuBold;
|
|
2298
|
+
exports.BubbleMenuCode = BubbleMenuCode;
|
|
2299
|
+
exports.BubbleMenuItalic = BubbleMenuItalic;
|
|
2300
|
+
exports.BubbleMenuItem = BubbleMenuItem;
|
|
2301
|
+
exports.BubbleMenuItemGroup = BubbleMenuItemGroup;
|
|
2302
|
+
exports.BubbleMenuLinkSelector = BubbleMenuLinkSelector;
|
|
2303
|
+
exports.BubbleMenuNodeSelector = BubbleMenuNodeSelector;
|
|
2304
|
+
exports.BubbleMenuRoot = BubbleMenuRoot;
|
|
2305
|
+
exports.BubbleMenuSeparator = BubbleMenuSeparator;
|
|
2306
|
+
exports.BubbleMenuStrike = BubbleMenuStrike;
|
|
2307
|
+
exports.BubbleMenuUnderline = BubbleMenuUnderline;
|
|
2308
|
+
exports.BubbleMenuUppercase = BubbleMenuUppercase;
|
|
1590
2309
|
exports.Button = Button;
|
|
1591
2310
|
exports.COLUMN_PARENT_TYPES = COLUMN_PARENT_TYPES;
|
|
1592
2311
|
exports.ClassAttribute = ClassAttribute;
|
|
@@ -1597,6 +2316,9 @@ exports.EmailNode = EmailNode;
|
|
|
1597
2316
|
exports.FourColumns = FourColumns;
|
|
1598
2317
|
exports.MAX_COLUMNS_DEPTH = MAX_COLUMNS_DEPTH;
|
|
1599
2318
|
exports.MaxNesting = MaxNesting;
|
|
2319
|
+
exports.NodeSelectorContent = NodeSelectorContent;
|
|
2320
|
+
exports.NodeSelectorRoot = NodeSelectorRoot;
|
|
2321
|
+
exports.NodeSelectorTrigger = NodeSelectorTrigger;
|
|
1600
2322
|
exports.Placeholder = Placeholder;
|
|
1601
2323
|
exports.PreservedStyle = PreservedStyle;
|
|
1602
2324
|
exports.PreviewText = PreviewText;
|
|
@@ -1609,5 +2331,8 @@ exports.TableHeader = TableHeader;
|
|
|
1609
2331
|
exports.TableRow = TableRow;
|
|
1610
2332
|
exports.ThreeColumns = ThreeColumns;
|
|
1611
2333
|
exports.TwoColumns = TwoColumns;
|
|
2334
|
+
exports.Uppercase = Uppercase;
|
|
2335
|
+
exports.editorEventBus = editorEventBus;
|
|
1612
2336
|
exports.getColumnsDepth = getColumnsDepth;
|
|
1613
|
-
exports.processStylesForUnlink = processStylesForUnlink;
|
|
2337
|
+
exports.processStylesForUnlink = processStylesForUnlink;
|
|
2338
|
+
exports.setTextAlignment = setTextAlignment;
|