@tulip-systems/editor 0.11.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.
- package/LICENSE +662 -0
- package/dist/client.d.mts +8 -0
- package/dist/client.mjs +8 -0
- package/dist/components/content.client.d.mts +4 -0
- package/dist/components/content.client.mjs +13 -0
- package/dist/components/editor.client.d.mts +25 -0
- package/dist/components/editor.client.mjs +49 -0
- package/dist/components/menu-nodes.client.mjs +90 -0
- package/dist/components/menu.client.d.mts +7 -0
- package/dist/components/menu.client.mjs +154 -0
- package/dist/extensions/file-handler/extension.d.mts +9 -0
- package/dist/extensions/file-handler/extension.mjs +35 -0
- package/dist/extensions/file-handler/utils.mjs +28 -0
- package/dist/extensions/image/extension.d.mts +9 -0
- package/dist/extensions/image/extension.mjs +41 -0
- package/dist/extensions/skeleton/extension.mjs +37 -0
- package/dist/extensions/skeleton/renderer.mjs +21 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +3 -0
- package/dist/lib/constants.d.mts +35 -0
- package/dist/lib/constants.mjs +87 -0
- package/dist/lib/extensions.d.mts +56 -0
- package/dist/lib/extensions.mjs +35 -0
- package/dist/lib/helpers.d.mts +9 -0
- package/dist/lib/helpers.mjs +58 -0
- package/dist/lib/markdown.mjs +9 -0
- package/dist/lib/types.d.mts +5 -0
- package/dist/lib/types.mjs +1 -0
- package/dist/lib/variants.mjs +7 -0
- package/package.json +83 -0
- package/styles.css +1 -0
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./lib/types.mjs";
|
|
2
|
+
import { FileHandler } from "./extensions/file-handler/extension.mjs";
|
|
3
|
+
import { Image } from "./extensions/image/extension.mjs";
|
|
4
|
+
import { createExtensionConfig, editorExtensionsConfigToExtensions, flattenEditorExtensionsConfig } from "./lib/extensions.mjs";
|
|
5
|
+
import { Editor, EditorContext, editorVariants } from "./components/editor.client.mjs";
|
|
6
|
+
import { EditorContent } from "./components/content.client.mjs";
|
|
7
|
+
import { EditorMenuBubble, EditorMenuFixed } from "./components/menu.client.mjs";
|
|
8
|
+
export { Editor, EditorContent, EditorContext, EditorMenuBubble, EditorMenuFixed, FileHandler, Image, createExtensionConfig, editorExtensionsConfigToExtensions, editorVariants, flattenEditorExtensionsConfig };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { EditorContext } from "./editor.client.mjs";
|
|
3
|
+
import { EditorContent } from "@tiptap/react";
|
|
4
|
+
import { TooltipProvider } from "@tulip-systems/ui/tooltip";
|
|
5
|
+
import { useContext } from "react";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
//#region src/components/content.client.tsx
|
|
8
|
+
function EditorContent$1() {
|
|
9
|
+
const { editor } = useContext(EditorContext);
|
|
10
|
+
return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx(EditorContent, { editor }) });
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { EditorContent$1 as EditorContent };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { EditorExtensionsConfig, EditorExtensionsConfigResult } from "../lib/extensions.mjs";
|
|
2
|
+
import { EditorJSONContent } from "../lib/types.mjs";
|
|
3
|
+
import { Content, Editor, EditorOptions } from "@tiptap/react";
|
|
4
|
+
import { PropsWithChildren } from "react";
|
|
5
|
+
import { VariantProps } from "class-variance-authority";
|
|
6
|
+
//#region src/components/editor.client.d.ts
|
|
7
|
+
type EditorContextValue = {
|
|
8
|
+
editor: Editor;
|
|
9
|
+
extensions: EditorExtensionsConfigResult;
|
|
10
|
+
};
|
|
11
|
+
declare const EditorContext: import("react").Context<EditorContextValue>;
|
|
12
|
+
declare const editorVariants: (props?: ({
|
|
13
|
+
variant?: "default" | "table" | "preview" | null | undefined;
|
|
14
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
15
|
+
type EditorProps = VariantProps<typeof editorVariants> & Pick<EditorOptions["editorProps"], "attributes"> & PropsWithChildren<{
|
|
16
|
+
value?: Content;
|
|
17
|
+
extensions?: Partial<EditorExtensionsConfig>;
|
|
18
|
+
onUpdate?: (value: EditorJSONContent) => void;
|
|
19
|
+
onBlur?: (value: EditorJSONContent) => void;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
className?: string;
|
|
22
|
+
}>;
|
|
23
|
+
declare function Editor$1({ value, extensions, onUpdate, onBlur, disabled, variant, className, children, attributes }: EditorProps): import("react").JSX.Element | null;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { Editor$1 as Editor, EditorContext, EditorProps, editorVariants };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { editorExtensionsConfigToExtensions, flattenEditorExtensionsConfig } from "../lib/extensions.mjs";
|
|
3
|
+
import { editorImageVariants, editorProseVariants } from "../lib/variants.mjs";
|
|
4
|
+
import { useEditor } from "@tiptap/react";
|
|
5
|
+
import { createContext } from "react";
|
|
6
|
+
import { cn } from "@tulip-systems/ui/cn";
|
|
7
|
+
import { cva } from "class-variance-authority";
|
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
|
9
|
+
//#region src/components/editor.client.tsx
|
|
10
|
+
const EditorContext = createContext({});
|
|
11
|
+
const editorVariants = cva("", {
|
|
12
|
+
variants: { variant: {
|
|
13
|
+
default: "border-input rounded-lg border [&_.editor-content]:p-3 [&_.editor-content[contenteditable='false']]:pointer-events-none [&_.editor-content[contenteditable='false']]:cursor-not-allowed [&_.editor-content[contenteditable='false']]:opacity-50",
|
|
14
|
+
table: "[&_.editor-content[contenteditable='false']]:pointer-events-none [&_.editor-content[contenteditable='false']]:cursor-not-allowed [&_.editor-content[contenteditable='false']]:opacity-50",
|
|
15
|
+
preview: "bg-transparent [&_.editor-content]:p-0 [&_.editor-content]:min-h-0"
|
|
16
|
+
} },
|
|
17
|
+
defaultVariants: { variant: "default" }
|
|
18
|
+
});
|
|
19
|
+
function Editor$1({ value, extensions, onUpdate, onBlur, disabled, variant, className, children, attributes }) {
|
|
20
|
+
const editor = useEditor({
|
|
21
|
+
extensions: editorExtensionsConfigToExtensions(extensions),
|
|
22
|
+
content: value,
|
|
23
|
+
onUpdate: ({ editor }) => onUpdate?.(editor.getJSON()),
|
|
24
|
+
onBlur: ({ editor }) => onBlur?.(editor.getJSON()),
|
|
25
|
+
editable: !disabled,
|
|
26
|
+
injectCSS: false,
|
|
27
|
+
immediatelyRender: false,
|
|
28
|
+
editorProps: {
|
|
29
|
+
transformPastedHTML: (html) => html.replace(/<img.*?>/g, ""),
|
|
30
|
+
attributes: {
|
|
31
|
+
...attributes,
|
|
32
|
+
class: cn("editor-content text-sm max-w-full focus:outline-none", editorProseVariants(), editorImageVariants(), className)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
if (!editor) return null;
|
|
37
|
+
return /* @__PURE__ */ jsx(EditorContext.Provider, {
|
|
38
|
+
value: {
|
|
39
|
+
editor,
|
|
40
|
+
extensions: flattenEditorExtensionsConfig(extensions)
|
|
41
|
+
},
|
|
42
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
43
|
+
className: editorVariants({ variant }),
|
|
44
|
+
children
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { Editor$1 as Editor, EditorContext, editorVariants };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { EditorContext } from "./editor.client.mjs";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { Button } from "@tulip-systems/ui/button";
|
|
5
|
+
import { ChevronDown, Heading1Icon, Heading2Icon, Heading3Icon, Heading4Icon, Heading5Icon, ListIcon, ListOrderedIcon, TextIcon } from "lucide-react";
|
|
6
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@tulip-systems/ui/dropdown-menu";
|
|
7
|
+
//#region src/components/menu-nodes.client.tsx
|
|
8
|
+
const blocks = [
|
|
9
|
+
{
|
|
10
|
+
label: "Paragraaf",
|
|
11
|
+
icon: TextIcon,
|
|
12
|
+
selected: (editor) => editor.isActive("paragraph"),
|
|
13
|
+
handler: (editor) => editor.commands.setParagraph(),
|
|
14
|
+
hidden: (extensions) => !extensions.paragraph
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
label: "Titel 1",
|
|
18
|
+
icon: Heading1Icon,
|
|
19
|
+
selected: (editor) => editor.isActive("heading", { level: 1 }),
|
|
20
|
+
handler: (editor) => editor.commands.setHeading({ level: 1 }),
|
|
21
|
+
hidden: (extensions) => !extensions.heading
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: "Titel 2",
|
|
25
|
+
icon: Heading2Icon,
|
|
26
|
+
selected: (editor) => editor.isActive("heading", { level: 2 }),
|
|
27
|
+
handler: (editor) => editor.commands.setHeading({ level: 2 }),
|
|
28
|
+
hidden: (extensions) => !extensions.heading
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
label: "Titel 3",
|
|
32
|
+
icon: Heading3Icon,
|
|
33
|
+
selected: (editor) => editor.isActive("heading", { level: 3 }),
|
|
34
|
+
handler: (editor) => editor.commands.setHeading({ level: 3 }),
|
|
35
|
+
hidden: (extensions) => !extensions.heading
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: "Titel 4",
|
|
39
|
+
icon: Heading4Icon,
|
|
40
|
+
selected: (editor) => editor.isActive("heading", { level: 4 }),
|
|
41
|
+
handler: (editor) => editor.commands.setHeading({ level: 4 }),
|
|
42
|
+
hidden: (extensions) => !extensions.heading
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: "Titel 5",
|
|
46
|
+
icon: Heading5Icon,
|
|
47
|
+
selected: (editor) => editor.isActive("heading", { level: 5 }),
|
|
48
|
+
handler: (editor) => editor.commands.setHeading({ level: 5 }),
|
|
49
|
+
hidden: (extensions) => !extensions.heading
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: "Numbered List",
|
|
53
|
+
icon: ListOrderedIcon,
|
|
54
|
+
selected: (editor) => editor.isActive("orderedList"),
|
|
55
|
+
handler: (editor) => editor.commands.toggleOrderedList(),
|
|
56
|
+
hidden: (extensions) => !extensions.orderedList
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: "Bulleted List",
|
|
60
|
+
icon: ListIcon,
|
|
61
|
+
selected: (editor) => editor.isActive("bulletList"),
|
|
62
|
+
handler: (editor) => editor.commands.toggleBulletList(),
|
|
63
|
+
hidden: (extensions) => !extensions.bulletList
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
function EditorMenuNodes() {
|
|
67
|
+
const { editor, extensions } = useContext(EditorContext);
|
|
68
|
+
const selectedBlock = blocks.find((block) => block.selected(editor));
|
|
69
|
+
const Icon = selectedBlock?.icon;
|
|
70
|
+
const filteredBlocks = blocks.filter((block) => !block.hidden(extensions));
|
|
71
|
+
if (filteredBlocks.length === 0) return null;
|
|
72
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
73
|
+
asChild: true,
|
|
74
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
75
|
+
variant: "outline",
|
|
76
|
+
size: "sm",
|
|
77
|
+
className: "flex items-center gap-8 p-2",
|
|
78
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
79
|
+
className: "flex items-center gap-2",
|
|
80
|
+
children: [Icon && /* @__PURE__ */ jsx(Icon, { className: "size-3.5" }), selectedBlock?.label ?? "Select a block"]
|
|
81
|
+
}), /* @__PURE__ */ jsx(ChevronDown, { className: "size-3.5" })]
|
|
82
|
+
})
|
|
83
|
+
}), /* @__PURE__ */ jsx(DropdownMenuContent, { children: filteredBlocks.map(({ label, icon: Icon, handler }) => /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
84
|
+
onClick: () => handler(editor),
|
|
85
|
+
className: "flex items-center gap-2",
|
|
86
|
+
children: [Icon && /* @__PURE__ */ jsx(Icon, { className: "size-3.5" }), label]
|
|
87
|
+
}, label)) })] });
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { EditorMenuNodes };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ComponentProps } from "react";
|
|
2
|
+
import { BubbleMenu } from "@tiptap/react/menus";
|
|
3
|
+
//#region src/components/menu.client.d.ts
|
|
4
|
+
declare function EditorMenuFixed(props: React.ComponentProps<"div">): import("react").JSX.Element;
|
|
5
|
+
declare function EditorMenuBubble(props: ComponentProps<typeof BubbleMenu>): import("react").JSX.Element;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { EditorMenuBubble, EditorMenuFixed };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { EditorContext } from "./editor.client.mjs";
|
|
2
|
+
import { EditorMenuNodes } from "./menu-nodes.client.mjs";
|
|
3
|
+
import { useContext } from "react";
|
|
4
|
+
import { cn } from "@tulip-systems/ui/cn";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { BubbleMenu } from "@tiptap/react/menus";
|
|
7
|
+
import { Button } from "@tulip-systems/ui/button";
|
|
8
|
+
import { ColorPicker } from "@tulip-systems/ui/color-picker";
|
|
9
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@tulip-systems/ui/popover";
|
|
10
|
+
import { BaselineIcon, BoldIcon, EraserIcon, ItalicIcon, PaintBucketIcon, RedoIcon, StrikethroughIcon, UnderlineIcon, UndoIcon } from "lucide-react";
|
|
11
|
+
//#region src/components/menu.client.tsx
|
|
12
|
+
function EditorMenuFixed(props) {
|
|
13
|
+
return /* @__PURE__ */ jsx("div", {
|
|
14
|
+
...props,
|
|
15
|
+
className: cn("z-10 overflow-auto border-input border-b bg-muted/30 p-2", props.className),
|
|
16
|
+
children: /* @__PURE__ */ jsxs(EditorMenuContent, { children: [
|
|
17
|
+
/* @__PURE__ */ jsx(EditorMenuUndoRedo, {}),
|
|
18
|
+
/* @__PURE__ */ jsx(EditorMenuNodes, {}),
|
|
19
|
+
/* @__PURE__ */ jsx(EditorMenuMarks, {}),
|
|
20
|
+
/* @__PURE__ */ jsx(EditorMenuColor, {})
|
|
21
|
+
] })
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function EditorMenuBubble(props) {
|
|
25
|
+
const { editor } = useContext(EditorContext);
|
|
26
|
+
return /* @__PURE__ */ jsx(BubbleMenu, {
|
|
27
|
+
...props,
|
|
28
|
+
editor,
|
|
29
|
+
options: {
|
|
30
|
+
...props.options,
|
|
31
|
+
strategy: "fixed"
|
|
32
|
+
},
|
|
33
|
+
className: cn("z-10 rounded-lg border border-input bg-muted p-2", props.className),
|
|
34
|
+
children: /* @__PURE__ */ jsx(EditorMenuContent, { children: /* @__PURE__ */ jsx(EditorMenuMarks, {}) })
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function EditorMenuContent(props) {
|
|
38
|
+
return /* @__PURE__ */ jsx("div", {
|
|
39
|
+
className: "flex items-center gap-6",
|
|
40
|
+
children: props.children
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function EditorMenuUndoRedo() {
|
|
44
|
+
const { editor, extensions } = useContext(EditorContext);
|
|
45
|
+
if (!extensions.undoRedo) return null;
|
|
46
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
47
|
+
className: "flex items-center gap-1",
|
|
48
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
49
|
+
onClick: () => editor.commands.undo(),
|
|
50
|
+
disabled: !editor?.can().undo(),
|
|
51
|
+
type: "button",
|
|
52
|
+
"aria-label": "Undo",
|
|
53
|
+
size: "icon",
|
|
54
|
+
className: "p-2",
|
|
55
|
+
variant: "outline",
|
|
56
|
+
children: /* @__PURE__ */ jsx(UndoIcon, { className: "size-3.5" })
|
|
57
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
58
|
+
onClick: () => editor.commands.redo(),
|
|
59
|
+
disabled: !editor?.can().redo(),
|
|
60
|
+
type: "button",
|
|
61
|
+
"aria-label": "Redo",
|
|
62
|
+
size: "icon",
|
|
63
|
+
className: "p-2",
|
|
64
|
+
variant: "outline",
|
|
65
|
+
children: /* @__PURE__ */ jsx(RedoIcon, { className: "size-3.5" })
|
|
66
|
+
})]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function EditorMenuMarks() {
|
|
70
|
+
const { editor, extensions } = useContext(EditorContext);
|
|
71
|
+
if (!extensions.bold && !extensions.italic && !extensions.underline && !extensions.strike) return null;
|
|
72
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
73
|
+
className: "flex items-center gap-1",
|
|
74
|
+
children: [
|
|
75
|
+
extensions.bold && /* @__PURE__ */ jsx(Button, {
|
|
76
|
+
type: "button",
|
|
77
|
+
size: "icon",
|
|
78
|
+
variant: editor?.isActive("bold") ? "secondary" : "outline",
|
|
79
|
+
onClick: () => editor.chain().focus().toggleBold().run(),
|
|
80
|
+
className: "border",
|
|
81
|
+
children: /* @__PURE__ */ jsx(BoldIcon, { className: "h-3.5 w-3.5" })
|
|
82
|
+
}),
|
|
83
|
+
extensions.italic && /* @__PURE__ */ jsx(Button, {
|
|
84
|
+
type: "button",
|
|
85
|
+
size: "icon",
|
|
86
|
+
variant: editor?.isActive("italic") ? "secondary" : "outline",
|
|
87
|
+
onClick: () => editor.chain().focus().toggleItalic().run(),
|
|
88
|
+
className: "border",
|
|
89
|
+
children: /* @__PURE__ */ jsx(ItalicIcon, { className: "size-3.5" })
|
|
90
|
+
}),
|
|
91
|
+
extensions.underline && /* @__PURE__ */ jsx(Button, {
|
|
92
|
+
type: "button",
|
|
93
|
+
size: "icon",
|
|
94
|
+
variant: editor?.isActive("underline") ? "secondary" : "outline",
|
|
95
|
+
onClick: () => editor.chain().focus().toggleUnderline().run(),
|
|
96
|
+
className: "border",
|
|
97
|
+
children: /* @__PURE__ */ jsx(UnderlineIcon, { className: "size-3.5" })
|
|
98
|
+
}),
|
|
99
|
+
extensions.strike && /* @__PURE__ */ jsx(Button, {
|
|
100
|
+
type: "button",
|
|
101
|
+
size: "icon",
|
|
102
|
+
variant: editor?.isActive("strike") ? "secondary" : "outline",
|
|
103
|
+
onClick: () => editor.chain().focus().toggleStrike().run(),
|
|
104
|
+
className: "border",
|
|
105
|
+
children: /* @__PURE__ */ jsx(StrikethroughIcon, { className: "size-3.5" })
|
|
106
|
+
})
|
|
107
|
+
]
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function EditorMenuColor() {
|
|
111
|
+
const { editor, extensions } = useContext(EditorContext);
|
|
112
|
+
if (!extensions.color && !extensions.highlight) return null;
|
|
113
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
114
|
+
className: "flex items-center gap-1",
|
|
115
|
+
children: [
|
|
116
|
+
extensions.color && /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
117
|
+
asChild: true,
|
|
118
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
119
|
+
size: "icon",
|
|
120
|
+
variant: "outline",
|
|
121
|
+
children: /* @__PURE__ */ jsx(BaselineIcon, { className: "size-3.5" })
|
|
122
|
+
})
|
|
123
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
124
|
+
className: "w-fit p-0",
|
|
125
|
+
children: /* @__PURE__ */ jsx(ColorPicker, { onValueChange: (color) => editor.chain().focus().setColor(color).run() })
|
|
126
|
+
})] }),
|
|
127
|
+
extensions.highlight && /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
128
|
+
asChild: true,
|
|
129
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
130
|
+
size: "icon",
|
|
131
|
+
variant: "outline",
|
|
132
|
+
disabled: !editor?.can().setHighlight(),
|
|
133
|
+
children: /* @__PURE__ */ jsx(PaintBucketIcon, { className: "size-3.5" })
|
|
134
|
+
})
|
|
135
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
136
|
+
className: "w-fit p-0",
|
|
137
|
+
children: /* @__PURE__ */ jsx(ColorPicker, { onValueChange: (color) => editor.chain().focus().setHighlight({ color }).run() })
|
|
138
|
+
})] }),
|
|
139
|
+
/* @__PURE__ */ jsx(Button, {
|
|
140
|
+
type: "button",
|
|
141
|
+
size: "icon",
|
|
142
|
+
variant: "outline",
|
|
143
|
+
className: "border",
|
|
144
|
+
onClick: () => {
|
|
145
|
+
editor.commands.unsetColor();
|
|
146
|
+
editor.commands.unsetHighlight();
|
|
147
|
+
},
|
|
148
|
+
children: /* @__PURE__ */ jsx(EraserIcon, { className: "h-4 w-4" })
|
|
149
|
+
})
|
|
150
|
+
]
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { EditorMenuBubble, EditorMenuFixed };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FileHandlerOptions } from "@tiptap/extension-file-handler";
|
|
2
|
+
//#region src/extensions/file-handler/extension.d.ts
|
|
3
|
+
interface FileHandlerOptions$1 extends FileHandlerOptions {
|
|
4
|
+
onReferenceRemoved?: (assetId: string) => void;
|
|
5
|
+
onReferenceAdded?: (assetId: string) => void;
|
|
6
|
+
}
|
|
7
|
+
declare const FileHandler$1: import("@tiptap/core").Extension<FileHandlerOptions$1, any>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { FileHandler$1 as FileHandler, FileHandlerOptions$1 as FileHandlerOptions };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { collectImageIds, diffIds } from "./utils.mjs";
|
|
3
|
+
import { FileHandler } from "@tiptap/extension-file-handler";
|
|
4
|
+
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
5
|
+
//#region src/extensions/file-handler/extension.ts
|
|
6
|
+
const FileHandler$1 = FileHandler.extend({
|
|
7
|
+
addOptions() {
|
|
8
|
+
return {
|
|
9
|
+
...this.parent?.(),
|
|
10
|
+
onReferenceRemoved: void 0,
|
|
11
|
+
onReferenceAdded: void 0
|
|
12
|
+
};
|
|
13
|
+
},
|
|
14
|
+
addProseMirrorPlugins() {
|
|
15
|
+
const parent = this.parent?.() ?? [];
|
|
16
|
+
const key = new PluginKey("fileHandlerItemTracker");
|
|
17
|
+
return [...parent, new Plugin({
|
|
18
|
+
key,
|
|
19
|
+
appendTransaction: (trs, oldState, newState) => {
|
|
20
|
+
if (!trs.some((tr) => tr.docChanged)) return null;
|
|
21
|
+
const onReferenceRemoved = this.options.onReferenceRemoved;
|
|
22
|
+
const onReferenceAdded = this.options.onReferenceAdded;
|
|
23
|
+
if (!onReferenceRemoved && !onReferenceAdded) return null;
|
|
24
|
+
const before = collectImageIds(oldState.doc);
|
|
25
|
+
const after = collectImageIds(newState.doc);
|
|
26
|
+
const { removed, added } = diffIds(before, after);
|
|
27
|
+
for (const id of removed) onReferenceRemoved?.(id);
|
|
28
|
+
for (const id of added) onReferenceAdded?.(id);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
})];
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
//#endregion
|
|
35
|
+
export { FileHandler$1 as FileHandler };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/extensions/file-handler/utils.ts
|
|
2
|
+
function collectImageIds(doc) {
|
|
3
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4
|
+
doc.descendants((node) => {
|
|
5
|
+
if (node.type.name !== "image") return;
|
|
6
|
+
const id = node.attrs?.assetId;
|
|
7
|
+
if (!id) return;
|
|
8
|
+
counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
9
|
+
});
|
|
10
|
+
return counts;
|
|
11
|
+
}
|
|
12
|
+
function diffIds(before, after) {
|
|
13
|
+
const removed = [];
|
|
14
|
+
const added = [];
|
|
15
|
+
const keys = /* @__PURE__ */ new Set([...before.keys(), ...after.keys()]);
|
|
16
|
+
for (const key of keys) {
|
|
17
|
+
const b = before.get(key) ?? 0;
|
|
18
|
+
const a = after.get(key) ?? 0;
|
|
19
|
+
if (a < b) removed.push(key);
|
|
20
|
+
if (a > b) added.push(key);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
removed,
|
|
24
|
+
added
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { collectImageIds, diffIds };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ImageOptions } from "@tiptap/extension-image";
|
|
2
|
+
//#region src/extensions/image/extension.d.ts
|
|
3
|
+
type ImageAttributes = Record<string, unknown>;
|
|
4
|
+
interface ImageOptions$1 extends ImageOptions {
|
|
5
|
+
resolveImageSrc?: (attrs: ImageAttributes) => string | null | undefined;
|
|
6
|
+
}
|
|
7
|
+
declare const Image$1: import("@tiptap/core").Node<ImageOptions$1, any>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { Image$1 as Image, ImageOptions$1 as ImageOptions };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Image } from "@tiptap/extension-image";
|
|
3
|
+
//#region src/extensions/image/extension.ts
|
|
4
|
+
const Image$1 = Image.extend({
|
|
5
|
+
atom: true,
|
|
6
|
+
addAttributes() {
|
|
7
|
+
return {
|
|
8
|
+
...this.parent?.(),
|
|
9
|
+
assetId: {
|
|
10
|
+
default: null,
|
|
11
|
+
parseHTML: (el) => el.getAttribute("data-asset-id"),
|
|
12
|
+
renderHTML: (attrs) => {
|
|
13
|
+
if (!attrs.assetId) return {};
|
|
14
|
+
return { "data-asset-id": attrs.assetId };
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
alt: { default: null },
|
|
18
|
+
title: { default: null },
|
|
19
|
+
width: { default: null },
|
|
20
|
+
height: { default: null },
|
|
21
|
+
isPending: {
|
|
22
|
+
default: false,
|
|
23
|
+
renderHTML: (attrs) => attrs.isPending ? { "data-pending": "true" } : {}
|
|
24
|
+
},
|
|
25
|
+
fileName: { default: null }
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
parseHTML() {
|
|
29
|
+
return [{ tag: "img[src]" }, { tag: "img[data-asset-id]" }];
|
|
30
|
+
},
|
|
31
|
+
renderHTML({ HTMLAttributes }) {
|
|
32
|
+
const { src: rawSrc, ...attrs } = HTMLAttributes;
|
|
33
|
+
const src = this.options.resolveImageSrc?.(HTMLAttributes) ?? rawSrc;
|
|
34
|
+
return ["img", {
|
|
35
|
+
...attrs,
|
|
36
|
+
...src && !attrs.isPending ? { src } : {}
|
|
37
|
+
}];
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
//#endregion
|
|
41
|
+
export { Image$1 as Image };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { SkeletonNodeRenderer } from "./renderer.mjs";
|
|
3
|
+
import { Node } from "@tiptap/core";
|
|
4
|
+
import { ReactNodeViewRenderer } from "@tiptap/react";
|
|
5
|
+
//#region src/extensions/skeleton/extension.ts
|
|
6
|
+
const Skeleton = Node.create({
|
|
7
|
+
name: "skeleton",
|
|
8
|
+
atom: true,
|
|
9
|
+
inline: true,
|
|
10
|
+
group: "inline",
|
|
11
|
+
selectable: false,
|
|
12
|
+
draggable: false,
|
|
13
|
+
addAttributes() {
|
|
14
|
+
return {
|
|
15
|
+
imageNodeId: { default: null },
|
|
16
|
+
width: { default: null },
|
|
17
|
+
height: { default: null }
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
addOptions() {
|
|
21
|
+
return { addToHistory: false };
|
|
22
|
+
},
|
|
23
|
+
parseHTML() {
|
|
24
|
+
return [{ tag: "span[data-skeleton]" }];
|
|
25
|
+
},
|
|
26
|
+
renderHTML({ HTMLAttributes }) {
|
|
27
|
+
return ["span", {
|
|
28
|
+
...HTMLAttributes,
|
|
29
|
+
"data-skeleton": "true"
|
|
30
|
+
}];
|
|
31
|
+
},
|
|
32
|
+
addNodeView() {
|
|
33
|
+
return ReactNodeViewRenderer(SkeletonNodeRenderer);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
//#endregion
|
|
37
|
+
export { Skeleton };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { NodeViewWrapper } from "@tiptap/react";
|
|
3
|
+
import { Skeleton } from "@tulip-systems/ui/skeleton";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/extensions/skeleton/renderer.tsx
|
|
6
|
+
function SkeletonNodeRenderer(props) {
|
|
7
|
+
const { width, height } = props.node.attrs;
|
|
8
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, {
|
|
9
|
+
as: "span",
|
|
10
|
+
"data-skeleton": "true",
|
|
11
|
+
children: /* @__PURE__ */ jsx(Skeleton, {
|
|
12
|
+
className: "min-h-125 w-full",
|
|
13
|
+
style: {
|
|
14
|
+
width,
|
|
15
|
+
height
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { SkeletonNodeRenderer };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { EditorJSONContent } from "./lib/types.mjs";
|
|
2
|
+
import { convertEditorContentToHTML, convertEditorContentToMarkdown, convertEditorContentToPlainText, convertHTMLToEditorContent, convertMarkdownToEditorContent } from "./lib/helpers.mjs";
|
|
3
|
+
export { EditorJSONContent, convertEditorContentToHTML, convertEditorContentToMarkdown, convertEditorContentToPlainText, convertHTMLToEditorContent, convertMarkdownToEditorContent };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { convertEditorContentToHTML, convertEditorContentToMarkdown, convertEditorContentToPlainText, convertHTMLToEditorContent, convertMarkdownToEditorContent } from "./lib/helpers.mjs";
|
|
2
|
+
import "./lib/types.mjs";
|
|
3
|
+
export { convertEditorContentToHTML, convertEditorContentToMarkdown, convertEditorContentToPlainText, convertHTMLToEditorContent, convertMarkdownToEditorContent };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FileHandlerOptions } from "../extensions/file-handler/extension.mjs";
|
|
2
|
+
import { ImageOptions } from "../extensions/image/extension.mjs";
|
|
3
|
+
import "../client.mjs";
|
|
4
|
+
//#region src/lib/constants.d.ts
|
|
5
|
+
declare const EDITOR_EXTENSIONS: {
|
|
6
|
+
blockquote: import("@tiptap/core").Node<import("@tiptap/extension-blockquote").BlockquoteOptions, any>;
|
|
7
|
+
document: import("@tiptap/core").Node<any, any>;
|
|
8
|
+
hardBreak: import("@tiptap/core").Node<import("@tiptap/extension-hard-break").HardBreakOptions, any>;
|
|
9
|
+
heading: import("@tiptap/core").Node<import("@tiptap/extension-heading").HeadingOptions, any>;
|
|
10
|
+
horizontalRule: import("@tiptap/core").Node<import("@tiptap/extension-horizontal-rule").HorizontalRuleOptions, any>;
|
|
11
|
+
listItem: import("@tiptap/core").Node<import("@tiptap/extension-list").ListItemOptions, any>;
|
|
12
|
+
orderedList: import("@tiptap/core").Node<import("@tiptap/extension-list").OrderedListOptions, any>;
|
|
13
|
+
bulletList: import("@tiptap/core").Node<import("@tiptap/extension-list").BulletListOptions, any>;
|
|
14
|
+
paragraph: import("@tiptap/core").Node<import("@tiptap/extension-paragraph").ParagraphOptions, any>;
|
|
15
|
+
text: import("@tiptap/core").Node<any, any>;
|
|
16
|
+
image: import("@tiptap/core").Node<ImageOptions, any>;
|
|
17
|
+
fileHandler: import("@tiptap/core").Extension<FileHandlerOptions, any>;
|
|
18
|
+
skeleton: import("@tiptap/core").Node<any, any>;
|
|
19
|
+
bold: import("@tiptap/core").Mark<import("@tiptap/extension-bold").BoldOptions, any>;
|
|
20
|
+
italic: import("@tiptap/core").Mark<import("@tiptap/extension-italic").ItalicOptions, any>;
|
|
21
|
+
link: import("@tiptap/core").Mark<import("@tiptap/extension-link").LinkOptions, any>;
|
|
22
|
+
strike: import("@tiptap/core").Mark<import("@tiptap/extension-strike").StrikeOptions, any>;
|
|
23
|
+
underline: import("@tiptap/core").Mark<import("@tiptap/extension-underline").UnderlineOptions, any>;
|
|
24
|
+
highlight: import("@tiptap/core").Mark<import("@tiptap/extension-highlight").HighlightOptions, any>;
|
|
25
|
+
dropcursor: import("@tiptap/core").Extension<import("@tiptap/extensions").DropcursorOptions, any>;
|
|
26
|
+
gapcursor: import("@tiptap/core").Extension<any, any>;
|
|
27
|
+
undoRedo: import("@tiptap/core").Extension<import("@tiptap/extensions").UndoRedoOptions, any>;
|
|
28
|
+
listKeymap: import("@tiptap/core").Extension<import("@tiptap/extension-list").ListKeymapOptions, any>;
|
|
29
|
+
trailingNode: import("@tiptap/core").Extension<import("@tiptap/extensions").TrailingNodeOptions, any>;
|
|
30
|
+
textStyle: import("@tiptap/core").Mark<import("@tiptap/extension-text-style").TextStyleOptions, any>;
|
|
31
|
+
color: import("@tiptap/core").Extension<import("@tiptap/extension-text-style").ColorOptions, any>;
|
|
32
|
+
};
|
|
33
|
+
type EditorExtensions = typeof EDITOR_EXTENSIONS;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { EDITOR_EXTENSIONS, EditorExtensions };
|