@voyantjs/ui 0.31.0 → 0.31.2

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;AAqBtD,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;AAuCD,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,WAAgC,EAChC,QAAgB,EAChB,SAAS,EACT,eAAe,EACf,eAAuB,EACvB,aAAa,GACd,EAAE,mBAAmB,2CA8JrB"}
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
- 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" }) }), _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 }) })] }));
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.31.0",
3
+ "version": "0.31.2",
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.31.0",
39
- "@voyantjs/notifications": "0.31.0",
40
- "@voyantjs/notifications-react": "0.31.0",
41
- "@voyantjs/utils": "0.31.0"
38
+ "@voyantjs/i18n": "0.31.2",
39
+ "@voyantjs/notifications": "0.31.2",
40
+ "@voyantjs/notifications-react": "0.31.2",
41
+ "@voyantjs/utils": "0.31.2"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@tailwindcss/postcss": "^4.1.11",