From 1d51ece9935f3a2a2649c1cf26bb231c1afa1449 Mon Sep 17 00:00:00 2001 From: Igor Martyniuk Date: Fri, 31 Jul 2026 12:41:47 +0000 Subject: [PATCH] Feature: Add TipTap editor package --- CHANGELOG.md | 5 + apps/admin/package.json | 1 + apps/admin/src/app/css/styles.css | 4 + .../components/RichTextFieldSection.tsx | 14 +- .../composed/fields/InputTiptapEditor.tsx | 59 ++ apps/admin/src/shared/forms/composed/index.ts | 1 + apps/admin/src/shared/widgets/common/FAQ.tsx | 60 -- apps/admin/vite.config.ts | 6 + package-lock.json | 781 +++++++++++++++--- .../editor-tiptap/.prettierignore | 5 + .../design-system/editor-tiptap/.prettierrc | 12 + .../design-system/editor-tiptap/README.md | 46 ++ .../design-system/editor-tiptap/package.json | 29 + .../editor-tiptap/src/RichTextEditor.tsx | 69 ++ .../src/components/BlockTypeSelect.tsx | 38 + .../src/components/LinkEditor.tsx | 103 +++ .../editor-tiptap/src/components/Toolbar.tsx | 285 +++++++ .../src/components/ToolbarButton.tsx | 49 ++ .../design-system/editor-tiptap/src/index.ts | 9 + .../editor-tiptap/src/presets.ts | 84 ++ .../editor-tiptap/src/styles.css | 103 +++ .../design-system/editor-tiptap/tsconfig.json | 21 + 22 files changed, 1600 insertions(+), 184 deletions(-) create mode 100644 apps/admin/src/shared/forms/composed/fields/InputTiptapEditor.tsx delete mode 100755 apps/admin/src/shared/widgets/common/FAQ.tsx create mode 100644 packages/design-system/editor-tiptap/.prettierignore create mode 100644 packages/design-system/editor-tiptap/.prettierrc create mode 100644 packages/design-system/editor-tiptap/README.md create mode 100644 packages/design-system/editor-tiptap/package.json create mode 100644 packages/design-system/editor-tiptap/src/RichTextEditor.tsx create mode 100644 packages/design-system/editor-tiptap/src/components/BlockTypeSelect.tsx create mode 100644 packages/design-system/editor-tiptap/src/components/LinkEditor.tsx create mode 100644 packages/design-system/editor-tiptap/src/components/Toolbar.tsx create mode 100644 packages/design-system/editor-tiptap/src/components/ToolbarButton.tsx create mode 100644 packages/design-system/editor-tiptap/src/index.ts create mode 100644 packages/design-system/editor-tiptap/src/presets.ts create mode 100644 packages/design-system/editor-tiptap/src/styles.css create mode 100644 packages/design-system/editor-tiptap/tsconfig.json diff --git a/CHANGELOG.md b/CHANGELOG.md index c229081..dbf8820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added +- Added `@crux-kit/editor-tiptap`, a free, headless rich-text editor package built on TipTap. The `light` preset covers bold, italic, underline, + strikethrough, headings, lists, blockquote, text alignment, links, and undo/redo, reading and + writing content as plain HTML so it themes with the shared design-system tokens. +- Added the `InputTiptapEditor` composed form field. + ### Changed ### Fixed diff --git a/apps/admin/package.json b/apps/admin/package.json index cb7fafd..33b07e9 100755 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -12,6 +12,7 @@ "preview": "vite preview" }, "dependencies": { + "@crux-kit/editor-tiptap": "*", "@crux-kit/hooks": "*", "@crux-kit/intl-date": "*", "@crux-kit/intl-name": "*", diff --git a/apps/admin/src/app/css/styles.css b/apps/admin/src/app/css/styles.css index 4128b7d..b993031 100755 --- a/apps/admin/src/app/css/styles.css +++ b/apps/admin/src/app/css/styles.css @@ -3,6 +3,10 @@ @plugin "@tailwindcss/typography"; @plugin "@tailwindcss/typography"; +/* @crux-kit/editor-tiptap — Tailwind class scanning + editor content styles */ +@source '../../../../../packages/design-system/editor-tiptap/src'; +@import '@crux-kit/editor-tiptap/styles.css'; + /* ReUI Configs */ @import './config.reui.css'; diff --git a/apps/admin/src/devtools/pages/SandboxFormsPage/components/RichTextFieldSection.tsx b/apps/admin/src/devtools/pages/SandboxFormsPage/components/RichTextFieldSection.tsx index 2e3fd88..92c05c3 100644 --- a/apps/admin/src/devtools/pages/SandboxFormsPage/components/RichTextFieldSection.tsx +++ b/apps/admin/src/devtools/pages/SandboxFormsPage/components/RichTextFieldSection.tsx @@ -1,12 +1,12 @@ import { useForm } from 'react-hook-form'; import SandboxShowcase from '#devtools/view/components/SandboxShowcase'; -import { FormElement, FormGrid, GridElement, InputMCEEditor } from '#shared/forms/composed'; +import { FormElement, FormGrid, GridElement, InputMCEEditor, InputTiptapEditor } from '#shared/forms/composed'; import { Form as FormProvider } from '#shared/ui/form'; import template from '../lib/templates/richTextField'; import FormActions from './FormActions'; export default function RichTextFieldSection() { - const form = useForm({ defaultValues: { content: '' } }); + const form = useForm({ defaultValues: { content: '', contentLight: '' } }); return ( + + + diff --git a/apps/admin/src/shared/forms/composed/fields/InputTiptapEditor.tsx b/apps/admin/src/shared/forms/composed/fields/InputTiptapEditor.tsx new file mode 100644 index 0000000..0328904 --- /dev/null +++ b/apps/admin/src/shared/forms/composed/fields/InputTiptapEditor.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { useFormContext } from 'react-hook-form'; +import { RichTextEditor, type EditorPreset } from '@crux-kit/editor-tiptap'; +import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '#shared/ui/form'; + +/** + * Free rich-text field backed by @crux-kit/editor-tiptap (TipTap). Same + * value/onChange (HTML string) seam as InputMCEEditor, so it is a drop-in + * replacement. + */ +export const InputTiptapEditor: React.FC<{ + name: string; + label?: string; + description?: React.ReactNode; + placeholder?: string; + required?: boolean; + disabled?: boolean; + preset?: EditorPreset; +}> = ({ + name, + description, + label, + placeholder, + required = false, + disabled = false, + preset = 'light' +}) => { + const form = useFormContext(); + + return ( + { + return ( + + {label && ( + + {label} + {required && *} + + )} + + + + {description} + + ); + }} + /> + ); +}; diff --git a/apps/admin/src/shared/forms/composed/index.ts b/apps/admin/src/shared/forms/composed/index.ts index 1136593..6cf6e33 100755 --- a/apps/admin/src/shared/forms/composed/index.ts +++ b/apps/admin/src/shared/forms/composed/index.ts @@ -30,3 +30,4 @@ export * from './fields/InputDate'; /* Text editor fields */ export * from './fields/InputMCEEditor'; +export * from './fields/InputTiptapEditor'; diff --git a/apps/admin/src/shared/widgets/common/FAQ.tsx b/apps/admin/src/shared/widgets/common/FAQ.tsx deleted file mode 100755 index 4d984be..0000000 --- a/apps/admin/src/shared/widgets/common/FAQ.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '#shared/ui/Accordion'; -import { Card, CardContent, CardHeader, CardTitle } from '#shared/ui/Card'; - -export interface FaqItem { - title: string; - text: string; -} - -export type FaqItems = Array; - -export function Faq() { - const items: FaqItems = [ - { - title: 'How is pricing determined for each plan ?', - text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision. Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision. Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision" - }, - { - title: 'What payment methods are accepted for subscriptions ?', - text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision" - }, - { - title: 'Are there any hidden fees in the pricing ?', - text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision" - }, - { - title: 'Is there a discount for annual subscriptions ?', - text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision" - }, - { - title: 'Do you offer refunds on subscription cancellations ?', - text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision" - }, - { - title: 'Can I add extra features to my current plan ?', - text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision" - } - ]; - - const generateItems = () => { - return ( - - {items.map((item, index) => ( - - {item.title} - {item.text} - - ))} - - ); - }; - - return ( - - - FAQ - - {generateItems()} - - ); -} diff --git a/apps/admin/vite.config.ts b/apps/admin/vite.config.ts index 1d05055..ca2ff3b 100755 --- a/apps/admin/vite.config.ts +++ b/apps/admin/vite.config.ts @@ -30,6 +30,12 @@ export default defineConfig(({ mode }) => { new URL('../../packages/design-system/keenicon/src', import.meta.url) ) }, + { + find: '@crux-kit/editor-tiptap', + replacement: fileURLToPath( + new URL('../../packages/design-system/editor-tiptap/src', import.meta.url) + ) + }, { find: '@crux-kit/hooks', replacement: fileURLToPath(new URL('../../packages/hooks/src', import.meta.url)) diff --git a/package-lock.json b/package-lock.json index 111ea88..63cda69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,8 +44,9 @@ }, "apps/admin": { "name": "@crux-kit/admin-app", - "version": "1.1.0", + "version": "1.2.0", "dependencies": { + "@crux-kit/editor-tiptap": "*", "@crux-kit/hooks": "*", "@crux-kit/intl-date": "*", "@crux-kit/intl-name": "*", @@ -211,8 +212,9 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "extraneous": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -223,7 +225,7 @@ }, "apps/backend": { "name": "@crux-kit/backend", - "version": "1.1.0", + "version": "1.2.0", "license": "UNLICENSED", "dependencies": { "@adonisjs/auth": "^10.1.0", @@ -1259,9 +1261,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1278,9 +1277,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1297,9 +1293,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1316,9 +1309,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2087,6 +2077,10 @@ "resolved": "apps/backend", "link": true }, + "node_modules/@crux-kit/editor-tiptap": { + "resolved": "packages/design-system/editor-tiptap", + "link": true + }, "node_modules/@crux-kit/hooks": { "resolved": "packages/hooks", "link": true @@ -4379,7 +4373,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@japa/assert/-/assert-4.2.0.tgz", "integrity": "sha512-Krgrcee01BN1StlVwK5JQP6LL5t3DE3uFNbfFoDTfW7kQuHB0xh6yfaV0hrgcoiEjsqmm2OOsVWeju9aXK4vIA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@poppinss/macroable": "^1.1.0", @@ -5703,9 +5697,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5723,9 +5714,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5743,9 +5731,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5763,9 +5748,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -7319,9 +7301,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7335,9 +7314,6 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7351,9 +7327,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7367,9 +7340,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7383,9 +7353,6 @@ "cpu": [ "loong64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7399,9 +7366,6 @@ "cpu": [ "loong64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7415,9 +7379,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7431,9 +7392,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7447,9 +7405,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7463,9 +7418,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7479,9 +7431,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7495,9 +7444,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7511,9 +7457,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8066,9 +8009,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8086,9 +8026,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8106,9 +8043,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8126,9 +8060,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8146,9 +8077,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8166,9 +8094,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8380,9 +8305,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8399,9 +8321,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8418,9 +8337,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8437,9 +8353,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8642,6 +8555,449 @@ } } }, + "node_modules/@tiptap/core": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.29.2.tgz", + "integrity": "sha512-oKUkiPUB7noilVYxI9lNzUD4rX17sHub+PYjMfHMWHG9A3nvIy+FdePIVIIhThKWF7ijhr3eIqHY51Bn+GAFtw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "3.29.2" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.29.2.tgz", + "integrity": "sha512-ca4OzKDh0yaxg2+Z56bC2QnWsNsFp2YMRfVig1PDXyMVFMNJpLcnhxgq/9btn+xYAlYrj8RymOeCTYREOR6Zjg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.29.2.tgz", + "integrity": "sha512-elYbGxJsYnBb4leqrcjdIJuiG380BcOgN+UUzvOv+qEjfGVzHodFOMBl3qnmD6urYHNu5/qQK2S0qSSRXKCLNQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.29.2.tgz", + "integrity": "sha512-kzcWarcpr031rZu+R3Hzut5uxb3Qj/xxMDEYZIQ3uiq1yb5vEMXj8/SIyWautk6Nu8Rr1leV3rGdR4NzhzyFAA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.29.2.tgz", + "integrity": "sha512-3bWcCUPbCHv0XttlMdnAtXLNYWx2pblByMgxmGsaP9FU0QnslGXty6A6gHCqI33ygRg1vrA6U5Wtpwbi5aKu5g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.29.2" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.29.2.tgz", + "integrity": "sha512-c6W5UGuB7WNLpYocsgRzpO2OOTI4QjaI9jjHRMuty9z+s9DtaYM/HrRLNwVh6MopkHb+i/89Wkv8gCS34fftig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.29.2.tgz", + "integrity": "sha512-w153ct8g6dLiPTdXQ6SOIMxX4SEo5Q50AmjdEEEcJ7ZcYUcde/ScSskLHfOYmyt5ZFAiyEwr121+pux+p3/oAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.29.2.tgz", + "integrity": "sha512-YUamvefLnsqu6124GavVTI7nqcFlQJ12ROB0oSwG69eSBZYNjg1tIs05LFrBxkwf4Xgqd6YzfJ9+FeG428RvzQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.29.2.tgz", + "integrity": "sha512-KKno7cU9r1HdR48CRrsDu69/1UjZdoslq/UcE+Kx+tdhAv/aljXMkRSNzGMrBNOBDmHRgS1+58zm21WQWdQzwA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "3.29.2" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.29.2.tgz", + "integrity": "sha512-CBTq4Xs5aGWr65uFCIkKBms796OhmirWf/ax//iJ4fl9IfwqjwFuc2vQs9PmuwpKn9ctDHo2sMTtvyeCvIt5LQ==", + "license": "MIT", + "optional": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.29.2.tgz", + "integrity": "sha512-8Q39UR4/Tit759IeW9xZIe3NMwN11GsuA3FLheDyyGn7RrW02HD3HhUDlazE54Ki4HoosjFmChPlN4Ik2ubdRQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "3.29.2" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.29.2.tgz", + "integrity": "sha512-eUW3LN3fq8rXnjEUeI3D2QONYdLsU3yYQm4jxlErs2h4cfwrFjgf19VSUFVmm6LrFbbQ0OnDVPeVLL6iOwDw2w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.29.2.tgz", + "integrity": "sha512-6W4aIy70Mh7BNlbG9zZ5FBLhJhU2UUEzgZJ/jwYSCcB30o8McLxJSEjhtoHiX8R78Ah2/JzBGvIe5olZlbeE4A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.29.2.tgz", + "integrity": "sha512-8/ZPzbB9X85Mc9/7xVLZupQKBr2UVcQTGr512xtqMW+XkCQRHCph46tRo828YE13IMWI5fWn/FaNCqXG9cULSw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.29.2.tgz", + "integrity": "sha512-iH63V/5wsaMnY4Jz0+meaAGhaec4AiOzOduzl6ZZr5IyGhZ1kthyW84ELt0dyLI3hNceAUhaNWc+I7+vX0aoXA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.29.2.tgz", + "integrity": "sha512-DcVer5SqrexKCEP6Ip1UPxJUMvcRCCItSv0wxoGytanrimBh2smvcg6X0DWnjlsi5H0updhyl+atYCmmQXUIXA==", + "license": "MIT", + "dependencies": { + "linkifyjs": "^4.3.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" + } + }, + "node_modules/@tiptap/extension-list": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.29.2.tgz", + "integrity": "sha512-WPZ9BHAPT6QeIm1vdVkuoOWvy9a8/EZeJwV2VhU8LXyTAttvzyj4rsbbHyJWvYWlUSTt/QF2AZ2zhKo7u1w3/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.29.2.tgz", + "integrity": "sha512-s8vBVHHFT0Qpu7CzAZ7S1kYmSiVaDvUNvMNcZUWnxj6VPfiwmx0eXd9FsjePrRCMoM5tFmPnhFTHDxY3D/eZeQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.29.2" + } + }, + "node_modules/@tiptap/extension-list-keymap": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.29.2.tgz", + "integrity": "sha512-R+3k8OLnxdCH7Xy9ieOwUt5m2Je74u8mikothGmsYVO2Zyq48fIbmZ+X6RBPCu7DBOI2FIUhHEFbKQeDWvDNmA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.29.2" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.29.2.tgz", + "integrity": "sha512-ndCunC+UsYOpkOtL7vGnDz21UNa45WUlcO9wMT1fbuYow2QnRhsuMlCWENXI52YPPARWuQ0RDgN7q6TaxPERBg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.29.2" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.29.2.tgz", + "integrity": "sha512-7qJj5YTr11vvjNgjDN1ypOfwTovc0QOCYcit/rskeuVgnmQZOZQzC/BbyKLLG7UGnpRLemU/mEGbW9pAqjAXkQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.29.2.tgz", + "integrity": "sha512-aEvLAbddUQZ+FukCreV3q4G2HfNI+odE7E9U+wbq6XsSWKyo8/pDu1muz+TFKNre4blSMOQ3JQmw5UeHDKy+fg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.29.2.tgz", + "integrity": "sha512-Ubko45JWWHe8glBt2PiGNF8hcbys/JNalFhiR7Y1X4iOOtAxAKJJxh3+eq+//NTlGuBPdWGp7zw8EEUp7anjKA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2" + } + }, + "node_modules/@tiptap/extension-text-align": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.29.2.tgz", + "integrity": "sha512-205FF87zjUEh79n6dgVy9SMj1P+rNserHMqPd8lF7YS2TOWikXPG4PFuQHWXXCYzaCvQXAiATSAyPXXmUWUvaQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.29.2.tgz", + "integrity": "sha512-K7XwH/xS/5AIREWQ00VTEf/W5U0olp7j6wwit7cdd/8nHv6h6AGr1+iEApHKoLXWQZLfGQKzJlT9W61LAl+fHA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2" + } + }, + "node_modules/@tiptap/extensions": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.29.2.tgz", + "integrity": "sha512-BCz+FCAChSYtUe4BFj97HEO+nSK+J7GxbJgZG4Hg7DT/gI+hRyeNndU8efiQAx3WGdzsFi3UxRpcF1tTQM7iMQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" + } + }, + "node_modules/@tiptap/pm": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.29.2.tgz", + "integrity": "sha512-GCOme7xHaS+DSoaA4CDcAD3l6JyBlvZhvCyfsy2Vp6j8tEoBkZWio7soYVosmlyn7zq8/64VeFZP5s47yfG7fQ==", + "license": "MIT", + "dependencies": { + "prosemirror-changeset": "^2.4.1", + "prosemirror-commands": "^1.7.1", + "prosemirror-dropcursor": "^1.8.2", + "prosemirror-gapcursor": "^1.4.1", + "prosemirror-history": "^1.5.0", + "prosemirror-inputrules": "^1.5.1", + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.11", + "prosemirror-schema-list": "^1.5.1", + "prosemirror-state": "^1.4.4", + "prosemirror-tables": "^1.8.5", + "prosemirror-transform": "^1.12.0", + "prosemirror-view": "^1.41.9" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.29.2.tgz", + "integrity": "sha512-ZMKgteYmZXnq7C22U9fbCTC6jAF8XlQM5n2K2FLWCdYAlP4FNV3XeQ9hY2a13KSQ0Q3yltWXZlcWBpt5ClxScg==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "fast-equals": "^5.3.3", + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "optionalDependencies": { + "@tiptap/extension-bubble-menu": "^3.29.2", + "@tiptap/extension-floating-menu": "^3.29.2" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2", + "@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" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.29.2.tgz", + "integrity": "sha512-oTu0tysiqk4zgjEtxRHjAQgxUKaAevZwueOWwSWubHdokqp7SpcbE5n9USJv89HKuTUDm3GjnQH6q8HNn/2DsA==", + "license": "MIT", + "dependencies": { + "@tiptap/core": "^3.29.2", + "@tiptap/extension-blockquote": "^3.29.2", + "@tiptap/extension-bold": "^3.29.2", + "@tiptap/extension-bullet-list": "^3.29.2", + "@tiptap/extension-code": "^3.29.2", + "@tiptap/extension-code-block": "^3.29.2", + "@tiptap/extension-document": "^3.29.2", + "@tiptap/extension-dropcursor": "^3.29.2", + "@tiptap/extension-gapcursor": "^3.29.2", + "@tiptap/extension-hard-break": "^3.29.2", + "@tiptap/extension-heading": "^3.29.2", + "@tiptap/extension-horizontal-rule": "^3.29.2", + "@tiptap/extension-italic": "^3.29.2", + "@tiptap/extension-link": "^3.29.2", + "@tiptap/extension-list": "^3.29.2", + "@tiptap/extension-list-item": "^3.29.2", + "@tiptap/extension-list-keymap": "^3.29.2", + "@tiptap/extension-ordered-list": "^3.29.2", + "@tiptap/extension-paragraph": "^3.29.2", + "@tiptap/extension-strike": "^3.29.2", + "@tiptap/extension-text": "^3.29.2", + "@tiptap/extension-underline": "^3.29.2", + "@tiptap/extensions": "^3.29.2", + "@tiptap/pm": "^3.29.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, "node_modules/@tokenizer/inflate": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", @@ -8793,7 +9149,7 @@ "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/deep-eql": "*", @@ -8811,7 +9167,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/estree": { @@ -8919,7 +9275,6 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -8956,6 +9311,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/validator": { "version": "13.15.10", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", @@ -10320,7 +10681,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -10687,7 +11048,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -12315,6 +12676,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/fast-equals": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.1.tgz", + "integrity": "sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -14334,9 +14704,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -14357,9 +14724,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -14380,9 +14744,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -14403,9 +14764,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -14466,6 +14824,12 @@ "dev": true, "license": "MIT" }, + "node_modules/linkifyjs": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.3.tgz", + "integrity": "sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==", + "license": "MIT" + }, "node_modules/lint-staged": { "version": "16.4.0", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", @@ -15110,6 +15474,12 @@ "node": ">= 0.8.0" } }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT" + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -16749,6 +17119,145 @@ "react-is": "^16.13.1" } }, + "node_modules/prosemirror-changeset": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.1.tgz", + "integrity": "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==", + "license": "MIT", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.3.tgz", + "integrity": "sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", + "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.11", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.11.tgz", + "integrity": "sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==", + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", + "integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.42.2", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.42.2.tgz", + "integrity": "sha512-Pdg0l5kXm8aLDquFAnQFTCITg0q44sLqBlHlpsVLD9segdOao8TOfQdAhCrCXyVgPSRr6UDDROOIWA3bIrN9YQ==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.25.8", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -17590,6 +18099,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT" + }, "node_modules/run-applescript": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", @@ -19709,6 +20224,12 @@ } } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -20042,14 +20563,32 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "packages/design-system/editor-tiptap": { + "name": "@crux-kit/editor-tiptap", + "version": "1.2.0", + "dependencies": { + "@crux-kit/utils": "*", + "@tiptap/core": "^3.29.2", + "@tiptap/extension-text-align": "^3.29.2", + "@tiptap/extensions": "^3.29.2", + "@tiptap/pm": "^3.29.2", + "@tiptap/react": "^3.29.2", + "@tiptap/starter-kit": "^3.29.2" + }, + "peerDependencies": { + "lucide-react": "*", + "react": ">=19", + "react-dom": ">=19" + } + }, "packages/design-system/keenicon": { "name": "@crux-kit/keenicon", - "version": "1.1.0", + "version": "1.2.0", "devDependencies": {} }, "packages/hooks": { "name": "@crux-kit/hooks", - "version": "1.1.0", + "version": "1.2.0", "dependencies": { "@crux-kit/types": "*" }, @@ -20057,7 +20596,7 @@ }, "packages/services/intl-date": { "name": "@crux-kit/intl-date", - "version": "1.1.0", + "version": "1.2.0", "dependencies": { "@crux-kit/types": "*" }, @@ -20065,7 +20604,7 @@ }, "packages/services/intl-name": { "name": "@crux-kit/intl-name", - "version": "1.1.0", + "version": "1.2.0", "dependencies": { "@crux-kit/types": "*" }, @@ -20073,17 +20612,17 @@ }, "packages/services/local-storage": { "name": "@crux-kit/local-storage", - "version": "1.1.0", + "version": "1.2.0", "devDependencies": {} }, "packages/types": { "name": "@crux-kit/types", - "version": "1.1.0", + "version": "1.2.0", "devDependencies": {} }, "packages/utils": { "name": "@crux-kit/utils", - "version": "1.1.0", + "version": "1.2.0", "dependencies": { "@crux-kit/types": "*" }, diff --git a/packages/design-system/editor-tiptap/.prettierignore b/packages/design-system/editor-tiptap/.prettierignore new file mode 100644 index 0000000..11ffad2 --- /dev/null +++ b/packages/design-system/editor-tiptap/.prettierignore @@ -0,0 +1,5 @@ +*.md +node_modules +dist +public +build \ No newline at end of file diff --git a/packages/design-system/editor-tiptap/.prettierrc b/packages/design-system/editor-tiptap/.prettierrc new file mode 100644 index 0000000..cfe5cbe --- /dev/null +++ b/packages/design-system/editor-tiptap/.prettierrc @@ -0,0 +1,12 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "endOfLine": "lf", + "singleQuote": true, + "trailingComma": "all", + "bracketSpacing": true, + "semi": true, + "bracketSameLine": false, + "jsxSingleQuote": false, + "arrowParens": "always" +} diff --git a/packages/design-system/editor-tiptap/README.md b/packages/design-system/editor-tiptap/README.md new file mode 100644 index 0000000..74a8976 --- /dev/null +++ b/packages/design-system/editor-tiptap/README.md @@ -0,0 +1,46 @@ +# @crux-kit/editor-tiptap + +A free, headless rich-text editor for Crux apps, built on [TipTap](https://tiptap.dev) +(MIT-licensed). Emits and consumes plain **HTML strings**, so it drops into the same +`value` / `onChange` seam the previous TinyMCE editor used. + +This package contains **all TipTap-specific editor code**. Other editor libraries live in +their own sibling packages (e.g. `@crux-kit/editor-quill`, `@crux-kit/editor-lexical`). + +The editor is **preset-driven**: one library, multiple feature levels. Pick a preset per +app / per field instead of shipping a different editor library in each app. + +## Usage + +```tsx +import { RichTextEditor } from '@crux-kit/editor-tiptap'; + +; +``` + +### Presets + +| Preset | Features | +| ------- | ------------------------------------------------------------------------------------------- | +| `light` | Bold, italic, underline, strike, headings, lists, blockquote, text align, links, undo/redo. | + +Extend by adding new presets in `src/presets.ts` — the toolbar renders from the preset, so +new apps (e.g. web studio / page manager) reuse the same component with a different preset. + +## Styling + +The toolbar uses the shared design-system Tailwind tokens (`bg-accent`, `border-input`, +`text-muted-foreground`, …), so it themes automatically with the host app. + +Wire it up once, entirely in the app's main Tailwind CSS entry: + +```css +/* generate the toolbar's utility classes */ +@source '../../../../../packages/design-system/editor-tiptap/src'; +/* editor content styles */ +@import '@crux-kit/editor-tiptap/styles.css'; +``` + +(The content styles can also be pulled in from JS with +`import '@crux-kit/editor-tiptap/styles.css';` — but keeping both lines together in the CSS +entry is cleaner.) diff --git a/packages/design-system/editor-tiptap/package.json b/packages/design-system/editor-tiptap/package.json new file mode 100644 index 0000000..6ae95c6 --- /dev/null +++ b/packages/design-system/editor-tiptap/package.json @@ -0,0 +1,29 @@ +{ + "name": "@crux-kit/editor-tiptap", + "version": "1.2.0", + "type": "module", + "types": "src/index.ts", + "private": true, + "exports": { + ".": "./src/index.ts", + "./styles.css": "./src/styles.css" + }, + "style": "src/styles.css", + "scripts": { + "format": "prettier --write ." + }, + "dependencies": { + "@crux-kit/utils": "*", + "@tiptap/core": "^3.29.2", + "@tiptap/extension-text-align": "^3.29.2", + "@tiptap/extensions": "^3.29.2", + "@tiptap/pm": "^3.29.2", + "@tiptap/react": "^3.29.2", + "@tiptap/starter-kit": "^3.29.2" + }, + "peerDependencies": { + "lucide-react": "*", + "react": ">=19", + "react-dom": ">=19" + } +} diff --git a/packages/design-system/editor-tiptap/src/RichTextEditor.tsx b/packages/design-system/editor-tiptap/src/RichTextEditor.tsx new file mode 100644 index 0000000..f6fc7ba --- /dev/null +++ b/packages/design-system/editor-tiptap/src/RichTextEditor.tsx @@ -0,0 +1,69 @@ +import { type Editor, EditorContent, useEditor } from '@tiptap/react'; +import React from 'react'; +import { cn } from '@crux-kit/utils'; +import { Toolbar } from './components/Toolbar'; +import { buildExtensions, type EditorPreset } from './presets'; + +export type RichTextEditorProps = { + /** Current value as an HTML string. */ + value: string; + /** Called with the new HTML string on every change. */ + onChange: (value: string) => void; + /** Feature level. Defaults to `light`. */ + preset?: EditorPreset; + disabled?: boolean; + placeholder?: string; + className?: string; + /** Access the underlying TipTap editor instance once it is ready. */ + onReady?: (editor: Editor) => void; +}; + +export const RichTextEditor: React.FC = ({ + value, + onChange, + preset = 'light', + disabled = false, + placeholder, + className, + onReady, +}) => { + const onChangeRef = React.useRef(onChange); + onChangeRef.current = onChange; + const onReadyRef = React.useRef(onReady); + onReadyRef.current = onReady; + + const editor = useEditor({ + immediatelyRender: false, + editable: !disabled, + extensions: buildExtensions(preset, placeholder), + content: value, + editorProps: { + attributes: { + class: 'crux-editor__content', + role: 'textbox', + 'aria-multiline': 'true', + }, + }, + onCreate: ({ editor }) => onReadyRef.current?.(editor), + onUpdate: ({ editor }) => onChangeRef.current(editor.getHTML()), + }); + + React.useEffect(() => { + if (!editor) return; + const next = value ?? ''; + if (next !== editor.getHTML()) { + editor.commands.setContent(next, { emitUpdate: false }); + } + }, [editor, value]); + + React.useEffect(() => { + editor?.setEditable(!disabled); + }, [editor, disabled]); + + return ( +
+ + +
+ ); +}; diff --git a/packages/design-system/editor-tiptap/src/components/BlockTypeSelect.tsx b/packages/design-system/editor-tiptap/src/components/BlockTypeSelect.tsx new file mode 100644 index 0000000..c0b2d04 --- /dev/null +++ b/packages/design-system/editor-tiptap/src/components/BlockTypeSelect.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { cn } from '@crux-kit/utils'; +import { type BlockType } from '../presets'; + +const OPTIONS: Array<{ value: BlockType; label: string }> = [ + { value: 'paragraph', label: 'Paragraph' }, + { value: 'h1', label: 'Heading 1' }, + { value: 'h2', label: 'Heading 2' }, + { value: 'h3', label: 'Heading 3' }, +]; + +export const BlockTypeSelect: React.FC<{ + value: BlockType; + disabled?: boolean; + onChange: (value: BlockType) => void; +}> = ({ value, disabled = false, onChange }) => { + return ( + + ); +}; diff --git a/packages/design-system/editor-tiptap/src/components/LinkEditor.tsx b/packages/design-system/editor-tiptap/src/components/LinkEditor.tsx new file mode 100644 index 0000000..ecac267 --- /dev/null +++ b/packages/design-system/editor-tiptap/src/components/LinkEditor.tsx @@ -0,0 +1,103 @@ +import { Check, Link2, Trash2 } from 'lucide-react'; +import React from 'react'; +import { cn } from '@crux-kit/utils'; +import { ToolbarButton } from './ToolbarButton'; + +export const LinkEditor: React.FC<{ + isActive: boolean; + disabled?: boolean; + getHref: () => string; + onApply: (href: string) => void; + onRemove: () => void; +}> = ({ isActive, disabled = false, getHref, onApply, onRemove }) => { + const [open, setOpen] = React.useState(false); + const [href, setHref] = React.useState(''); + const wrapperRef = React.useRef(null); + const inputRef = React.useRef(null); + + const close = React.useCallback(() => setOpen(false), []); + + const toggle = React.useCallback(() => { + setOpen((prev) => { + const next = !prev; + if (next) setHref(getHref()); + return next; + }); + }, [getHref]); + + React.useEffect(() => { + if (!open) return; + const id = window.setTimeout(() => inputRef.current?.focus(), 0); + + const onPointerDown = (e: MouseEvent) => { + if (!wrapperRef.current?.contains(e.target as Node)) close(); + }; + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') close(); + }; + + document.addEventListener('mousedown', onPointerDown); + document.addEventListener('keydown', onKeyDown); + return () => { + window.clearTimeout(id); + document.removeEventListener('mousedown', onPointerDown); + document.removeEventListener('keydown', onKeyDown); + }; + }, [open, close]); + + const apply = () => { + const trimmed = href.trim(); + if (trimmed) onApply(trimmed); + else onRemove(); + close(); + }; + + const remove = () => { + onRemove(); + close(); + }; + + return ( + + + + + + {open && ( +
+ setHref(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + apply(); + } + }} + className={cn( + 'border-input bg-background text-foreground h-8 flex-1 rounded-md border px-2 text-sm', + 'focus-visible:ring-ring focus-visible:outline-hidden focus-visible:ring-2', + )} + /> + + + + {isActive && ( + + + + )} +
+ )} +
+ ); +}; diff --git a/packages/design-system/editor-tiptap/src/components/Toolbar.tsx b/packages/design-system/editor-tiptap/src/components/Toolbar.tsx new file mode 100644 index 0000000..68bd6c3 --- /dev/null +++ b/packages/design-system/editor-tiptap/src/components/Toolbar.tsx @@ -0,0 +1,285 @@ +import { + AlignCenter, + AlignJustify, + AlignLeft, + AlignRight, + Bold, + Italic, + List, + ListOrdered, + Quote, + RemoveFormatting, + Redo2, + Strikethrough, + Underline, + Undo2, + Unlink, +} from 'lucide-react'; +import React from 'react'; +import { type Editor, useEditorState } from '@tiptap/react'; +import { type BlockType, type ToolbarItem, toolbarLayouts, type EditorPreset } from '../presets'; +import { BlockTypeSelect } from './BlockTypeSelect'; +import { LinkEditor } from './LinkEditor'; +import { ToolbarButton, ToolbarSeparator } from './ToolbarButton'; + +function getBlockType(editor: Editor): BlockType { + if (editor.isActive('heading', { level: 1 })) return 'h1'; + if (editor.isActive('heading', { level: 2 })) return 'h2'; + if (editor.isActive('heading', { level: 3 })) return 'h3'; + return 'paragraph'; +} + +export const Toolbar: React.FC<{ + editor: Editor | null; + preset: EditorPreset; + disabled?: boolean; +}> = ({ editor, preset, disabled = false }) => { + const state = useEditorState({ + editor, + selector: ({ editor: e }) => { + if (!e) return null; + return { + canUndo: e.can().undo(), + canRedo: e.can().redo(), + blockType: getBlockType(e), + isBold: e.isActive('bold'), + isItalic: e.isActive('italic'), + isUnderline: e.isActive('underline'), + isStrike: e.isActive('strike'), + isBulletList: e.isActive('bulletList'), + isOrderedList: e.isActive('orderedList'), + isBlockquote: e.isActive('blockquote'), + alignLeft: e.isActive({ textAlign: 'left' }), + alignCenter: e.isActive({ textAlign: 'center' }), + alignRight: e.isActive({ textAlign: 'right' }), + alignJustify: e.isActive({ textAlign: 'justify' }), + isLink: e.isActive('link'), + }; + }, + }); + + if (!editor || !state) return null; + + const setBlockType = (type: BlockType) => { + const chain = editor.chain().focus(); + if (type === 'paragraph') chain.setParagraph().run(); + else chain.setHeading({ level: Number(type.slice(1)) as 1 | 2 | 3 }).run(); + }; + + const renderItem = (item: ToolbarItem, index: number): React.ReactNode => { + switch (item) { + case '|': + return ; + case 'undo': + return ( + editor.chain().focus().undo().run()} + > + + + ); + case 'redo': + return ( + editor.chain().focus().redo().run()} + > + + + ); + case 'blockType': + return ( + + ); + case 'bold': + return ( + editor.chain().focus().toggleBold().run()} + > + + + ); + case 'italic': + return ( + editor.chain().focus().toggleItalic().run()} + > + + + ); + case 'underline': + return ( + editor.chain().focus().toggleUnderline().run()} + > + + + ); + case 'strike': + return ( + editor.chain().focus().toggleStrike().run()} + > + + + ); + case 'bulletList': + return ( + editor.chain().focus().toggleBulletList().run()} + > + + + ); + case 'orderedList': + return ( + editor.chain().focus().toggleOrderedList().run()} + > + + + ); + case 'blockquote': + return ( + editor.chain().focus().toggleBlockquote().run()} + > + + + ); + case 'alignLeft': + return ( + editor.chain().focus().setTextAlign('left').run()} + > + + + ); + case 'alignCenter': + return ( + editor.chain().focus().setTextAlign('center').run()} + > + + + ); + case 'alignRight': + return ( + editor.chain().focus().setTextAlign('right').run()} + > + + + ); + case 'alignJustify': + return ( + editor.chain().focus().setTextAlign('justify').run()} + > + + + ); + case 'link': + return ( + (editor.getAttributes('link')['href'] as string) ?? ''} + onApply={(href) => + editor.chain().focus().extendMarkRange('link').setLink({ href }).run() + } + onRemove={() => editor.chain().focus().extendMarkRange('link').unsetLink().run()} + /> + ); + case 'unlink': + return ( + editor.chain().focus().extendMarkRange('link').unsetLink().run()} + > + + + ); + case 'clear': + return ( + editor.chain().focus().clearNodes().unsetAllMarks().run()} + > + + + ); + default: + return null; + } + }; + + return ( +
+ {toolbarLayouts[preset].map((item, index) => renderItem(item, index))} +
+ ); +}; diff --git a/packages/design-system/editor-tiptap/src/components/ToolbarButton.tsx b/packages/design-system/editor-tiptap/src/components/ToolbarButton.tsx new file mode 100644 index 0000000..0bca22d --- /dev/null +++ b/packages/design-system/editor-tiptap/src/components/ToolbarButton.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { cn } from '@crux-kit/utils'; + +export type ToolbarButtonProps = { + active?: boolean; + disabled?: boolean; + title: string; + onClick: () => void; + children: React.ReactNode; +}; + +/** + * A single toolbar control. Styled with the shared design-system Tailwind + * tokens so it matches the host app in light and dark mode. Kept dependency-free + * (plain + ); + }, +); + +ToolbarButton.displayName = 'ToolbarButton'; + +export const ToolbarSeparator: React.FC = () => ( + +); diff --git a/packages/design-system/editor-tiptap/src/index.ts b/packages/design-system/editor-tiptap/src/index.ts new file mode 100644 index 0000000..891051f --- /dev/null +++ b/packages/design-system/editor-tiptap/src/index.ts @@ -0,0 +1,9 @@ +export { RichTextEditor, type RichTextEditorProps } from './RichTextEditor'; +export { + type EditorPreset, + type ToolbarItem, + type BlockType, + toolbarLayouts, + buildExtensions, +} from './presets'; +export { type Editor } from '@tiptap/react'; diff --git a/packages/design-system/editor-tiptap/src/presets.ts b/packages/design-system/editor-tiptap/src/presets.ts new file mode 100644 index 0000000..e32b5f7 --- /dev/null +++ b/packages/design-system/editor-tiptap/src/presets.ts @@ -0,0 +1,84 @@ +import { type AnyExtension } from '@tiptap/core'; +import { TextAlign } from '@tiptap/extension-text-align'; +import { Placeholder } from '@tiptap/extensions'; +import { StarterKit } from '@tiptap/starter-kit'; + +export type EditorPreset = 'light'; + +export type ToolbarItem = + | '|' + | 'undo' + | 'redo' + | 'blockType' + | 'bold' + | 'italic' + | 'underline' + | 'strike' + | 'bulletList' + | 'orderedList' + | 'blockquote' + | 'alignLeft' + | 'alignCenter' + | 'alignRight' + | 'alignJustify' + | 'link' + | 'unlink' + | 'clear'; + +export type BlockType = 'paragraph' | 'h1' | 'h2' | 'h3'; + +export const HEADING_LEVELS = [1, 2, 3] as const; + +export function buildExtensions(preset: EditorPreset, placeholder?: string): AnyExtension[] { + switch (preset) { + case 'light': + default: + return [ + StarterKit.configure({ + heading: { levels: [...HEADING_LEVELS] }, + link: { + openOnClick: false, + autolink: true, + defaultProtocol: 'https', + HTMLAttributes: { rel: 'noopener noreferrer nofollow', target: '_blank' }, + }, + codeBlock: false, + code: false, + horizontalRule: false, + }), + TextAlign.configure({ types: ['heading', 'paragraph'] }), + Placeholder.configure({ + placeholder: placeholder ?? '', + showOnlyWhenEditable: true, + }), + ]; + } +} + +export const toolbarLayouts: Record = { + light: [ + 'undo', + 'redo', + '|', + 'blockType', + '|', + 'bold', + 'italic', + 'underline', + 'strike', + '|', + 'bulletList', + 'orderedList', + 'blockquote', + '|', + 'alignLeft', + 'alignCenter', + 'alignRight', + 'alignJustify', + '|', + 'link', + 'unlink', + '|', + 'clear', + ], +}; diff --git a/packages/design-system/editor-tiptap/src/styles.css b/packages/design-system/editor-tiptap/src/styles.css new file mode 100644 index 0000000..c56262b --- /dev/null +++ b/packages/design-system/editor-tiptap/src/styles.css @@ -0,0 +1,103 @@ +/** + * Content styles for @crux-kit/editor-tiptap. + * Plain CSS on purpose: this file is imported directly by consumers and must not + * depend on the host app's Tailwind pipeline. Colors reference the shared + * design-system CSS variables (--foreground, --border, …) so the editor themes + * automatically in light/dark mode. + */ + +.crux-editor__content { + min-height: 12rem; + padding: 0.75rem 0.875rem; + outline: none; + color: var(--foreground); + font-size: 0.875rem; + line-height: 1.6; +} + +.crux-editor__content :first-child { + margin-top: 0; +} + +.crux-editor__content :last-child { + margin-bottom: 0; +} + +.crux-editor__content p { + margin: 0 0 0.75em; +} + +.crux-editor__content h1, +.crux-editor__content h2, +.crux-editor__content h3 { + margin: 1.25em 0 0.5em; + font-weight: 600; + line-height: 1.25; + color: var(--foreground); +} + +.crux-editor__content h1 { + font-size: 1.75em; +} + +.crux-editor__content h2 { + font-size: 1.4em; +} + +.crux-editor__content h3 { + font-size: 1.15em; +} + +.crux-editor__content ul, +.crux-editor__content ol { + margin: 0 0 0.75em; + padding-left: 1.5rem; +} + +.crux-editor__content ul { + list-style: disc; +} + +.crux-editor__content ol { + list-style: decimal; +} + +.crux-editor__content li { + margin: 0.25em 0; +} + +.crux-editor__content li p { + margin: 0; +} + +.crux-editor__content blockquote { + margin: 0 0 0.75em; + padding-left: 1rem; + border-left: 3px solid var(--border); + color: var(--muted-foreground); +} + +.crux-editor__content a { + color: var(--primary); + text-decoration: underline; + text-underline-offset: 2px; + cursor: pointer; +} + +.crux-editor__content strong { + font-weight: 600; +} + +/* Placeholder (from @tiptap/extensions Placeholder) */ +.crux-editor__content p.is-editor-empty:first-child::before { + content: attr(data-placeholder); + float: left; + height: 0; + pointer-events: none; + color: var(--muted-foreground); +} + +/* ProseMirror selection niceties */ +.crux-editor__content .ProseMirror-selectednode { + outline: 2px solid var(--ring); +} diff --git a/packages/design-system/editor-tiptap/tsconfig.json b/packages/design-system/editor-tiptap/tsconfig.json new file mode 100644 index 0000000..07b2146 --- /dev/null +++ b/packages/design-system/editor-tiptap/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2023", "DOM"], + "jsx": "react-jsx", + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "composite": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +}