diff --git a/.storybook/main.ts b/.storybook/main.ts index 1371a1cc..51e847f3 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -5,17 +5,30 @@ import path, { dirname, join } from "path"; const require = createRequire(import.meta.url); const config: StorybookConfig = { - stories: [ - "../*.mdx", - "../**/src/**/*.mdx", - "../**/src/**/*.stories.@(js|jsx|mjs|ts|tsx)", - ], - addons: [ - getAbsolutePath("@storybook/addon-docs"), - getAbsolutePath("@storybook/addon-a11y"), - getAbsolutePath("@storybook/addon-themes"), - getAbsolutePath("@storybook/addon-vitest"), - ], + stories: + process.env.NODE_ENV === "production" + ? [ + "../*.mdx", + "../**/src/**/*.mdx", + "../**/src/**/!(*.interactions).stories.@(js|jsx|mjs|ts|tsx)", + ] + : [ + "../*.mdx", + "../**/src/**/*.mdx", + "../**/src/**/*.stories.@(js|jsx|mjs|ts|tsx)", + ], + addons: + process.env.NODE_ENV === "production" + ? [ + getAbsolutePath("@storybook/addon-docs"), + getAbsolutePath("@storybook/addon-themes"), + ] + : [ + getAbsolutePath("@storybook/addon-docs"), + getAbsolutePath("@storybook/addon-a11y"), + getAbsolutePath("@storybook/addon-themes"), + getAbsolutePath("@storybook/addon-vitest"), + ], framework: { name: getAbsolutePath("@storybook/react-vite"), options: {}, @@ -43,6 +56,9 @@ const config: StorybookConfig = { prop.parent ? !/node_modules/.test(prop.parent.fileName) : true, }, }, + features: { + interactions: process.env.NODE_ENV !== "production", + }, }; export default config; diff --git a/package.json b/package.json index aa8fc671..cf782c46 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "@storybook/addon-themes": "^9.1.10", "@storybook/addon-vitest": "^9.1.17", "@storybook/react-vite": "^9.1.3", - "@storybook/test": "^8.6.15", "@tailwindcss/vite": "^4.1.11", "@testing-library/jest-dom": "^6.8.0", "@testing-library/react": "^16.3.0", @@ -68,6 +67,8 @@ "npm-run-all": "^4.1.5", "playwright": "^1.57.0", "prettier": "^3.7.4", + "react-dom": "^19.2.4", + "react": "^19.2.4", "rimraf": "^6.1.2", "storybook": "^9.1.17", "typescript": "~5.9.3", diff --git a/packages/frappe-ui-react/package.json b/packages/frappe-ui-react/package.json index b2076e06..69c15aa6 100644 --- a/packages/frappe-ui-react/package.json +++ b/packages/frappe-ui-react/package.json @@ -37,26 +37,39 @@ "publish:remote": "pnpm publish" }, "dependencies": { - "@base-ui/react": "^1.0.0", - "@floating-ui/react": "^0.27.13", - "@headlessui/react": "^2.2.6", + "@base-ui/react": "^1.1.0", + "@floating-ui/react": "^0.27.16", + "@headlessui/react": "^2.2.9", "@popperjs/core": "^2.11.8", + "@tailwindcss/typography": "^0.5.19", + "@tiptap/extension-code-block-lowlight": "^3.17.1", + "@tiptap/extension-highlight": "^3.17.1", + "@tiptap/extension-list": "^3.17.1", + "@tiptap/extension-placeholder": "^3.17.1", + "@tiptap/extension-table": "^3.17.1", + "@tiptap/extension-task-list": "^3.17.1", + "@tiptap/extension-text-align": "^3.17.1", + "@tiptap/extension-text-style": "^3.17.1", + "@tiptap/pm": "^3.17.1", + "@tiptap/react": "^3.17.1", + "@tiptap/starter-kit": "^3.17.1", "clsx": "^2.1.1", - "dayjs": "^1.11.13", + "dayjs": "^1.11.19", "dompurify": "^3.3.1", "echarts": "^6.0.0", "feather-icons": "^4.29.2", + "lowlight": "^3.3.0", "lucide-react": "^0.539.0", - "react": "^19.1.0", + "react": "^19.2.3", "react-dom": "^19.2.3", - "react-grid-layout": "^1.5.2", + "react-grid-layout": "^1.5.3", "react-resizable": "^3.1.3", - "styled-components": "^6.1.19", + "styled-components": "^6.3.8", "tailwindcss": "^4.1.18" }, "devDependencies": { "@types/feather-icons": "^4.29.4", - "@types/node": "^24.1.0", - "@types/react-grid-layout": "^1.3.5" + "@types/node": "^24.10.9", + "@types/react-grid-layout": "^1.3.6" } } diff --git a/packages/frappe-ui-react/src/components/index.ts b/packages/frappe-ui-react/src/components/index.ts index 8e18ddec..a1a7101c 100644 --- a/packages/frappe-ui-react/src/components/index.ts +++ b/packages/frappe-ui-react/src/components/index.ts @@ -33,7 +33,7 @@ export * from "./tabs"; export { default as TaskStatus } from "./taskStatus"; export * from "./textInput"; export * from "./textarea"; -export { default as TextEditor } from "./textEditor"; +export * from "./textEditor"; export * from "./toast"; export * from "./tooltip"; export * from "./tree"; diff --git a/packages/frappe-ui-react/src/components/switch/switch.stories.tsx b/packages/frappe-ui-react/src/components/switch/switch.stories.tsx index c524adf9..ea0773e8 100644 --- a/packages/frappe-ui-react/src/components/switch/switch.stories.tsx +++ b/packages/frappe-ui-react/src/components/switch/switch.stories.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import type { Meta, StoryObj } from "@storybook/react-vite"; import Switch from "./switch"; -import { screen, userEvent, expect } from "@storybook/test"; const sizes = ["sm", "md"] as const; @@ -164,9 +163,4 @@ export const Classes: Story = { ); }, - play: async () => { - const switchElement = screen.getByRole("switch"); - await userEvent.click(switchElement); - expect(switchElement).toHaveAttribute("aria-checked", "true"); - }, }; diff --git a/packages/frappe-ui-react/src/components/textEditor/extension/codeBlock.css b/packages/frappe-ui-react/src/components/textEditor/extension/codeBlock.css new file mode 100644 index 00000000..e3b18d0b --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/extension/codeBlock.css @@ -0,0 +1,109 @@ +/* CodeBlock styles */ +.code-block { + position: relative; +} + +.code-block-container { + position: relative; +} + +.language-selector { + position: absolute; + top: 0.25rem; + right: 0.25rem; + padding-top: 0; + padding-bottom: 0; + opacity: 0; + z-index: 10; + transition-property: opacity; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + transition-delay: 0s; + pointer-events: none; +} + +.code-block-container:hover .language-selector { + opacity: 1; + transition-delay: 0s; + pointer-events: auto; +} + +.language-selector:focus-within { + opacity: 1; + transition-delay: 0s; + pointer-events: auto; +} + +/* When mouse leaves the code block, delay the hiding */ +.code-block-container:not(:hover) .language-selector:not(:focus-within) { + transition-delay: 1.5s; +} + +.ProseMirror pre { + background: #0d0d0d; + color: #fff; + font-family: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', + 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Mono', 'Source Code Pro', 'Fira Mono', + 'Droid Sans Mono', 'Consolas', 'Courier New', monospace; + padding: 0.75rem 1rem; + border-radius: 0.75rem; + caret-color: #fff; +} + +.ProseMirror pre code { + color: inherit; + padding: 0; + background: none; + font-size: 12px; +} + +.ProseMirror pre .hljs-comment, +.ProseMirror pre .hljs-quote { + color: #999; +} + +.ProseMirror pre .hljs-variable, +.ProseMirror pre .hljs-template-variable, +.ProseMirror pre .hljs-attribute, +.ProseMirror pre .hljs-tag, +.ProseMirror pre .hljs-name, +.ProseMirror pre .hljs-regexp, +.ProseMirror pre .hljs-link, +.ProseMirror pre .hljs-selector-id, +.ProseMirror pre .hljs-selector-class { + color: #f2777a; +} + +.ProseMirror pre .hljs-number, +.ProseMirror pre .hljs-meta, +.ProseMirror pre .hljs-built_in, +.ProseMirror pre .hljs-builtin-name, +.ProseMirror pre .hljs-literal, +.ProseMirror pre .hljs-type, +.ProseMirror pre .hljs-params { + color: #f99157; +} + +.ProseMirror pre .hljs-string, +.ProseMirror pre .hljs-symbol, +.ProseMirror pre .hljs-bullet { + color: #99cc99; +} + +.ProseMirror pre .hljs-title, +.ProseMirror pre .hljs-section { + color: #ffcc66; +} + +.ProseMirror pre .hljs-keyword, +.ProseMirror pre .hljs-selector-tag { + color: #6196cc; +} + +.ProseMirror pre .hljs-emphasis { + font-style: italic; +} + +.ProseMirror pre .hljs-strong { + font-weight: 700; +} diff --git a/packages/frappe-ui-react/src/components/textEditor/extension/codeBlock.ts b/packages/frappe-ui-react/src/components/textEditor/extension/codeBlock.ts new file mode 100644 index 00000000..ec4a34a8 --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/extension/codeBlock.ts @@ -0,0 +1,67 @@ +/** + * External dependencies. + */ +import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; +import { createLowlight, all } from "lowlight"; + +/** + * Internal dependencies. + */ +import { getCodeBlockCtx, INDENT, lineStartsBetween } from "./utils"; +import { ReactNodeViewRenderer } from "@tiptap/react"; +import CodeBlockComponent from "./codeBlockNode"; + +const lowlight = createLowlight(all); + +export const ExtendedCodeBlock = CodeBlockLowlight.extend({ + addKeyboardShortcuts() { + return { + Tab: () => { + const { state, dispatch } = this.editor.view; + const ctx = getCodeBlockCtx(state); + if (!ctx) return false; + + const { start, text, fromOffset, toOffset } = ctx; + const multiline = text.slice(fromOffset, toOffset).includes("\n"); + const tr = state.tr; + if (multiline) { + for (const off of lineStartsBetween(text, fromOffset, toOffset)) { + const pos = tr.mapping.map(start + off); + tr.insertText(INDENT, pos); + } + } else { + tr.insertText(INDENT, state.selection.from); + } + dispatch(tr); + return true; + }, + "Shift-Tab": () => { + const { state, dispatch } = this.editor.view; + const ctx = getCodeBlockCtx(state); + if (!ctx) return false; + + const { start, text, fromOffset, toOffset } = ctx; + const tr = state.tr; + for (const off of lineStartsBetween(text, fromOffset, toOffset)) { + let len = 0; + if (text.substr(off, 4) === INDENT) len = 4; + else if (text[off] === "\t") len = 1; + + if (len > 0) { + const s = tr.mapping.map(start + off); + const e = tr.mapping.map(start + off + len); + tr.delete(s, e); + } + } + + dispatch(tr); + return true; + }, + }; + }, + addNodeView() { + return ReactNodeViewRenderer(CodeBlockComponent); + }, +}).configure({ + lowlight, +}); diff --git a/packages/frappe-ui-react/src/components/textEditor/extension/codeBlockNode.tsx b/packages/frappe-ui-react/src/components/textEditor/extension/codeBlockNode.tsx new file mode 100644 index 00000000..87863d89 --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/extension/codeBlockNode.tsx @@ -0,0 +1,57 @@ +import { NodeViewContent, NodeViewWrapper } from "@tiptap/react"; +import { type NodeViewProps } from "@tiptap/react"; +import { useMemo } from "react"; +import "./codeBlock.css"; + +interface Language { + label: string; + value: string; +} + +function CodeBlockComponent(props: NodeViewProps) { + const selectedLanguage = props.node.attrs.language; + + const languages = useMemo(() => { + const supportedLanguages = props.extension.options.lowlight.listLanguages(); + return supportedLanguages + .map((language: string) => ({ + label: language, + value: language, + })) + .concat([{ label: "html", value: "xml" }]) + .sort((a: Language, b: Language) => a.label.localeCompare(b.label)); + }, [props.extension.options.lowlight]); + + function handleLanguageChange(e: React.ChangeEvent) { + const language = e.target.value === "null" ? null : e.target.value; + props.updateAttributes({ language }); + } + + return ( + +
+ +
+          
+            
+          
+        
+
+
+ ); +} + +export default CodeBlockComponent; diff --git a/packages/frappe-ui-react/src/components/textEditor/extension/utils.ts b/packages/frappe-ui-react/src/components/textEditor/extension/utils.ts new file mode 100644 index 00000000..356a622f --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/extension/utils.ts @@ -0,0 +1,37 @@ +import type { Editor } from "@tiptap/react"; + +export function lineStartsBetween( + text: string, + fromOffset: number, + toOffset: number +) { + const startOff = text.lastIndexOf("\n", Math.max(0, fromOffset - 1)) + 1; + let endOff = toOffset; + if (endOff > 0 && text[endOff - 1] === "\n") endOff--; + + const starts: number[] = [startOff]; + let i = startOff; + while (true) { + const nl = text.indexOf("\n", i); + if (nl === -1 || nl >= endOff) break; + starts.push(nl + 1); + i = nl + 1; + } + return starts; +} + +export function getCodeBlockCtx(state: Editor["state"]) { + const { $from, from, to } = state.selection; + let d = $from.depth; + while (d > 0 && $from.node(d).type.name !== "codeBlock") d--; + if (d === 0) return null; + + const node = $from.node(d); + const start = $from.start(d); + const text: string = node.textContent; + const fromOffset = from - start; + const toOffset = to - start; + return { start, text, fromOffset, toOffset }; +} + +export const INDENT = " ".repeat(4); diff --git a/packages/frappe-ui-react/src/components/textEditor/index.ts b/packages/frappe-ui-react/src/components/textEditor/index.ts new file mode 100644 index 00000000..aff81e01 --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/index.ts @@ -0,0 +1,2 @@ +export { default as TextEditor } from "./textEditor"; +export * from "./types"; diff --git a/packages/frappe-ui-react/src/components/textEditor/index.tsx b/packages/frappe-ui-react/src/components/textEditor/index.tsx deleted file mode 100644 index 1bc4c71b..00000000 --- a/packages/frappe-ui-react/src/components/textEditor/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -export interface TextEditorProps { - allowImageUpload?: boolean; - allowVideoUpload?: boolean; - className?: string; - hideToolbar?: boolean; - onChange: (value: string) => void; - value?: string; - placeholder?: string; -} - -/* eslint-disable @typescript-eslint/no-unused-vars */ -const TextEditor = (_props: TextEditorProps) => { - return
; -}; - -export default TextEditor; diff --git a/packages/frappe-ui-react/src/components/textEditor/menu/commands/fontColor.tsx b/packages/frappe-ui-react/src/components/textEditor/menu/commands/fontColor.tsx new file mode 100644 index 00000000..c7b885f3 --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/menu/commands/fontColor.tsx @@ -0,0 +1,136 @@ +/** + * External dependencies. + */ +import clsx from "clsx"; +import { useCurrentEditor } from "@tiptap/react"; + +/** + * Internal dependencies. + */ +import { Popover } from "../../../popover"; + +interface FontColorProps { + children: (props: { + isActive?: boolean; + onClick?: () => void; + }) => React.ReactNode; +} + +type Color = { + name: string; + value: string; +}; + +const foregroundColors: Color[] = [ + { name: "Default", value: "#171717" }, + { name: "Red", value: "#cc2929" }, + { name: "Orange", value: "#d45a08" }, + { name: "Yellow", value: "#d1930d" }, + { name: "Green", value: "#278f5e" }, + { name: "Teal", value: "#0b9e92" }, + { name: "Cyan", value: "#32a4c7" }, + { name: "Blue", value: "#007be0" }, + { name: "Purple", value: "#8642c2" }, + { name: "Pink", value: "#cf3a96" }, + { name: "Gray", value: "#7c7c7c" }, +]; + +const backgroundColors: Color[] = [ + { name: "Default", value: "#ffffff" }, + { name: "Red", value: "#ffe7e7" }, + { name: "Orange", value: "#ffefe4" }, + { name: "Yellow", value: "#fff7d3" }, + { name: "Green", value: "#e4faeb" }, + { name: "Teal", value: "#e6f7f4" }, + { name: "Cyan", value: "#ddf7ff" }, + { name: "Blue", value: "#e6f4ff" }, + { name: "Purple", value: "#f6e9ff" }, + { name: "Pink", value: "#fde8f5" }, + { name: "Gray", value: "#f3f3f3" }, +]; + +const FontColor = ({ children }: FontColorProps) => { + const { editor } = useCurrentEditor(); + + const setForegroundColor = (color: Color) => { + if (!editor) { + return; + } + editor.chain().focus().setColor(color.value).run(); + }; + + const setBackgroundColor = (color: Color) => { + if (!editor) { + return; + } + editor.chain().focus().setHighlight({ color: color.value }).run(); + }; + + return ( + + children({ + isActive: isOpen, + onClick: () => togglePopover(), + }) + } + body={({ close }) => ( +
+
+
Text Color
+
+ {foregroundColors.map((color) => ( + + ))} +
+
Background Color
+
+ {backgroundColors.map((color) => ( +
+ +
+ ))} +
+
+
+ )} + /> + ); +}; + +export default FontColor; diff --git a/packages/frappe-ui-react/src/components/textEditor/menu/commands/index.ts b/packages/frappe-ui-react/src/components/textEditor/menu/commands/index.ts new file mode 100644 index 00000000..49bd6e70 --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/menu/commands/index.ts @@ -0,0 +1,269 @@ +/** + * External dependencies. + */ +import { + AlignCenterIcon, + AlignLeftIcon, + AlignRightIcon, + BoldIcon, + CodeIcon, + Heading1Icon, + Heading2Icon, + Heading3Icon, + Heading4Icon, + Heading5Icon, + Heading6Icon, + ItalicIcon, + ListCheckIcon, + ListIcon, + ListOrderedIcon, + PaintBucketIcon, + QuoteIcon, + Redo2Icon, + SeparatorHorizontal, + StrikethroughIcon, + TableIcon, + TypeIcon, + Undo2Icon, +} from "lucide-react"; + +/** + * Internal dependencies. + */ +import type { TYPE_COMMANDS_KEYS, EditorCommand } from "./types"; +import FontColor from "./fontColor"; + +export const COMMANDS: Record = { + paragraph: { + label: "Paragraph", + icon: TypeIcon, + action: (editor) => editor.chain().focus().setParagraph().run(), + isActive: (editor) => editor.isActive("paragraph"), + }, + heading_1: { + label: "Heading 1", + text: "H1", + icon: Heading1Icon, + action: (editor) => + editor.chain().focus().toggleHeading({ level: 1 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 1 }), + }, + heading_2: { + label: "Heading 2", + text: "H2", + icon: Heading2Icon, + action: (editor) => + editor.chain().focus().toggleHeading({ level: 2 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 2 }), + }, + heading_3: { + label: "Heading 3", + text: "H3", + icon: Heading3Icon, + action: (editor) => + editor.chain().focus().toggleHeading({ level: 3 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 3 }), + }, + heading_4: { + label: "Heading 4", + text: "H4", + icon: Heading4Icon, + action: (editor) => + editor.chain().focus().toggleHeading({ level: 4 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 4 }), + }, + heading_5: { + label: "Heading 5", + text: "H5", + icon: Heading5Icon, + action: (editor) => + editor.chain().focus().toggleHeading({ level: 5 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 5 }), + }, + heading_6: { + label: "Heading 6", + text: "H6", + icon: Heading6Icon, + action: (editor) => + editor.chain().focus().toggleHeading({ level: 6 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 6 }), + }, + bold: { + label: "Bold", + icon: BoldIcon, + action: (editor) => editor.chain().focus().toggleBold().run(), + isActive: (editor) => editor.isActive("bold"), + }, + italic: { + label: "Italic", + icon: ItalicIcon, + action: (editor) => editor.chain().focus().toggleItalic().run(), + isActive: (editor) => editor.isActive("italic"), + }, + bullet_list: { + label: "Bullet List", + icon: ListIcon, + action: (editor) => editor.chain().focus().toggleBulletList().run(), + isActive: (editor) => editor.isActive("bulletList"), + }, + numbered_list: { + label: "Numbered List", + icon: ListOrderedIcon, + action: (editor) => editor.chain().focus().toggleOrderedList().run(), + isActive: (editor) => editor.isActive("orderedList"), + }, + task_list: { + label: "Task List", + icon: ListCheckIcon, + action: (editor) => editor.chain().focus().toggleTaskList().run(), + isActive: (editor) => editor.isActive("taskList"), + }, + align_center: { + label: "Align Center", + icon: AlignCenterIcon, + action: (editor) => editor.chain().focus().setTextAlign("center").run(), + isActive: (editor) => editor.isActive({ textAlign: "center" }), + }, + align_left: { + label: "Align Left", + icon: AlignLeftIcon, + action: (editor) => editor.chain().focus().setTextAlign("left").run(), + isActive: (editor) => editor.isActive({ textAlign: "left" }), + }, + align_right: { + label: "Align Right", + icon: AlignRightIcon, + action: (editor) => editor.chain().focus().setTextAlign("right").run(), + isActive: (editor) => editor.isActive({ textAlign: "right" }), + }, + font_color: { + label: "Font Color", + icon: PaintBucketIcon, + isActive: (editor) => + editor.isActive("textStyle") || editor.isActive("highlight"), + component: FontColor, + }, + strike: { + label: "Strike", + icon: StrikethroughIcon, + action: (editor) => editor.chain().focus().toggleStrike().run(), + isActive: (editor) => editor.isActive("strike"), + }, + codeblock: { + label: "Code Block", + icon: CodeIcon, + action: (editor) => editor.chain().focus().toggleCodeBlock().run(), + isActive: (editor) => editor.isActive("codeBlock"), + }, + horizontal_rule: { + label: "Horizontal Rule", + icon: SeparatorHorizontal, + action: (editor) => editor.chain().focus().setHorizontalRule().run(), + isActive: (editor) => editor.isActive("horizontalRule"), + }, + insert_table: { + label: "Insert Table", + icon: TableIcon, + action: (editor) => + editor + .chain() + .focus() + .insertTable({ rows: 3, cols: 3, withHeaderRow: true }) + .run(), + isActive: () => false, + }, + add_column_before: { + label: "Add Column Before", + action: (editor) => editor.chain().focus().addColumnBefore().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().addColumnBefore(), + }, + add_column_after: { + label: "Add Column After", + action: (editor) => editor.chain().focus().addColumnAfter().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().addColumnAfter(), + }, + delete_column: { + label: "Delete Column", + action: (editor) => editor.chain().focus().deleteColumn().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().deleteColumn(), + }, + add_row_before: { + label: "Add Row Before", + action: (editor) => editor.chain().focus().addRowBefore().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().addRowBefore(), + }, + add_row_after: { + label: "Add Row After", + action: (editor) => editor.chain().focus().addRowAfter().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().addRowAfter(), + }, + delete_row: { + label: "Delete Row", + action: (editor) => editor.chain().focus().deleteRow().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().deleteRow(), + }, + delete_table: { + label: "Delete Table", + action: (editor) => editor.chain().focus().deleteTable().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().deleteTable(), + }, + merge_cells: { + label: "Merge Cells", + action: (editor) => editor.chain().focus().mergeCells().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().mergeCells(), + }, + split_cell: { + label: "Split Cell", + action: (editor) => editor.chain().focus().splitCell().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().splitCell(), + }, + toggle_header_column: { + label: "Toggle Header Column", + action: (editor) => editor.chain().focus().toggleHeaderColumn().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().toggleHeaderColumn(), + }, + toggle_header_row: { + label: "Toggle Header Row", + action: (editor) => editor.chain().focus().toggleHeaderRow().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().toggleHeaderRow(), + }, + toggle_header_cell: { + label: "Toggle Header Cell", + action: (editor) => editor.chain().focus().toggleHeaderCell().run(), + isActive: () => false, + isDisabled: (editor) => !editor.can().toggleHeaderCell(), + }, + blockquote: { + label: "Blockquote", + icon: QuoteIcon, + action: (editor) => editor.chain().focus().toggleBlockquote().run(), + isActive: (editor) => editor.isActive("blockquote"), + }, + undo: { + label: "Undo", + icon: Undo2Icon, + action: (editor) => editor.chain().focus().undo().run(), + isDisabled: (editor) => !editor.can().undo(), + isActive: () => false, + }, + redo: { + label: "Redo", + icon: Redo2Icon, + action: (editor) => editor.chain().focus().redo().run(), + isDisabled: (editor) => !editor.can().redo(), + isActive: () => false, + }, +}; + +export default COMMANDS; diff --git a/packages/frappe-ui-react/src/components/textEditor/menu/commands/types.ts b/packages/frappe-ui-react/src/components/textEditor/menu/commands/types.ts new file mode 100644 index 00000000..6d338815 --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/menu/commands/types.ts @@ -0,0 +1,60 @@ +/** + * External dependencies. + */ +import type { Editor } from "@tiptap/react"; +import type { FC } from "react"; + +export const COMMANDS_KEYS = [ + "paragraph", + "heading_1", + "heading_2", + "heading_3", + "heading_4", + "heading_5", + "heading_6", + "bold", + "italic", + "bullet_list", + "numbered_list", + "task_list", + "align_left", + "align_center", + "align_right", + "font_color", + "strike", + "codeblock", + "horizontal_rule", + "insert_table", + "add_column_before", + "add_column_after", + "delete_column", + "add_row_before", + "add_row_after", + "delete_row", + "merge_cells", + "split_cell", + "toggle_header_column", + "toggle_header_row", + "toggle_header_cell", + "delete_table", + "blockquote", + "undo", + "redo", +] as const; + +export type TYPE_COMMANDS_KEYS = (typeof COMMANDS_KEYS)[number]; + +export interface EditorCommand { + label: string; + text?: string; + icon?: FC<{ className?: string }>; + isActive: (editor: Editor) => boolean; + isDisabled?: (editor: Editor) => boolean; + action?: (editor: Editor) => void; + component?: FC<{ + children: (props: { + isActive?: boolean; + onClick?: () => void; + }) => React.ReactNode; + }>; +} diff --git a/packages/frappe-ui-react/src/components/textEditor/menu/fixedMenu.tsx b/packages/frappe-ui-react/src/components/textEditor/menu/fixedMenu.tsx new file mode 100644 index 00000000..f6d6944f --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/menu/fixedMenu.tsx @@ -0,0 +1,14 @@ +/** + * External dependencies. + */ + +/** + * Internal dependencies. + */ +import Menu from "./menu"; + +const FixedMenu = () => { + return ; +}; + +export default FixedMenu; diff --git a/packages/frappe-ui-react/src/components/textEditor/menu/menu.tsx b/packages/frappe-ui-react/src/components/textEditor/menu/menu.tsx new file mode 100644 index 00000000..25b32969 --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/menu/menu.tsx @@ -0,0 +1,211 @@ +/** + * External dependencies. + */ +import { useCurrentEditor, useEditorState } from "@tiptap/react"; +import clsx from "clsx"; + +/** + * Internal dependencies. + */ +import COMMANDS from "./commands"; +import { Popover } from "../../popover"; +import type { TYPE_COMMANDS_KEYS, EditorCommand } from "./commands/types"; +import { Button } from "../../button"; + +export interface MenuProps { + className?: string; +} + +const DEFAULT_COMMANDS: Array< + TYPE_COMMANDS_KEYS | "separator" | Array +> = [ + "paragraph", + [ + "heading_1", + "heading_2", + "heading_3", + "heading_4", + "heading_5", + "heading_6", + ], + "separator", + "bold", + "italic", + "strike", + "font_color", + "separator", + "bullet_list", + "numbered_list", + "task_list", + "separator", + "align_left", + "align_center", + "align_right", + "separator", + "codeblock", + "horizontal_rule", + [ + "insert_table", + "add_column_before", + "add_column_after", + "delete_column", + "add_row_before", + "add_row_after", + "delete_row", + "toggle_header_column", + "toggle_header_row", + "toggle_header_cell", + "delete_table", + ], + "blockquote", + "undo", + "redo", +]; + +const Menu = ({ className }: MenuProps) => { + const { editor } = useCurrentEditor(); + const editorState = useEditorState({ + editor, + selector: ({ editor }) => ({ + selection: editor?.state?.selection, + focused: editor?.isFocused ?? false, + }), + }); + + const isButtonActive = (button: EditorCommand): boolean => { + if (editor && editorState && editorState.focused && button.isActive) { + return button.isActive(editor); + } + return false; + }; + + if (!editor) { + return null; + } + + return ( +
+ {DEFAULT_COMMANDS.map((command_key, index) => { + if (Array.isArray(command_key)) { + const activeCommand = editorState?.focused + ? command_key.find((b) => COMMANDS[b].isActive(editor)) || + command_key[0] + : command_key[0]; + + const ActiveIcon = COMMANDS[activeCommand].icon; + const title = command_key[0] === "heading_1" ? "heading" : "table"; + + return ( +
+ ( + + )} + body={({ close }) => ( +
    + {command_key.map((command_key) => { + const command = COMMANDS[command_key]; + const isDisabled = command.isDisabled?.(editor); + if (isDisabled) { + return null; + } + return ( +
  • + +
  • + ); + })} +
+ )} + /> +
+ ); + } + if (command_key == "separator") { + return ( +
+ ); + } + + const command: EditorCommand = COMMANDS[command_key]; + const label = command.label; + const Icon = command.icon; + + if (command.component) { + return ( + + {({ isActive, onClick }) => ( + + )} + + ); + } + + return ( + + ); + })} +
+ ); +}; + +export default Menu; diff --git a/packages/frappe-ui-react/src/components/textEditor/textEditor.css b/packages/frappe-ui-react/src/components/textEditor/textEditor.css new file mode 100644 index 00000000..662c056f --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/textEditor.css @@ -0,0 +1,221 @@ +/* Typography prose color variables override */ +.prose { + --tw-prose-body: var(--color-ink-gray-8); + --tw-prose-headings: var(--color-ink-gray-9); + --tw-prose-lead: var(--color-ink-gray-5); + --tw-prose-links: var(--color-ink-gray-9); + --tw-prose-bold: var(--color-ink-gray-9); + --tw-prose-counters: var(--color-ink-gray-4); + --tw-prose-bullets: var(--color-ink-gray-2); + --tw-prose-hr: var(--color-ink-gray-1); + --tw-prose-quotes: var(--color-ink-gray-8); + --tw-prose-quote-borders: var(--color-ink-gray-1); + --tw-prose-captions: var(--color-ink-gray-4); + --tw-prose-kbd: var(--color-ink-gray-9); + --tw-prose-code: var(--color-ink-gray-9); + --tw-prose-pre-code: var(--color-ink-gray-1); + --tw-prose-pre-bg: var(--color-ink-gray-8); + --tw-prose-th-borders: var(--color-ink-gray-2); + --tw-prose-td-borders: var(--color-ink-gray-1); +} + +/* Heading font weights */ +.prose h1, +.prose h2, +.prose h3, +.prose h4, +.prose h5, +.prose h1 strong, +.prose h2 strong, +.prose h3 strong, +.prose h4 strong, +.prose h5 strong { + font-weight: 600; +} + +/* Image alignment */ +.prose img[data-align='right'] { + margin-left: auto; + margin-right: 0; +} + +.prose img[data-align='center'] { + margin-left: auto; + margin-right: auto; +} + +/* Small prose variant */ +.prose-sm { + font-size: 14px; + font-weight: 420; + line-height: 1.5; + letter-spacing: 0.02em; +} + +.prose-sm h1 { + font-size: calc(20 / 14 * 1em); +} + +.prose-sm h2 { + font-size: calc(18 / 14 * 1em); +} + +.prose-sm h3 { + font-size: calc(16 / 14 * 1em); +} + +.prose-sm h4 { + font-size: calc(14 / 14 * 1em); +} + +.prose-sm h5 { + font-size: calc(13 / 14 * 1em); +} + +.prose-sm p { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.prose-sm ul > li, +.prose-sm ol > li { + margin: 0.5rem 0; +} + +.prose-sm ul > li > p, +.prose-sm ul > li > p:first-child:last-child, +.prose-sm ol > li > p, +.prose-sm ol > li > p:first-child:last-child { + margin: 0.5rem 0; +} + +.prose-sm ul > li > p:first-child, +.prose-sm ol > li > p:first-child { + margin-top: 0.5rem; +} + +.prose-sm ul > li > p:last-child, +.prose-sm ol > li > p:last-child { + margin-bottom: 0.5rem; +} + +.ProseMirror { + outline: none; + caret-color: var(--color-ink-gray-9); + word-break: break-word; +} + +/* Firefox */ +.ProseMirror-focused:focus-visible { + outline: none; +} + +/* Placeholder */ +.ProseMirror:not(.ProseMirror-focused) p.is-editor-empty::before { + content: attr(data-placeholder); + float: left; + color: var(--color-ink-gray-4); + pointer-events: none; + height: 0; +} + +.ProseMirror-selectednode video, +img.ProseMirror-selectednode { + outline: 2px solid var(--color-outline-gray-2); +} + +/* Table styles */ +.prose table p { + margin: 0; +} + +/* Prosemirror specific table styles */ +.ProseMirror table .selectedCell:after { + z-index: 2; + position: absolute; + content: ''; + left: 0; + right: 0; + top: 0; + bottom: 0; + pointer-events: none; + background: var(--color-ink-blue-1); + opacity: 0.3; +} + +.ProseMirror table .column-resize-handle { + position: absolute; + right: -1px; + top: 0; + bottom: -2px; + width: 4px; + background-color: var(--color-ink-blue-2); + pointer-events: none; +} + +.ProseMirror ul[data-type='taskList'] { + list-style: none; + padding: 0; +} + +.ProseMirror ul[data-type='taskList'] li { + align-items: flex-start; + display: flex; + margin: 0; +} + +.ProseMirror ul[data-type='taskList'] li > label { + flex: 0 0 auto; + margin-right: 0.5rem; + margin-top: 0.25rem; + height: 1lh; + display: flex; + align-items: center; + user-select: none; +} + +.ProseMirror ul[data-type='taskList'] li > div { + flex: 1 1 auto; + margin-bottom: 0; +} + +.ProseMirror ul[data-type='taskList'] li > div > p { + margin: 0.25rem 0; +} + +.ProseMirror ul[data-type='taskList'] ul[data-type='taskList'] { + margin: 0; +} + +.ProseMirror ul[data-type='taskList'] input[type='checkbox'] { + cursor: pointer; + width: 14px; + height: 14px; + border-radius: 4px; + color: var(--color-ink-gray-9); +} + +.resize-cursor { + cursor: ew-resize; + cursor: col-resize; +} + +.tag-item, +.tag-suggestion-active { + background-color: var(--color-surface-gray-1, #f8f8f8); + color: inherit; + border: 1px solid transparent; + padding: 0px 2px; + border-radius: 4px; + font-size: 1em; + white-space: nowrap; + cursor: default; +} + +.tag-item.ProseMirror-selectednode { + border-color: var(--color-outline-gray-3, #c7c7c7); +} + +.tag-suggestion-active { + background-color: var(--color-surface-gray-2, #f3f3f3); +} diff --git a/packages/frappe-ui-react/src/components/textEditor/textEditor.interactions.stories.tsx b/packages/frappe-ui-react/src/components/textEditor/textEditor.interactions.stories.tsx new file mode 100644 index 00000000..417155e6 --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/textEditor.interactions.stories.tsx @@ -0,0 +1,257 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { screen, userEvent, expect, within } from "storybook/test"; +import TextEditor from "./textEditor"; + +const meta: Meta = { + title: "Components/TextEditor/Interactions", + component: TextEditor, + parameters: { + docs: { source: { type: "dynamic" } }, + layout: "centered", + }, + argTypes: { + content: { + control: "text", + description: "HTML content of the editor", + }, + placeholder: { + control: "text", + description: "Placeholder text when editor is empty", + }, + editorClass: { + control: "text", + description: "CSS classes to apply to the editor content area", + }, + editable: { + control: "boolean", + description: "Whether the editor is editable", + }, + autofocus: { + control: "boolean", + description: "Whether to autofocus the editor on mount", + }, + extensions: { + control: false, + description: "Additional TipTap extensions", + }, + starterkitOptions: { + control: "object", + description: "Configuration for StarterKit extension", + }, + fixedMenu: { + control: "boolean", + description: "Show fixed menu toolbar", + }, + onChange: { + action: "changed", + description: "Callback when content changes", + }, + onFocus: { + action: "focused", + description: "Callback when editor receives focus", + }, + onBlur: { + action: "blurred", + description: "Callback when editor loses focus", + }, + onTransaction: { + control: false, + description: "Callback on editor transaction", + }, + }, +}; + +const CONTENT: string = ` +
+

Heading 2

+

+ This is a paragraph with bold and italic text. +

+
    +
  • Item 1
  • +
  • Item 2
  • +
+
import { Button } from '@rtcamp/frappe-ui-react'
+const value = ref(true);
+ +
+`; + +export default meta; +type Story = StoryObj; + +export const EditorHeading: Story = { + args: { + content: CONTENT, + editorClass: "prose-sm min-h-[4rem] border rounded-b-lg border-t-0 p-2", + fixedMenu: true, + }, + render: function BasicRender(args) { + return ( +
+ +
+ ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + // Select the text to apply the heading + const text = canvas.getByText((content) => { + return content.includes("paragraph"); + }); + await userEvent.tripleClick(text); + + // Find the heading dropdown or button + const headingButton = await canvas.findByRole("button", { + name: "heading", + }); + await userEvent.click(headingButton); + + // Select a heading option (e.g., Heading 2). Popups are outside canvas + const headingOption = await screen.findByRole("menuitem", { + name: /heading 2/i, + }); + await userEvent.click(headingOption); + + const newText = canvas.getByText((content) => { + return content.includes("paragraph"); + }); + + expect(newText.tagName).toBe("H2"); + }, +}; + +export const EditorBold: Story = { + args: { + content: CONTENT, + editorClass: "prose-sm min-h-[4rem] border rounded-b-lg border-t-0 p-2", + fixedMenu: true, + }, + render: function BasicRender(args) { + return ( +
+ +
+ ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + // Select the text to apply bold formatting + const text = canvas.getByText((content) => { + return content.includes("paragraph"); + }); + await userEvent.tripleClick(text); + + // Find the bold button + const boldButton = await screen.findByTitle("Bold"); + await userEvent.click(boldButton); + + // Verify the text is bold + const boldText = canvas.getByText((content) => { + return content.includes("paragraph"); + }); + + expect(boldText).toHaveStyle("font-weight: 600"); + }, +}; + +export const EditorItalic: Story = { + args: { + content: CONTENT, + editorClass: "prose-sm min-h-[4rem] border rounded-b-lg border-t-0 p-2", + fixedMenu: true, + }, + render: function BasicRender(args) { + return ( +
+ +
+ ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const text = canvas.getByText((content) => { + return content.includes("paragraph"); + }); + await userEvent.tripleClick(text); + const button = await screen.findByTitle("Italic"); + await userEvent.click(button); + + const newText = canvas.getByText((content) => { + return content.includes("paragraph"); + }); + + expect(newText).toHaveStyle("font-style: italic"); + }, +}; + +export const EditorStrike: Story = { + args: { + content: CONTENT, + editorClass: "prose-sm min-h-[4rem] border rounded-b-lg border-t-0 p-2", + fixedMenu: true, + }, + render: function BasicRender(args) { + return ( +
+ +
+ ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const text = canvas.getByText((content) => { + return content.includes("paragraph"); + }); + await userEvent.tripleClick(text); + const button = await screen.findByTitle("Strike"); + await userEvent.click(button); + + const newText = canvas.getByText((content) => { + return content.includes("paragraph"); + }); + + expect(newText.tagName).toBe("S"); + }, +}; + +export const EditorFontColor: Story = { + args: { + content: CONTENT, + editorClass: "prose-sm min-h-[4rem] border rounded-b-lg border-t-0 p-2", + fixedMenu: true, + }, + render: function BasicRender(args) { + return ( +
+ +
+ ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const text = canvas.getByText((content) => { + return content.includes("paragraph"); + }); + await userEvent.tripleClick(text); + + const button = await screen.findByTitle("Font Color"); + await userEvent.click(button); + + const colorButton = await screen.findByTitle("Red"); + await userEvent.click(colorButton); + + await userEvent.click(button); + + const highlightButton = await screen.findByTitle("Highlight Red"); + await userEvent.click(highlightButton); + + const newText = canvas.getByText((content) => { + return content.includes("paragraph"); + }); + + expect(newText).toHaveStyle("color: rgb(204, 41, 41)"); + expect(newText.tagName).toBe("MARK"); + expect(newText).toHaveStyle("background-color: #ffe7e7"); + }, +}; diff --git a/packages/frappe-ui-react/src/components/textEditor/textEditor.stories.tsx b/packages/frappe-ui-react/src/components/textEditor/textEditor.stories.tsx new file mode 100644 index 00000000..e5e46013 --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/textEditor.stories.tsx @@ -0,0 +1,96 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import TextEditor from "./textEditor"; + +const meta: Meta = { + title: "Components/TextEditor", + component: TextEditor, + parameters: { + docs: { source: { type: "dynamic" } }, + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + content: { + control: "text", + description: "HTML content of the editor", + }, + placeholder: { + control: "text", + description: "Placeholder text when editor is empty", + }, + editorClass: { + control: "text", + description: "CSS classes to apply to the editor content area", + }, + editable: { + control: "boolean", + description: "Whether the editor is editable", + }, + autofocus: { + control: "boolean", + description: "Whether to autofocus the editor on mount", + }, + extensions: { + control: false, + description: "Additional TipTap extensions", + }, + starterkitOptions: { + control: "object", + description: "Configuration for StarterKit extension", + }, + fixedMenu: { + control: "boolean", + description: "Show fixed menu toolbar", + }, + onChange: { + action: "changed", + description: "Callback when content changes", + }, + onFocus: { + action: "focused", + description: "Callback when editor receives focus", + }, + onBlur: { + action: "blurred", + description: "Callback when editor loses focus", + }, + onTransaction: { + control: false, + description: "Callback on editor transaction", + }, + }, +}; + +const CONTENT: string = ` +
+

Heading 2

+

+ This is a paragraph with bold and italic text. +

+
    +
  • Item 1
  • +
  • Item 2
  • +
+
import { Button } from '@rtcamp/frappe-ui-react'
+const value = ref(true);
+ +
+`; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: { + content: CONTENT, + editorClass: "prose-sm min-h-[4rem] border rounded-b-lg border-t-0 p-2", + fixedMenu: true, + }, + render: function BasicRender(args) { + return ( +
+ +
+ ); + }, +}; diff --git a/packages/frappe-ui-react/src/components/textEditor/textEditor.tsx b/packages/frappe-ui-react/src/components/textEditor/textEditor.tsx new file mode 100644 index 00000000..5357dbed --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/textEditor.tsx @@ -0,0 +1,117 @@ +/** + * External dependencies. + */ +import { EditorContent, EditorContext, useEditor } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import { TaskItem, TaskList } from "@tiptap/extension-list"; +import TextAlign from "@tiptap/extension-text-align"; +import Highlight from "@tiptap/extension-highlight"; +import { TextStyleKit } from "@tiptap/extension-text-style"; +import Placeholder from "@tiptap/extension-placeholder"; +import { TableKit } from "@tiptap/extension-table"; +import clsx from "clsx"; + +/** + * Internal dependencies. + */ +import "./textEditor.css"; +import { normalizeClasses } from "../../utils"; +import type { TextEditorProps } from "./types"; +import FixedMenu from "./menu/fixedMenu"; +import { ExtendedCodeBlock } from "./extension/codeBlock"; + +const TextEditor = ({ + content, + placeholder = "", + editorClass = "", + editable = true, + autofocus = false, + extensions = [], + starterkitOptions = {}, + fixedMenu = false, + onChange, + onFocus, + onBlur, + onTransaction, + Top, + Editor, + Bottom, +}: TextEditorProps) => { + const editor = useEditor( + { + content, + editable, + autofocus, + editorProps: { + attributes: { + class: clsx( + "prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-outline-gray-2 prose-th:border-outline-gray-2 prose-td:relative prose-th:relative prose-th:bg-surface-gray-2 border-outline-gray-1", + normalizeClasses(editorClass) + ), + }, + }, + extensions: [ + StarterKit.configure({ + codeBlock: false, + horizontalRule: { + HTMLAttributes: { + class: "not-prose border-outline-gray-1 m-0", + }, + }, + ...starterkitOptions, + }), + Placeholder.configure({ + placeholder: + typeof placeholder === "function" ? placeholder() : placeholder, + }), + TaskList, + TaskItem.configure({ + nested: true, + }), + TextAlign.configure({ + types: ["heading", "paragraph"], + }), + TableKit, + TextStyleKit, + Highlight.configure({ multicolor: true }), + ExtendedCodeBlock, + ...extensions, + ], + onUpdate: ({ editor }) => { + onChange?.(editor.getHTML()); + }, + onFocus: ({ event }) => { + onFocus?.(event); + }, + onBlur: ({ event }) => { + onBlur?.(event); + }, + onTransaction: () => { + onTransaction?.(editor); + }, + }, + [ + content, + editable, + autofocus, + editorClass, + starterkitOptions, + extensions, + onChange, + onFocus, + onBlur, + onTransaction, + ] + ); + + return ( + + {Top && } + {fixedMenu && } + {Editor ? : } + {Bottom && } + + ); +}; + +export default TextEditor; diff --git a/packages/frappe-ui-react/src/components/textEditor/types.ts b/packages/frappe-ui-react/src/components/textEditor/types.ts new file mode 100644 index 00000000..6388efbe --- /dev/null +++ b/packages/frappe-ui-react/src/components/textEditor/types.ts @@ -0,0 +1,27 @@ +/** + * External dependencies. + */ +import { Editor, type Extension } from "@tiptap/react"; +import type { StarterKitOptions } from "@tiptap/starter-kit"; +import type { FC } from "react"; + +export interface TextEditorProps { + // Props + content?: string | null; + placeholder?: string | (() => string); + editorClass?: string | string[] | Record; + editable?: boolean; + autofocus?: boolean; + extensions?: Extension[]; + starterkitOptions?: Partial; + fixedMenu?: boolean; + // Events + onChange?: (content: string) => void; + onFocus?: (event: FocusEvent) => void; + onBlur?: (event: FocusEvent) => void; + onTransaction?: (editor: Editor) => void; + // Slots + Top?: FC; + Editor?: FC<{ editor: Editor }>; + Bottom?: FC; +} diff --git a/packages/frappe-ui-react/src/theme.css b/packages/frappe-ui-react/src/theme.css index 9d8a1e5a..05ad3c20 100644 --- a/packages/frappe-ui-react/src/theme.css +++ b/packages/frappe-ui-react/src/theme.css @@ -1,5 +1,6 @@ @import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"); @import "tailwindcss"; +@plugin "@tailwindcss/typography"; @config './utils/tailwind.config.cjs'; @source './components'; diff --git a/packages/frappe-ui-react/src/utils/index.ts b/packages/frappe-ui-react/src/utils/index.ts index 930359c2..b138daab 100644 --- a/packages/frappe-ui-react/src/utils/index.ts +++ b/packages/frappe-ui-react/src/utils/index.ts @@ -2,3 +2,4 @@ export { default as noop } from "./noop"; export * from "./debounce"; export * from "./fileUploadHandler"; export * from "./htmlAttrsToJsx"; +export * from "./normalizeClasses"; diff --git a/packages/frappe-ui-react/src/utils/normalizeClasses.ts b/packages/frappe-ui-react/src/utils/normalizeClasses.ts new file mode 100644 index 00000000..1bf01185 --- /dev/null +++ b/packages/frappe-ui-react/src/utils/normalizeClasses.ts @@ -0,0 +1,23 @@ +/** + * Normalizes a given input of class names into a single string. + * + * This utility function accepts a variety of input types (string, array, or object) + * and converts them into a space-separated string of class names. + * + * @param cls - The input class names, which can be: + * - A string: Returned as-is. + * - An array of strings: Joined into a single string with spaces. + * - An object: Keys with truthy values are included in the resulting string. + * @returns A space-separated string of class names. + */ +export function normalizeClasses(cls: string | string[] | object): string { + if (typeof cls === "string") return cls; + if (Array.isArray(cls)) return cls.join(" "); + if (typeof cls === "object") { + return Object.entries(cls) + .filter(([, value]) => value) + .map(([key]) => key) + .join(" "); + } + return ""; +} diff --git a/packages/frappe-ui-react/src/utils/tailwind.config.cjs b/packages/frappe-ui-react/src/utils/tailwind.config.cjs index 21cc402a..93e09861 100644 --- a/packages/frappe-ui-react/src/utils/tailwind.config.cjs +++ b/packages/frappe-ui-react/src/utils/tailwind.config.cjs @@ -9,5 +9,5 @@ module.exports = { theme: { extend, }, - plugins: [], + plugins: [require("@tailwindcss/typography")], }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ae05c5b..37ecb5c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,9 +42,6 @@ importers: '@storybook/react-vite': specifier: ^9.1.3 version: 9.1.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.57.0)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) - '@storybook/test': - specifier: ^8.6.15 - version: 8.6.15(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))) '@tailwindcss/vite': specifier: ^4.1.11 version: 4.1.18(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) @@ -126,6 +123,12 @@ importers: prettier: specifier: ^3.7.4 version: 3.8.1 + react: + specifier: ^19.2.4 + version: 19.2.4 + react-dom: + specifier: ^19.2.4 + version: 19.2.4(react@19.2.4) rimraf: specifier: ^6.1.2 version: 6.1.2 @@ -151,22 +154,58 @@ importers: packages/frappe-ui-react: dependencies: '@base-ui/react': - specifier: ^1.0.0 + specifier: ^1.1.0 version: 1.1.0(@types/react@19.2.10)(react-dom@19.2.3(react@19.2.4))(react@19.2.4) '@floating-ui/react': - specifier: ^0.27.13 + specifier: ^0.27.16 version: 0.27.16(react-dom@19.2.3(react@19.2.4))(react@19.2.4) '@headlessui/react': - specifier: ^2.2.6 + specifier: ^2.2.9 version: 2.2.9(react-dom@19.2.3(react@19.2.4))(react@19.2.4) '@popperjs/core': specifier: ^2.11.8 version: 2.11.8 + '@tailwindcss/typography': + specifier: ^0.5.19 + version: 0.5.19(tailwindcss@4.1.18) + '@tiptap/extension-code-block-lowlight': + specifier: ^3.17.1 + version: 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/extension-code-block@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)(highlight.js@11.11.1)(lowlight@3.3.0) + '@tiptap/extension-highlight': + specifier: ^3.17.1 + version: 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-list': + specifier: ^3.17.1 + version: 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + '@tiptap/extension-placeholder': + specifier: ^3.17.1 + version: 3.18.0(@tiptap/extensions@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)) + '@tiptap/extension-table': + specifier: ^3.17.1 + version: 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + '@tiptap/extension-task-list': + specifier: ^3.17.1 + version: 3.18.0(@tiptap/extension-list@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)) + '@tiptap/extension-text-align': + specifier: ^3.17.1 + version: 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-text-style': + specifier: ^3.17.1 + version: 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/pm': + specifier: ^3.17.1 + version: 3.18.0 + '@tiptap/react': + specifier: ^3.17.1 + version: 3.18.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.3(react@19.2.4))(react@19.2.4) + '@tiptap/starter-kit': + specifier: ^3.17.1 + version: 3.18.0 clsx: specifier: ^2.1.1 version: 2.1.1 dayjs: - specifier: ^1.11.13 + specifier: ^1.11.19 version: 1.11.19 dompurify: specifier: ^3.3.1 @@ -177,23 +216,26 @@ importers: feather-icons: specifier: ^4.29.2 version: 4.29.2 + lowlight: + specifier: ^3.3.0 + version: 3.3.0 lucide-react: specifier: ^0.539.0 version: 0.539.0(react@19.2.4) react: - specifier: ^19.1.0 + specifier: ^19.2.3 version: 19.2.4 react-dom: specifier: ^19.2.3 version: 19.2.3(react@19.2.4) react-grid-layout: - specifier: ^1.5.2 + specifier: ^1.5.3 version: 1.5.3(react-dom@19.2.3(react@19.2.4))(react@19.2.4) react-resizable: specifier: ^3.1.3 version: 3.1.3(react-dom@19.2.3(react@19.2.4))(react@19.2.4) styled-components: - specifier: ^6.1.19 + specifier: ^6.3.8 version: 6.3.8(react-dom@19.2.3(react@19.2.4))(react@19.2.4) tailwindcss: specifier: ^4.1.18 @@ -203,10 +245,10 @@ importers: specifier: ^4.29.4 version: 4.29.4 '@types/node': - specifier: ^24.1.0 + specifier: ^24.10.9 version: 24.10.9 '@types/react-grid-layout': - specifier: ^1.3.5 + specifier: ^1.3.6 version: 1.3.6 packages: @@ -1530,6 +1572,9 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -1733,11 +1778,6 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - '@storybook/instrumenter@8.6.15': - resolution: {integrity: sha512-TvHR/+yyIAOp/1bLulFai2kkhIBtAlBw7J6Jd9DKyInoGhTWNE1G1Y61jD5GWXX29AlwaHfzGUaX5NL1K+FJpg==} - peerDependencies: - storybook: ^8.6.15 - '@storybook/react-dom-shim@9.1.17': resolution: {integrity: sha512-Ss/lNvAy0Ziynu+KniQIByiNuyPz3dq7tD62hqSC/pHw190X+M7TKU3zcZvXhx2AQx1BYyxtdSHIZapb+P5mxQ==} peerDependencies: @@ -1766,11 +1806,6 @@ packages: typescript: optional: true - '@storybook/test@8.6.15': - resolution: {integrity: sha512-EwquDRUDVvWcZds3T2abmB5wSN/Vattal4YtZ6fpBlIUqONV4o/cOBX39cFfQSUCBrIXIjQ6RmapQCHK/PvBYw==} - peerDependencies: - storybook: ^8.6.15 - '@svgr/babel-plugin-add-jsx-attribute@8.0.0': resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} engines: {node: '>=14'} @@ -1927,6 +1962,11 @@ packages: resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} engines: {node: '>= 10'} + '@tailwindcss/typography@0.5.19': + resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + '@tailwindcss/vite@4.1.18': resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==} peerDependencies: @@ -1941,18 +1981,10 @@ packages: '@tanstack/virtual-core@3.13.18': resolution: {integrity: sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==} - '@testing-library/dom@10.4.0': - resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} - engines: {node: '>=18'} - '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} - '@testing-library/jest-dom@6.5.0': - resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} - engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - '@testing-library/jest-dom@6.9.1': resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} @@ -1972,18 +2004,201 @@ packages: '@types/react-dom': optional: true - '@testing-library/user-event@14.5.2': - resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} - engines: {node: '>=12', npm: '>=6'} - peerDependencies: - '@testing-library/dom': '>=7.21.4' - '@testing-library/user-event@14.6.1': resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' + '@tiptap/core@3.18.0': + resolution: {integrity: sha512-Gczd4GbK1DNgy/QUPElMVozoa0GW9mW8E31VIi7Q4a9PHHz8PcrxPmuWwtJ2q0PF8MWpOSLuBXoQTWaXZRPRnQ==} + peerDependencies: + '@tiptap/pm': ^3.18.0 + + '@tiptap/extension-blockquote@3.18.0': + resolution: {integrity: sha512-1HjEoM5vZDfFnq2OodNpW13s56a9pbl7jolUv1V9FrE3X5s7n0HCfDzIVpT7z1HgTdPtlN5oSt5uVyBwuwSUfA==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-bold@3.18.0': + resolution: {integrity: sha512-xUgOvHCdGXh9Lfxd7DtgsSr0T/egIwBllWHIBWDjQEQQ0b+ICn+0+i703btHMB4hjdduZtgVDrhK8jAW3U6swA==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-bubble-menu@3.18.0': + resolution: {integrity: sha512-9kYG1fVYQcA3Kp5Bq96lrKCp9oLpQqceDsK688r7iT1yymQlBPMunaqaqb5ZLQGhnNYbhfG+8xcQsvEKjklErA==} + peerDependencies: + '@tiptap/core': ^3.18.0 + '@tiptap/pm': ^3.18.0 + + '@tiptap/extension-bullet-list@3.18.0': + resolution: {integrity: sha512-8sEpY0nxAGGFDYlF+WVFPKX00X2dAAjmoi0+2eWvK990PdQqwXrQsRs7pkUbpE2mDtATV8+GlDXk9KDkK/ZXhA==} + peerDependencies: + '@tiptap/extension-list': ^3.18.0 + + '@tiptap/extension-code-block-lowlight@3.18.0': + resolution: {integrity: sha512-euUvh9r1KNSua9X4VdMS6lcWgUkcd0YznCFhp4b5gSqT5/5F7tGlvEg5mNpBeNhOIreDQV6zfBc7HvLfh7cLEA==} + peerDependencies: + '@tiptap/core': ^3.18.0 + '@tiptap/extension-code-block': ^3.18.0 + '@tiptap/pm': ^3.18.0 + highlight.js: ^11 + lowlight: ^2 || ^3 + + '@tiptap/extension-code-block@3.18.0': + resolution: {integrity: sha512-fCx1oT95ikGfoizw+XCjeglQxlLK4lWgUcB4Dcn5TdaCoFBQMEaZs7Q0jVajxxxULnyArkg60uarc1ac/IF2Hw==} + peerDependencies: + '@tiptap/core': ^3.18.0 + '@tiptap/pm': ^3.18.0 + + '@tiptap/extension-code@3.18.0': + resolution: {integrity: sha512-0SU53O0NRmdtRM2Hgzm372dVoHjs2F40o/dtB7ls4kocf4W89FyWeC2R6ZsFQqcXisNh9RTzLtYfbNyizGuZIw==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-document@3.18.0': + resolution: {integrity: sha512-e0hOGrjTMpCns8IC5p+c5CEiE1BBmFBFL+RpIxU/fjT2SaZ7q2xsFguBu94lQDT0cD6fdZokFRpGwEMxZNVGCg==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-dropcursor@3.18.0': + resolution: {integrity: sha512-pIW/K9fGth221dkfA5SInHcqfnCr0aG9LGkRiEh4gwM4cf6ceUBrvcD+QlemSZ4q9oktNGJmXT+sEXVOQ8QoeQ==} + peerDependencies: + '@tiptap/extensions': ^3.18.0 + + '@tiptap/extension-floating-menu@3.18.0': + resolution: {integrity: sha512-a2cBQi0I/X0o3a9b+adwJvkdxLzQzJIkP9dc/v25qGTSCjC1+ycois5WQOn8T4T8t4g/fAH1UOXEWnkWyTxLIg==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': ^3.18.0 + '@tiptap/pm': ^3.18.0 + + '@tiptap/extension-gapcursor@3.18.0': + resolution: {integrity: sha512-covioXPPHX3SnlTwC/1rcHUHAc7/JFd4vN0kZQmZmvGHlxqq2dPmtrPh8D7TuDuhG0k/3Z6i8dJFP0phfRAhuA==} + peerDependencies: + '@tiptap/extensions': ^3.18.0 + + '@tiptap/extension-hard-break@3.18.0': + resolution: {integrity: sha512-IXLiOHEmbU2Wn1jFRZC6apMxiJQvSRWhwoiubAvRxyiPSnFTeaEgT8Qgo5DjwB39NckP+o7XX7RrgzlkwdFPQQ==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-heading@3.18.0': + resolution: {integrity: sha512-MTamVnYsFWVndLSq5PRQ7ZmbF6AExsFS9uIvGtUAwuhzvR4of/WHh6wpvWYjA+BLXTWRrfuGHaZTl7UXBN13fg==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-highlight@3.18.0': + resolution: {integrity: sha512-syk2WsqfcZN+QZ8AS97hu7atqaLYi8MHQQRrjgO9z+CQzlA7qrTHFl9VF54vfCMQV3arVZlO/qLqFxDZ05tWyw==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-horizontal-rule@3.18.0': + resolution: {integrity: sha512-fEq7DwwQZ496RHNbMQypBVNqoWnhDEERbzWMBqlmfCfc/0FvJrHtsQkk3k4lgqMYqmBwym3Wp0SrRYiyKCPGTw==} + peerDependencies: + '@tiptap/core': ^3.18.0 + '@tiptap/pm': ^3.18.0 + + '@tiptap/extension-italic@3.18.0': + resolution: {integrity: sha512-1C4nB08psiRo0BPxAbpYq8peUOKnjQWtBCLPbE6B9ToTK3vmUk0AZTqLO11FvokuM1GF5l2Lg3sKrKFuC2hcjQ==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-link@3.18.0': + resolution: {integrity: sha512-1J28C4+fKAMQi7q/UsTjAmgmKTnzjExXY98hEBneiVzFDxqF69n7+Vb7nVTNAIhmmJkZMA0DEcMhSiQC/1/u4A==} + peerDependencies: + '@tiptap/core': ^3.18.0 + '@tiptap/pm': ^3.18.0 + + '@tiptap/extension-list-item@3.18.0': + resolution: {integrity: sha512-auTSt+NXoUnT0xofzFa+FnXsrW1TPdT1OB3U1OqQCIWkumZqL45A8OK9kpvyQsWj/xJ8fy1iZwFlKXPtxjLd2w==} + peerDependencies: + '@tiptap/extension-list': ^3.18.0 + + '@tiptap/extension-list-keymap@3.18.0': + resolution: {integrity: sha512-ZzO5r/cW7G0zpL/eM69WPnMpzb0YsSjtI60CYGA0iQDRJnK9INvxu0RU0ewM2faqqwASmtjuNJac+Fjk6scdXg==} + peerDependencies: + '@tiptap/extension-list': ^3.18.0 + + '@tiptap/extension-list@3.18.0': + resolution: {integrity: sha512-9lQBo45HNqIFcLEHAk+CY3W51eMMxIJjWbthm2CwEWr4PB3+922YELlvq8JcLH1nVFkBVpmBFmQe/GxgnCkzwQ==} + peerDependencies: + '@tiptap/core': ^3.18.0 + '@tiptap/pm': ^3.18.0 + + '@tiptap/extension-ordered-list@3.18.0': + resolution: {integrity: sha512-5bUAfklYLS5o6qvLLfreGyGvD1JKXqOQF0YntLyPuCGrXv7+XjPWQL2BmEf59fOn2UPT2syXLQ1WN5MHTArRzg==} + peerDependencies: + '@tiptap/extension-list': ^3.18.0 + + '@tiptap/extension-paragraph@3.18.0': + resolution: {integrity: sha512-uvFhdwiur4NhhUdBmDsajxjGAIlg5qga55fYag2DzOXxIQE2M7/aVMRkRpuJzb88GY4EHSh8rY34HgMK2FJt2Q==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-placeholder@3.18.0': + resolution: {integrity: sha512-jhN1Xa+MpfrTcCYZsFSvZYpUuMutPTC20ms0IsH1yN0y9tbAS+T6PHPC+dsvyAinYdA8yKElM6OO+jpyz4X1cw==} + peerDependencies: + '@tiptap/extensions': ^3.18.0 + + '@tiptap/extension-strike@3.18.0': + resolution: {integrity: sha512-kl/fa68LZg8NWUqTkRTfgyCx+IGqozBmzJxQDc1zxurrIU+VFptDV9UuZim587sbM2KGjCi/PNPjPGk1Uu0PVg==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-table@3.18.0': + resolution: {integrity: sha512-04BQYiSKxhy33Pd7UFZchW8UYH0FOts8LCwel11n507w2lNd/wbYMTI2A5AfOEOXvr6Xwx/jOWX4MWuhMqiZwQ==} + peerDependencies: + '@tiptap/core': ^3.18.0 + '@tiptap/pm': ^3.18.0 + + '@tiptap/extension-task-list@3.18.0': + resolution: {integrity: sha512-/znJ9/B4dBa4PizWtKFbVyuR7ROnlQA2HngL9Z5IvZQUlru1djpVfU4nuUImOkRWg4H7CRV5i2xL21JkcS57UA==} + peerDependencies: + '@tiptap/extension-list': ^3.18.0 + + '@tiptap/extension-text-align@3.18.0': + resolution: {integrity: sha512-NEd2IUgOymKPmGOnxum4hLRbdQyBlK1Cmkt8QGIrmatovPrw2PtWmHVZ6foNChsi/r932dKVfqZ/uMUh8QUppQ==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-text-style@3.18.0': + resolution: {integrity: sha512-4HNTzkRGP+9YL8ccCRHFwH469GN6/NKefFAzYZjh2l8JOrPgiYqIlu7Tc+q9w1L5m8f6sC5DBQfGOsgWxuM3GQ==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-text@3.18.0': + resolution: {integrity: sha512-9TvctdnBCwK/zyTi9kS7nGFNl5OvGM8xE0u38ZmQw5t79JOqJHgOroyqMjw8LHK/1PWrozfNCmsZbpq4IZuKXw==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extension-underline@3.18.0': + resolution: {integrity: sha512-009IeXURNJ/sm1pBqbj+2YQgjQaBtNlJR3dbl6xu49C+qExqCmI7klhKQuwsVVGLR7ahsYlp7d9RlftnhCXIcQ==} + peerDependencies: + '@tiptap/core': ^3.18.0 + + '@tiptap/extensions@3.18.0': + resolution: {integrity: sha512-uSRIE9HGshBN6NRFR3LX2lZqBLvX92SgU5A9AvUbJD4MqU63E+HdruJnRjsVlX3kPrmbIDowxrzXlUcg3K0USQ==} + peerDependencies: + '@tiptap/core': ^3.18.0 + '@tiptap/pm': ^3.18.0 + + '@tiptap/pm@3.18.0': + resolution: {integrity: sha512-8RoI5gW0xBVCsuxahpK8vx7onAw6k2/uR3hbGBBnH+HocDMaAZKot3nTyY546ij8ospIC1mnQ7k4BhVUZesZDQ==} + + '@tiptap/react@3.18.0': + resolution: {integrity: sha512-VC20YhoiWe2E03D1BRH+AVMgXeA7li+bzIoaBtpK9+AdizAC+TvWCb2I/9mQCy9m31zGYTD0vv0e7bVlJi+aKA==} + peerDependencies: + '@tiptap/core': ^3.18.0 + '@tiptap/pm': ^3.18.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tiptap/starter-kit@3.18.0': + resolution: {integrity: sha512-LctpCelqI/5nHEeZgCPiwI1MmTjGr6YCIBGWmS5s4DJE7NfevEkwomR/C05QKdVUwPhpCXIMeS1+h/RYqRo1KA==} + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -2017,6 +2232,9 @@ packages: '@types/feather-icons@4.29.4': resolution: {integrity: sha512-cvwI455PWx/gJ33XDTIZOdauRy+XCxZggkOT/tAQYZLdySPFATD4RnDC9mxOnCIEaK9kwPm3zZigkAsMkhXb5w==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -2035,6 +2253,15 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/mdx@2.0.13': resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} @@ -2067,6 +2294,12 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2260,9 +2493,6 @@ packages: '@vitest/browser': optional: true - '@vitest/expect@2.0.5': - resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} - '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -2277,12 +2507,6 @@ packages: vite: optional: true - '@vitest/pretty-format@2.0.5': - resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} - - '@vitest/pretty-format@2.1.9': - resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} - '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} @@ -2292,18 +2516,9 @@ packages: '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/spy@2.0.5': - resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} - '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/utils@2.0.5': - resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} - - '@vitest/utils@2.1.9': - resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} - '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} @@ -2523,10 +2738,6 @@ packages: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} - chalk@3.0.0: - resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} - engines: {node: '>=8'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2613,6 +2824,9 @@ packages: typescript: optional: true + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-spawn@6.0.6: resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} engines: {node: '>=4.8'} @@ -2631,6 +2845,11 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + cssstyle@4.6.0: resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} engines: {node: '>=18'} @@ -2712,6 +2931,9 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2944,6 +3166,10 @@ packages: fast-equals@4.0.3: resolution: {integrity: sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==} + fast-equals@5.4.0: + resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} + engines: {node: '>=6.0.0'} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -3131,6 +3357,10 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -3653,6 +3883,12 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + linkifyjs@4.3.2: + resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} + lint-staged@16.2.7: resolution: {integrity: sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==} engines: {node: '>=20.17'} @@ -3701,6 +3937,9 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3733,10 +3972,17 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + memorystream@0.3.1: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} @@ -3868,6 +4114,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -4011,6 +4260,10 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -4050,6 +4303,68 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + prosemirror-changeset@2.3.1: + resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==} + + prosemirror-collab@1.3.1: + resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} + + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + + prosemirror-dropcursor@1.8.2: + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} + + prosemirror-gapcursor@1.4.0: + resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==} + + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} + + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-markdown@1.13.3: + resolution: {integrity: sha512-3E+Et6cdXIH0EgN2tGYQ+EBT7N4kMiZFsW+hzx+aPtOmADDHWCdd2uUQb7yklJrfUYUOjEEu22BiN6UFgPe4cQ==} + + prosemirror-menu@1.2.5: + resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==} + + prosemirror-model@1.25.4: + resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==} + + prosemirror-schema-basic@1.2.4: + resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} + + prosemirror-trailing-node@3.0.0: + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} + peerDependencies: + prosemirror-model: ^1.22.1 + prosemirror-state: ^1.4.2 + prosemirror-view: ^1.33.8 + + prosemirror-transform@1.11.0: + resolution: {integrity: sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==} + + prosemirror-view@1.41.5: + resolution: {integrity: sha512-UDQbIPnDrjE8tqUBbPmCOZgtd75htE6W3r0JCmY9bL6W1iemDM37MZEKC49d+tdQ0v/CKx4gjxLoLsfkD2NiZA==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -4193,6 +4508,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} @@ -4508,18 +4826,10 @@ packages: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} - tinyrainbow@1.2.0: - resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} - engines: {node: '>=14.0.0'} - tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} - engines: {node: '>=14.0.0'} - tinyspy@4.0.4: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} @@ -4610,6 +4920,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -4658,6 +4971,9 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + v8-to-istanbul@9.3.0: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} @@ -4743,6 +5059,9 @@ packages: jsdom: optional: true + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -6353,6 +6672,8 @@ snapshots: dependencies: react: 19.2.4 + '@remirror/core-constants@3.0.0': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/pluginutils@5.3.0(rollup@4.57.0)': @@ -6508,12 +6829,6 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@storybook/instrumenter@8.6.15(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)))': - dependencies: - '@storybook/global': 5.0.0 - '@vitest/utils': 2.1.9 - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) - '@storybook/react-dom-shim@9.1.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)))': dependencies: react: 19.2.4 @@ -6550,17 +6865,6 @@ snapshots: optionalDependencies: typescript: 5.9.3 - '@storybook/test@8.6.15(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)))': - dependencies: - '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.6.15(storybook@9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))) - '@testing-library/dom': 10.4.0 - '@testing-library/jest-dom': 6.5.0 - '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) - '@vitest/expect': 2.0.5 - '@vitest/spy': 2.0.5 - storybook: 9.1.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 @@ -6696,6 +7000,11 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + '@tailwindcss/typography@0.5.19(tailwindcss@4.1.18)': + dependencies: + postcss-selector-parser: 6.0.10 + tailwindcss: 4.1.18 + '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': dependencies: '@tailwindcss/node': 4.1.18 @@ -6711,17 +7020,6 @@ snapshots: '@tanstack/virtual-core@3.13.18': {} - '@testing-library/dom@10.4.0': - dependencies: - '@babel/code-frame': 7.28.6 - '@babel/runtime': 7.28.6 - '@types/aria-query': 5.0.4 - aria-query: 5.3.0 - chalk: 4.1.2 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - pretty-format: 27.5.1 - '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.28.6 @@ -6733,16 +7031,6 @@ snapshots: picocolors: 1.1.1 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.5.0': - dependencies: - '@adobe/css-tools': 4.4.4 - aria-query: 5.3.2 - chalk: 3.0.0 - css.escape: 1.5.1 - dom-accessibility-api: 0.6.3 - lodash: 4.17.23 - redent: 3.0.0 - '@testing-library/jest-dom@6.9.1': dependencies: '@adobe/css-tools': 4.4.4 @@ -6762,14 +7050,220 @@ snapshots: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': - dependencies: - '@testing-library/dom': 10.4.0 - '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: '@testing-library/dom': 10.4.1 + '@tiptap/core@3.18.0(@tiptap/pm@3.18.0)': + dependencies: + '@tiptap/pm': 3.18.0 + + '@tiptap/extension-blockquote@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-bold@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-bubble-menu@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)': + dependencies: + '@floating-ui/dom': 1.7.4 + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + '@tiptap/pm': 3.18.0 + optional: true + + '@tiptap/extension-bullet-list@3.18.0(@tiptap/extension-list@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/extension-list': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + + '@tiptap/extension-code-block-lowlight@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/extension-code-block@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)(highlight.js@11.11.1)(lowlight@3.3.0)': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + '@tiptap/extension-code-block': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + '@tiptap/pm': 3.18.0 + highlight.js: 11.11.1 + lowlight: 3.3.0 + + '@tiptap/extension-code-block@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + '@tiptap/pm': 3.18.0 + + '@tiptap/extension-code@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-document@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-dropcursor@3.18.0(@tiptap/extensions@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/extensions': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + + '@tiptap/extension-floating-menu@3.18.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)': + dependencies: + '@floating-ui/dom': 1.7.4 + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + '@tiptap/pm': 3.18.0 + optional: true + + '@tiptap/extension-gapcursor@3.18.0(@tiptap/extensions@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/extensions': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + + '@tiptap/extension-hard-break@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-heading@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-highlight@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-horizontal-rule@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + '@tiptap/pm': 3.18.0 + + '@tiptap/extension-italic@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-link@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + '@tiptap/pm': 3.18.0 + linkifyjs: 4.3.2 + + '@tiptap/extension-list-item@3.18.0(@tiptap/extension-list@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/extension-list': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + + '@tiptap/extension-list-keymap@3.18.0(@tiptap/extension-list@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/extension-list': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + + '@tiptap/extension-list@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + '@tiptap/pm': 3.18.0 + + '@tiptap/extension-ordered-list@3.18.0(@tiptap/extension-list@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/extension-list': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + + '@tiptap/extension-paragraph@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-placeholder@3.18.0(@tiptap/extensions@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/extensions': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + + '@tiptap/extension-strike@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-table@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + '@tiptap/pm': 3.18.0 + + '@tiptap/extension-task-list@3.18.0(@tiptap/extension-list@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/extension-list': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + + '@tiptap/extension-text-align@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-text-style@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-text@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extension-underline@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + + '@tiptap/extensions@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + '@tiptap/pm': 3.18.0 + + '@tiptap/pm@3.18.0': + dependencies: + prosemirror-changeset: 2.3.1 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.4.0 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-markdown: 1.13.3 + prosemirror-menu: 1.2.5 + prosemirror-model: 1.25.4 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5) + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.5 + + '@tiptap/react@3.18.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.3(react@19.2.4))(react@19.2.4)': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + '@tiptap/pm': 3.18.0 + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@types/use-sync-external-store': 0.0.6 + fast-equals: 5.4.0 + react: 19.2.4 + react-dom: 19.2.3(react@19.2.4) + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + '@tiptap/extension-floating-menu': 3.18.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + transitivePeerDependencies: + - '@floating-ui/dom' + + '@tiptap/starter-kit@3.18.0': + dependencies: + '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) + '@tiptap/extension-blockquote': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-bold': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-bullet-list': 3.18.0(@tiptap/extension-list@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)) + '@tiptap/extension-code': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-code-block': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + '@tiptap/extension-document': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-dropcursor': 3.18.0(@tiptap/extensions@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)) + '@tiptap/extension-gapcursor': 3.18.0(@tiptap/extensions@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)) + '@tiptap/extension-hard-break': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-heading': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-horizontal-rule': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + '@tiptap/extension-italic': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-link': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + '@tiptap/extension-list': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + '@tiptap/extension-list-item': 3.18.0(@tiptap/extension-list@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)) + '@tiptap/extension-list-keymap': 3.18.0(@tiptap/extension-list@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)) + '@tiptap/extension-ordered-list': 3.18.0(@tiptap/extension-list@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)) + '@tiptap/extension-paragraph': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-strike': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-text': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extension-underline': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0)) + '@tiptap/extensions': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) + '@tiptap/pm': 3.18.0 + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -6811,6 +7305,10 @@ snapshots: '@types/feather-icons@4.29.4': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -6834,6 +7332,15 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdurl@2.0.0': {} + '@types/mdx@2.0.13': {} '@types/node@24.10.9': @@ -6863,6 +7370,10 @@ snapshots: '@types/trusted-types@2.0.7': optional: true + '@types/unist@3.0.3': {} + + '@types/use-sync-external-store@0.0.6': {} + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.35': @@ -7073,13 +7584,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/expect@2.0.5': - dependencies: - '@vitest/spy': 2.0.5 - '@vitest/utils': 2.0.5 - chai: 5.3.3 - tinyrainbow: 1.2.0 - '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.3 @@ -7096,14 +7600,6 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) - '@vitest/pretty-format@2.0.5': - dependencies: - tinyrainbow: 1.2.0 - - '@vitest/pretty-format@2.1.9': - dependencies: - tinyrainbow: 1.2.0 - '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 @@ -7120,27 +7616,10 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@2.0.5': - dependencies: - tinyspy: 3.0.2 - '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 - '@vitest/utils@2.0.5': - dependencies: - '@vitest/pretty-format': 2.0.5 - estree-walker: 3.0.3 - loupe: 3.2.1 - tinyrainbow: 1.2.0 - - '@vitest/utils@2.1.9': - dependencies: - '@vitest/pretty-format': 2.1.9 - loupe: 3.2.1 - tinyrainbow: 1.2.0 - '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 @@ -7404,11 +7883,6 @@ snapshots: escape-string-regexp: 1.0.5 supports-color: 5.5.0 - chalk@3.0.0: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -7480,6 +7954,8 @@ snapshots: optionalDependencies: typescript: 5.9.3 + crelt@1.0.6: {} + cross-spawn@6.0.6: dependencies: nice-try: 1.0.5 @@ -7504,6 +7980,8 @@ snapshots: css.escape@1.5.1: {} + cssesc@3.0.0: {} + cssstyle@4.6.0: dependencies: '@asamuzakjp/css-color': 3.2.0 @@ -7570,6 +8048,10 @@ snapshots: detect-newline@3.1.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + diff-sequences@29.6.3: {} doctrine@3.0.0: @@ -7922,6 +8404,8 @@ snapshots: fast-equals@4.0.3: {} + fast-equals@5.4.0: {} + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -8107,6 +8591,8 @@ snapshots: dependencies: hermes-estree: 0.25.1 + highlight.js@11.11.1: {} + hosted-git-info@2.8.9: {} html-encoding-sniffer@4.0.0: @@ -8797,6 +9283,12 @@ snapshots: lines-and-columns@1.2.4: {} + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.3.2: {} + lint-staged@16.2.7: dependencies: commander: 14.0.2 @@ -8859,6 +9351,12 @@ snapshots: dependencies: tslib: 2.8.1 + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + highlight.js: 11.11.1 + lru-cache@10.4.3: {} lru-cache@11.2.5: {} @@ -8891,8 +9389,19 @@ snapshots: dependencies: tmpl: 1.0.5 + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + math-intrinsics@1.1.0: {} + mdurl@2.0.0: {} + memorystream@0.3.1: {} merge-stream@2.0.0: {} @@ -9016,6 +9525,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + orderedmap@2.1.1: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -9130,6 +9641,11 @@ snapshots: possible-typed-array-names@1.1.0: {} + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-value-parser@4.2.0: {} postcss@8.4.49: @@ -9177,6 +9693,111 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + prosemirror-changeset@2.3.1: + dependencies: + prosemirror-transform: 1.11.0 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.4 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.5 + + prosemirror-gapcursor@1.4.0: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.5 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.5 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.3: + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.0 + prosemirror-model: 1.25.4 + + prosemirror-menu@1.2.5: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.5.0 + prosemirror-state: 1.4.4 + + prosemirror-model@1.25.4: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.5 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.5 + + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5): + dependencies: + '@remirror/core-constants': 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.5 + + prosemirror-transform@1.11.0: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-view@1.41.5: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + punycode.js@2.3.1: {} + punycode@2.3.1: {} pure-rand@7.0.1: {} @@ -9368,6 +9989,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.57.0 fsevents: 2.3.3 + rope-sequence@1.3.4: {} + rrweb-cssom@0.8.0: {} safe-array-concat@1.1.3: @@ -9728,12 +10351,8 @@ snapshots: tinypool@1.1.1: {} - tinyrainbow@1.2.0: {} - tinyrainbow@2.0.0: {} - tinyspy@3.0.2: {} - tinyspy@4.0.4: {} tldts-core@6.1.86: {} @@ -9828,6 +10447,8 @@ snapshots: typescript@5.9.3: {} + uc.micro@2.1.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -9893,6 +10514,8 @@ snapshots: dependencies: react: 19.2.4 + util-deprecate@1.0.2: {} + v8-to-istanbul@9.3.0: dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -9994,6 +10617,8 @@ snapshots: - tsx - yaml + w3c-keyname@2.2.8: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0