@voyantjs/ui 0.30.7 → 0.31.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-editor.d.ts","sourceRoot":"","sources":["../../src/components/rich-text-editor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"rich-text-editor.d.ts","sourceRoot":"","sources":["../../src/components/rich-text-editor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,cAAc,CAAA;AAuBtD,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAChD,CAAA;AAmGD,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,WAAgC,EAChC,QAAgB,EAChB,SAAS,EACT,eAAe,EACf,eAAuB,EACvB,aAAa,GACd,EAAE,mBAAmB,2CA6NrB"}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Placeholder } from "@tiptap/extensions/placeholder";
|
|
3
3
|
import { EditorContent, useEditor } from "@tiptap/react";
|
|
4
4
|
import StarterKit from "@tiptap/starter-kit";
|
|
5
|
-
import { Bold, Heading2, Heading3, Italic, List, ListOrdered, Quote, Redo, Strikethrough, Undo, } from "lucide-react";
|
|
5
|
+
import { Bold, Heading2, Heading3, Italic, Link, List, ListOrdered, Quote, Redo, Strikethrough, Undo, Unlink, } from "lucide-react";
|
|
6
6
|
import { useEffect } from "react";
|
|
7
7
|
import { cn } from "../lib/utils.js";
|
|
8
8
|
import { Button } from "./button.js";
|
|
@@ -11,6 +11,50 @@ const EMPTY_CONTENT = "<p></p>";
|
|
|
11
11
|
function normalizeEditorContent(value) {
|
|
12
12
|
return value.trim() ? value : EMPTY_CONTENT;
|
|
13
13
|
}
|
|
14
|
+
function isAllowedLinkUri(uri) {
|
|
15
|
+
const value = uri.trim();
|
|
16
|
+
if (!value) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (value.startsWith("/") ||
|
|
20
|
+
value.startsWith("#") ||
|
|
21
|
+
value.startsWith("./") ||
|
|
22
|
+
value.startsWith("../")) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const parsed = new URL(value);
|
|
27
|
+
return ["http:", "https:", "mailto:", "tel:"].includes(parsed.protocol);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function isRelativeLinkUri(uri) {
|
|
34
|
+
const value = uri.trim();
|
|
35
|
+
return (value.startsWith("/") ||
|
|
36
|
+
value.startsWith("#") ||
|
|
37
|
+
value.startsWith("./") ||
|
|
38
|
+
value.startsWith("../"));
|
|
39
|
+
}
|
|
40
|
+
function hasLinkProtocol(uri) {
|
|
41
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(uri.trim());
|
|
42
|
+
}
|
|
43
|
+
function normalizeLinkHref(href) {
|
|
44
|
+
const value = href.trim();
|
|
45
|
+
if (!value) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
if (value.startsWith("/") ||
|
|
49
|
+
value.startsWith("#") ||
|
|
50
|
+
value.startsWith("./") ||
|
|
51
|
+
value.startsWith("../") ||
|
|
52
|
+
hasLinkProtocol(value)) {
|
|
53
|
+
return isAllowedLinkUri(value) ? value : null;
|
|
54
|
+
}
|
|
55
|
+
const withProtocol = `https://${value}`;
|
|
56
|
+
return isAllowedLinkUri(withProtocol) ? withProtocol : null;
|
|
57
|
+
}
|
|
14
58
|
function ToolbarButton({ active = false, disabled = false, label, onClick, children, }) {
|
|
15
59
|
return (_jsx(Button, { type: "button", variant: active ? "default" : "outline", size: "sm", className: "h-8 w-8 p-0", disabled: disabled, onClick: onClick, "aria-label": label, title: label, children: children }));
|
|
16
60
|
}
|
|
@@ -20,6 +64,26 @@ export function RichTextEditor({ value, onChange, placeholder = "Write something
|
|
|
20
64
|
heading: {
|
|
21
65
|
levels: [2, 3],
|
|
22
66
|
},
|
|
67
|
+
link: {
|
|
68
|
+
autolink: true,
|
|
69
|
+
defaultProtocol: "https",
|
|
70
|
+
linkOnPaste: true,
|
|
71
|
+
openOnClick: false,
|
|
72
|
+
protocols: ["mailto", "tel"],
|
|
73
|
+
HTMLAttributes: {
|
|
74
|
+
rel: "noopener noreferrer",
|
|
75
|
+
target: "_blank",
|
|
76
|
+
},
|
|
77
|
+
isAllowedUri: (url, { defaultValidate }) => {
|
|
78
|
+
if (isRelativeLinkUri(url)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (hasLinkProtocol(url)) {
|
|
82
|
+
return isAllowedLinkUri(url) && defaultValidate(url);
|
|
83
|
+
}
|
|
84
|
+
return defaultValidate(url) && isAllowedLinkUri(`https://${url.trim()}`);
|
|
85
|
+
},
|
|
86
|
+
},
|
|
23
87
|
}),
|
|
24
88
|
Placeholder.configure({
|
|
25
89
|
placeholder,
|
|
@@ -67,5 +131,24 @@ export function RichTextEditor({ value, onChange, placeholder = "Write something
|
|
|
67
131
|
onEditorReady(null);
|
|
68
132
|
};
|
|
69
133
|
}, [editor, onEditorReady]);
|
|
70
|
-
|
|
134
|
+
const setLink = () => {
|
|
135
|
+
if (!editor) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const previousHref = editor.getAttributes("link").href;
|
|
139
|
+
const href = window.prompt("Link URL", typeof previousHref === "string" ? previousHref : "");
|
|
140
|
+
if (href === null) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (href.trim() === "") {
|
|
144
|
+
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const normalizedHref = normalizeLinkHref(href);
|
|
148
|
+
if (!normalizedHref) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
editor.chain().focus().extendMarkRange("link").setLink({ href: normalizedHref }).run();
|
|
152
|
+
};
|
|
153
|
+
return (_jsxs("div", { className: cn("rounded-md border border-input bg-transparent", className), children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: [_jsx(ToolbarButton, { label: "Bold", active: editor?.isActive("bold"), disabled: !editor?.can().chain().focus().toggleBold().run(), onClick: () => editor?.chain().focus().toggleBold().run(), children: _jsx(Bold, { className: "h-4 w-4" }) }), _jsx(ToolbarButton, { label: "Italic", active: editor?.isActive("italic"), disabled: !editor?.can().chain().focus().toggleItalic().run(), onClick: () => editor?.chain().focus().toggleItalic().run(), children: _jsx(Italic, { className: "h-4 w-4" }) }), _jsx(ToolbarButton, { label: "Strike", active: editor?.isActive("strike"), disabled: !editor?.can().chain().focus().toggleStrike().run(), onClick: () => editor?.chain().focus().toggleStrike().run(), children: _jsx(Strikethrough, { className: "h-4 w-4" }) }), _jsx(ToolbarButton, { label: "Heading 2", active: editor?.isActive("heading", { level: 2 }), disabled: !editor?.can().chain().focus().toggleHeading({ level: 2 }).run(), onClick: () => editor?.chain().focus().toggleHeading({ level: 2 }).run(), children: _jsx(Heading2, { className: "h-4 w-4" }) }), _jsx(ToolbarButton, { label: "Heading 3", active: editor?.isActive("heading", { level: 3 }), disabled: !editor?.can().chain().focus().toggleHeading({ level: 3 }).run(), onClick: () => editor?.chain().focus().toggleHeading({ level: 3 }).run(), children: _jsx(Heading3, { className: "h-4 w-4" }) }), _jsx(ToolbarButton, { label: "Bullet list", active: editor?.isActive("bulletList"), disabled: !editor?.can().chain().focus().toggleBulletList().run(), onClick: () => editor?.chain().focus().toggleBulletList().run(), children: _jsx(List, { className: "h-4 w-4" }) }), _jsx(ToolbarButton, { label: "Ordered list", active: editor?.isActive("orderedList"), disabled: !editor?.can().chain().focus().toggleOrderedList().run(), onClick: () => editor?.chain().focus().toggleOrderedList().run(), children: _jsx(ListOrdered, { className: "h-4 w-4" }) }), _jsx(ToolbarButton, { label: "Blockquote", active: editor?.isActive("blockquote"), disabled: !editor?.can().chain().focus().toggleBlockquote().run(), onClick: () => editor?.chain().focus().toggleBlockquote().run(), children: _jsx(Quote, { className: "h-4 w-4" }) }), _jsx(ToolbarButton, { label: "Link", active: editor?.isActive("link"), disabled: !editor || disabled, onClick: setLink, children: _jsx(Link, { className: "h-4 w-4" }) }), _jsx(ToolbarButton, { label: "Remove link", disabled: !editor?.isActive("link"), onClick: () => editor?.chain().focus().extendMarkRange("link").unsetLink().run(), children: _jsx(Unlink, { className: "h-4 w-4" }) }), _jsxs("div", { className: "ml-auto flex items-center gap-2", children: [_jsx(ToolbarButton, { label: "Undo", disabled: !editor?.can().chain().focus().undo().run(), onClick: () => editor?.chain().focus().undo().run(), children: _jsx(Undo, { className: "h-4 w-4" }) }), _jsx(ToolbarButton, { label: "Redo", disabled: !editor?.can().chain().focus().redo().run(), onClick: () => editor?.chain().focus().redo().run(), children: _jsx(Redo, { className: "h-4 w-4" }) })] })] }), _jsx("div", { className: cn("[&_.ProseMirror_p.is-editor-empty:first-child::before]:pointer-events-none [&_.ProseMirror_p.is-editor-empty:first-child::before]:float-left [&_.ProseMirror_p.is-editor-empty:first-child::before]:h-0 [&_.ProseMirror_p.is-editor-empty:first-child::before]:text-muted-foreground [&_.ProseMirror_p.is-editor-empty:first-child::before]:content-[attr(data-placeholder)] [&_.variable-node]:inline-flex [&_.variable-node]:items-center [&_.variable-node]:rounded-md [&_.variable-node]:border [&_.variable-node]:border-emerald-500/30 [&_.variable-node]:bg-emerald-500/10 [&_.variable-node]:px-1.5 [&_.variable-node]:py-0.5 [&_.variable-node]:font-mono [&_.variable-node]:text-xs [&_.variable-node]:text-emerald-200", editorClassName), children: _jsx(EditorContent, { editor: editor }) })] }));
|
|
71
154
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyantjs/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"tw-animate-css": "^1.3.5",
|
|
36
36
|
"vaul": "^1.1.2",
|
|
37
37
|
"zod": "^4.3.6",
|
|
38
|
-
"@voyantjs/i18n": "0.
|
|
39
|
-
"@voyantjs/notifications": "0.
|
|
40
|
-
"@voyantjs/notifications-react": "0.
|
|
41
|
-
"@voyantjs/utils": "0.
|
|
38
|
+
"@voyantjs/i18n": "0.31.1",
|
|
39
|
+
"@voyantjs/notifications": "0.31.1",
|
|
40
|
+
"@voyantjs/notifications-react": "0.31.1",
|
|
41
|
+
"@voyantjs/utils": "0.31.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@tailwindcss/postcss": "^4.1.11",
|