From 6f2fe8612e495c5350c48f22bc0cb83e48afe4c7 Mon Sep 17 00:00:00 2001
From: neon <18559658+neonsolstice@users.noreply.github.com>
Date: Tue, 30 Jun 2026 20:21:25 +0000
Subject: [PATCH] feat(books): add rich text description formatting bar
Replace the plain textarea for book descriptions with a Tiptap-based rich text editor in the metadata edit tab. Include HTML sanitization utilities to ensure safe rendering of user-provided descriptions.
Closes #460
---
client/package.json | 4 +
.../detail/tabs/EditMetadataTab.vue | 8 +-
.../detail/tabs/RichDescriptionEditor.vue | 452 +++++++++++++
.../book/composables/useSafeHtml.test.ts | 5 +-
.../features/book/composables/useSafeHtml.ts | 6 +-
.../book/lib/description-html.test.ts | 29 +
.../src/features/book/lib/description-html.ts | 26 +
pnpm-lock.yaml | 592 ++++++++++++++++++
8 files changed, 1110 insertions(+), 12 deletions(-)
create mode 100644 client/src/features/book/components/detail/tabs/RichDescriptionEditor.vue
create mode 100644 client/src/features/book/lib/description-html.test.ts
create mode 100644 client/src/features/book/lib/description-html.ts
diff --git a/client/package.json b/client/package.json
index 5988815d8..bbd8e7157 100644
--- a/client/package.json
+++ b/client/package.json
@@ -43,6 +43,10 @@
"@tailwindcss/vite": "^4.3.1",
"@tanstack/vue-table": "^8.21.3",
"@tanstack/vue-virtual": "^3.13.29",
+ "@tiptap/extension-link": "^3.27.1",
+ "@tiptap/pm": "^3.27.1",
+ "@tiptap/starter-kit": "^3.27.1",
+ "@tiptap/vue-3": "^3.27.1",
"@types/howler": "^2.2.13",
"@vueuse/core": "^14.3.0",
"canvas-confetti": "^1.9.4",
diff --git a/client/src/features/book/components/detail/tabs/EditMetadataTab.vue b/client/src/features/book/components/detail/tabs/EditMetadataTab.vue
index 553ccc1c1..62dd2bf6c 100644
--- a/client/src/features/book/components/detail/tabs/EditMetadataTab.vue
+++ b/client/src/features/book/components/detail/tabs/EditMetadataTab.vue
@@ -18,6 +18,7 @@ import ChipInput from '@/components/ui/ChipInput.vue'
import CoverEditorPanel from './CoverEditorPanel.vue'
import MetadataSearchDrawer from './MetadataSearchDrawer.vue'
import MetadataFieldLabel from './MetadataFieldLabel.vue'
+import RichDescriptionEditor from './RichDescriptionEditor.vue'
import SeriesMembershipEditor from './SeriesMembershipEditor.vue'
import WriteAndRenameResultPanel from '../WriteAndRenameResultPanel.vue'
import type { MetadataPatch } from '../../../composables/useMetadataDiff'
@@ -1422,12 +1423,7 @@ function handleCoverChanged(source: 'extracted' | 'custom' | null) {
multiline
@toggle="handleLockToggle"
>
-
+
diff --git a/client/src/features/book/components/detail/tabs/RichDescriptionEditor.vue b/client/src/features/book/components/detail/tabs/RichDescriptionEditor.vue
new file mode 100644
index 000000000..8c0f53ff8
--- /dev/null
+++ b/client/src/features/book/components/detail/tabs/RichDescriptionEditor.vue
@@ -0,0 +1,452 @@
+
+
+
+
+
diff --git a/client/src/features/book/composables/useSafeHtml.test.ts b/client/src/features/book/composables/useSafeHtml.test.ts
index e0abbe50e..375e530f5 100644
--- a/client/src/features/book/composables/useSafeHtml.test.ts
+++ b/client/src/features/book/composables/useSafeHtml.test.ts
@@ -4,7 +4,7 @@ import { useSafeHtml } from './useSafeHtml'
vi.mock('dompurify', () => ({
default: {
- sanitize: vi.fn<(input: string, config: { ALLOWED_TAGS: string[] }) => string>((input: string, config: { ALLOWED_TAGS: string[] }) => {
+ sanitize: vi.fn<(input: string, config: { ALLOWED_ATTR: string[]; ALLOWED_TAGS: string[] }) => string>((input: string, config) => {
if (!config?.ALLOWED_TAGS) return ''
// Simulate DOMPurify by stripping disallowed tags and dangerous attributes
return input
@@ -42,7 +42,8 @@ describe('useSafeHtml', () => {
const html = useSafeHtml(() => 'Hello')
const _ = html.value
expect(sanitizeMock).toHaveBeenCalledWith('Hello', {
- ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br', 'ul', 'ol', 'li', 'a'],
+ ALLOWED_ATTR: ['href'],
+ ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 's', 'u', 'p', 'br', 'ul', 'ol', 'li', 'a', 'blockquote'],
})
})
diff --git a/client/src/features/book/composables/useSafeHtml.ts b/client/src/features/book/composables/useSafeHtml.ts
index ddfdcd676..bee2ae1a7 100644
--- a/client/src/features/book/composables/useSafeHtml.ts
+++ b/client/src/features/book/composables/useSafeHtml.ts
@@ -1,12 +1,10 @@
import { computed } from 'vue'
-import DOMPurify from 'dompurify'
-
-const SAFE_TAGS = ['b', 'i', 'em', 'strong', 'p', 'br', 'ul', 'ol', 'li', 'a']
+import { sanitizeDescriptionHtml } from '@/features/book/lib/description-html'
export function useSafeHtml(rawHtml: () => string | null | undefined) {
return computed(() => {
const content = rawHtml()
if (!content) return ''
- return DOMPurify.sanitize(content, { ALLOWED_TAGS: SAFE_TAGS })
+ return sanitizeDescriptionHtml(content)
})
}
diff --git a/client/src/features/book/lib/description-html.test.ts b/client/src/features/book/lib/description-html.test.ts
new file mode 100644
index 000000000..f0858cb96
--- /dev/null
+++ b/client/src/features/book/lib/description-html.test.ts
@@ -0,0 +1,29 @@
+import { describe, expect, it } from 'vitest'
+import { normalizeDescriptionHtml, sanitizeDescriptionHtml } from './description-html'
+
+describe('description-html', () => {
+ it('keeps supported description tags and link hrefs', () => {
+ const html = sanitizeDescriptionHtml(
+ '
Hello world underlined removed BookOrbit
',
+ )
+
+ expect(html).toBe(
+ 'Hello world underlined removed BookOrbit
',
+ )
+ })
+
+ it('removes unsupported tags and unsafe attributes', () => {
+ const html = sanitizeDescriptionHtml('Hello
')
+
+ expect(html).toBe('Hello
')
+ })
+
+ it('normalizes empty editor output to null', () => {
+ expect(normalizeDescriptionHtml('
')).toBeNull()
+ expect(normalizeDescriptionHtml('')).toBeNull()
+ })
+
+ it('returns sanitized HTML for visible content', () => {
+ expect(normalizeDescriptionHtml('Visible
')).toBe('Visible
')
+ })
+})
diff --git a/client/src/features/book/lib/description-html.ts b/client/src/features/book/lib/description-html.ts
new file mode 100644
index 000000000..c07721f49
--- /dev/null
+++ b/client/src/features/book/lib/description-html.ts
@@ -0,0 +1,26 @@
+import DOMPurify from 'dompurify'
+
+export const DESCRIPTION_HTML_TAGS = ['b', 'i', 'em', 'strong', 's', 'u', 'p', 'br', 'ul', 'ol', 'li', 'a', 'blockquote'] as const
+export const DESCRIPTION_HTML_ATTRS = ['href'] as const
+
+export function sanitizeDescriptionHtml(content: string): string {
+ return DOMPurify.sanitize(content, {
+ ALLOWED_TAGS: [...DESCRIPTION_HTML_TAGS],
+ ALLOWED_ATTR: [...DESCRIPTION_HTML_ATTRS],
+ })
+}
+
+export function normalizeDescriptionHtml(content: string | null | undefined): string | null {
+ const sanitized = sanitizeDescriptionHtml(content ?? '')
+ return hasVisibleDescriptionText(sanitized) ? sanitized : null
+}
+
+function hasVisibleDescriptionText(content: string): boolean {
+ const text = content
+ .replace(/
/gi, '')
+ .replace(/<[^>]*>/g, '')
+ .replace(/ /gi, ' ')
+ .trim()
+
+ return text.length > 0
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 02e39a38d..01629f690 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -74,6 +74,18 @@ importers:
"@tanstack/vue-virtual":
specifier: ^3.13.29
version: 3.13.29(vue@3.5.38(typescript@6.0.3))
+ "@tiptap/extension-link":
+ specifier: ^3.27.1
+ version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+ "@tiptap/pm":
+ specifier: ^3.27.1
+ version: 3.27.1
+ "@tiptap/starter-kit":
+ specifier: ^3.27.1
+ version: 3.27.1
+ "@tiptap/vue-3":
+ specifier: ^3.27.1
+ version: 3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(vue@3.5.38(typescript@6.0.3))
"@types/howler":
specifier: ^2.2.13
version: 2.2.13
@@ -5541,6 +5553,237 @@ packages:
peerDependencies:
vue: ^2.7.0 || ^3.0.0
+ "@tiptap/core@3.27.1":
+ resolution:
+ {
+ integrity: sha512-rV6Qn4wmC6BxfF+4mu6bqGWj9vA4oXXhsrpXaJL2uhjxeHAGofjwcHof2X84VYzeyXgdlsGmqKie4TAppVXZUQ==,
+ }
+ peerDependencies:
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/extension-blockquote@3.27.1":
+ resolution:
+ {
+ integrity: sha512-VMF7xJx6qEGiX6DTKNiL31NLqypOcd/4sNjFSe8rb41PwejBJh/nOqVIbBvWkiT6NMGFLxMhj7zJ8/zPo1hXeg==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+
+ "@tiptap/extension-bold@3.27.1":
+ resolution:
+ {
+ integrity: sha512-TlC5bsS+pqETTrlz4CZz9RO/cKBYtELGIxwtKeivUn3eNfnOxQbbu4WDsiwIfzRFyd0OMnKl6BPM2KnYEehoEQ==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+
+ "@tiptap/extension-bubble-menu@3.27.1":
+ resolution:
+ {
+ integrity: sha512-j/j8Qp9Z5nViade2m7zjrO/CYH/Ca80Qj7aqo0eUaei6FZQ5izlF9o4XQU5EFMAutV6mwynsPUp8FVo5sCuYfw==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/extension-bullet-list@3.27.1":
+ resolution:
+ {
+ integrity: sha512-faCUHnRP47o9Zh9VZZX6EX/569udw9Vopm2PgEKPWuKLE2qaS5WBuUVU0iItdJmKUqaWiOZkpoW4jvnDmj0dfg==,
+ }
+ peerDependencies:
+ "@tiptap/extension-list": 3.27.1
+
+ "@tiptap/extension-code-block@3.27.1":
+ resolution:
+ {
+ integrity: sha512-pHlzmZx2OlHfyQ0yRlT5UL4mGokz947DthZuYefN1OleVqOkHpWBG+2JQwqoNq6bmzMne92zbH32rhcJUEYSjA==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/extension-code@3.27.1":
+ resolution:
+ {
+ integrity: sha512-epOUpFfEmBzjvnqvjv2qHX7NAuLo5dlOGV690lWu+sAYMjibuJBeVvAiKPyFCfRCCTUxdbDB3jbaOA1yEcEJ7w==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+
+ "@tiptap/extension-document@3.27.1":
+ resolution:
+ {
+ integrity: sha512-8FbBTkfnRP4iVaoj+2h3iWa+H0eGDD3yTyVCwrmue/sQTkqUNUoSuAZa3GDG4Sd41xdPwTJxl9nUWGgM1qDCnw==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+
+ "@tiptap/extension-dropcursor@3.27.1":
+ resolution:
+ {
+ integrity: sha512-blFf9x9RG0Qr7P3FoAH/033ffa+mMLZn34trVs8Vi0Ppk6FmJAg5HpYFOtmYoeREdNDJ5rHJKV7SoACbOHgskQ==,
+ }
+ peerDependencies:
+ "@tiptap/extensions": 3.27.1
+
+ "@tiptap/extension-floating-menu@3.27.1":
+ resolution:
+ {
+ integrity: sha512-BmJF1VqB7dSJkgAalrpVFj88WLhxKjcWPuWHOqf2ITrUU2832BhKLXKmxjWUy1gqV8PfNNVWtGfIERy7I0y0+Q==,
+ }
+ peerDependencies:
+ "@floating-ui/dom": ^1.0.0
+ "@tiptap/core": 3.27.1
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/extension-gapcursor@3.27.1":
+ resolution:
+ {
+ integrity: sha512-QoezN0wdvXIwLQ4ee2ccWDaX3RG0lzgQpIMpMz55oPDhpUVax1+19ApsS53LkcktpS4EbnPL4xO4DaJk0Vp7PQ==,
+ }
+ peerDependencies:
+ "@tiptap/extensions": 3.27.1
+
+ "@tiptap/extension-hard-break@3.27.1":
+ resolution:
+ {
+ integrity: sha512-iv/m9hzl6jfSj9Q8UEjAxONvCoUDaP7M9SRCPx3PaLNxA230TTD6RE0Ye4zFJ8ze7ZVoJJMAqg9Qpq1iYg2JOQ==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+
+ "@tiptap/extension-heading@3.27.1":
+ resolution:
+ {
+ integrity: sha512-SrC4l1kEIyv9ZXFaI/8LQqU2MyMmjczw7XXsWUQOTN4YXv0JyVgMNR3cI/wz0d2xsTfBdZ1N85Tdng+Ga1t0Sg==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+
+ "@tiptap/extension-horizontal-rule@3.27.1":
+ resolution:
+ {
+ integrity: sha512-QlKE7qn5qMnIGVGhXQlvYedvLtNJ9z0dmit5w8vPb8tKzW4Spk6M7N2kruprrDA8GBwHfeR5wmF+njfUm34qxg==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/extension-italic@3.27.1":
+ resolution:
+ {
+ integrity: sha512-jGGeyn9uRUnNjSTHpbqhiGsp6KaYTSbV09jDXPJI9cDwfV9hpugLvpaCZd0BMBbhU1B1W6kOfX0BE15qX/HQfA==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+
+ "@tiptap/extension-link@3.27.1":
+ resolution:
+ {
+ integrity: sha512-/2jBfsxBZUDGJmpZifqRQPz7f1E5qpS1BckTZ39TADzUJX+feKy7RJ3DtQ02+8y6SSMzvP9loGVjrk6zEMTk4g==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/extension-list-item@3.27.1":
+ resolution:
+ {
+ integrity: sha512-zwRl01ETfCkWUvtvK5fw9bXtAajMPkvlkE3Cq6JvH3LF7XXJwDtNj5Tj7exacMpCaSZmlNc43vFb2rAYnrnwMA==,
+ }
+ peerDependencies:
+ "@tiptap/extension-list": 3.27.1
+
+ "@tiptap/extension-list-keymap@3.27.1":
+ resolution:
+ {
+ integrity: sha512-OIMZNlzPSO8WRd4ic73Fxckzl4N1tesjjLL2XApaNA/uMpO0LoF6WSRPAWv+Z24Wp92ARRJAnRP7iZoI5+Jxig==,
+ }
+ peerDependencies:
+ "@tiptap/extension-list": 3.27.1
+
+ "@tiptap/extension-list@3.27.1":
+ resolution:
+ {
+ integrity: sha512-c2Upru7lj0/ZV/Ibww6cNz6sUS8m6Dp/9uygFhYcZOd3X8M0xBIEk42c6m6SQehkPziVA8QOgNJz7sMqsbz1OQ==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/extension-ordered-list@3.27.1":
+ resolution:
+ {
+ integrity: sha512-GYrKqD//9nHJ2r80uXqbDMzRnFpGzbaEQRTSGaO/SH7DvXWFMow8evkOdjQ7PCQO07jNjJo75+A85Jwu3Ov3AA==,
+ }
+ peerDependencies:
+ "@tiptap/extension-list": 3.27.1
+
+ "@tiptap/extension-paragraph@3.27.1":
+ resolution:
+ {
+ integrity: sha512-7K7eo1gruOgAsnbK+GCV23AUVUI0cL1bTig8HaPneoFMVbig7vddk8jNLKBWO8TXVbG7TuHdnDN4F98vdtwh5Q==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+
+ "@tiptap/extension-strike@3.27.1":
+ resolution:
+ {
+ integrity: sha512-Y3DW1jlSlCNCyMGHP3+3qBNNPS83wuFz4RTYGjZtvRRTCRh7apZme9XRWMq1rN5mJ2Cr7fKocA2/5Bs13KgN6Q==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+
+ "@tiptap/extension-text@3.27.1":
+ resolution:
+ {
+ integrity: sha512-6ZwaZwSrDh+KFFv6V1J79oO37yPs7y1bFxvk1/9Ih2rn3Xr5AWz+eMS+n8RpH3djBVVAQpdIAeYQgcn+VCSsTg==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+
+ "@tiptap/extension-underline@3.27.1":
+ resolution:
+ {
+ integrity: sha512-N889J4nXN/TPfVt8uF9N1A0SY82E90zwc1y26lqOcw6KWNLmQrlhMh/9OD4ikLDbekmFpOBq/UicpHf/6S8hbQ==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+
+ "@tiptap/extensions@3.27.1":
+ resolution:
+ {
+ integrity: sha512-1Tdx9faw8k0/83V6X+xCDVhV8yElGt95JxeW3YMkKQJI56QdlPz0xOdJPlMiSGJKinPyVier+x9LJD/YZUZIaw==,
+ }
+ peerDependencies:
+ "@tiptap/core": 3.27.1
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/pm@3.27.1":
+ resolution:
+ {
+ integrity: sha512-Ffjx+vimmBU7zH/KrpXzJid3+pziCe/VL2aexSTP63cyQwKQ65LkFkCKaIsSpFdQQuakVZBGWjCA5RoBV852pw==,
+ }
+
+ "@tiptap/starter-kit@3.27.1":
+ resolution:
+ {
+ integrity: sha512-vfxRsqW8rCc0k4pzo0ilU3wobVi2wqVj88VZI2SlgZlNnUAkrDGDIAph7CTa9k9fshV+O1ivpEgPC5yC046jow==,
+ }
+
+ "@tiptap/vue-3@3.27.1":
+ resolution:
+ {
+ integrity: sha512-o5GB6hfUnyf9sCB306rHWmaIYRL+02ROX657EkuY8tEWKHMTuMjHWl2AqHMP47wz0W9DaMOJLvcPpYdAEKq3Mw==,
+ }
+ peerDependencies:
+ "@floating-ui/dom": ^1.0.0
+ "@tiptap/core": 3.27.1
+ "@tiptap/pm": 3.27.1
+ vue: ^3.0.0
+
"@tokenizer/inflate@0.4.1":
resolution:
{
@@ -10455,6 +10698,12 @@ packages:
integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==,
}
+ linkifyjs@4.3.3:
+ resolution:
+ {
+ integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==,
+ }
+
lint-staged@17.0.8:
resolution:
{
@@ -11323,6 +11572,12 @@ packages:
}
engines: { node: ">=10" }
+ orderedmap@2.1.1:
+ resolution:
+ {
+ integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==,
+ }
+
own-keys@1.0.1:
resolution:
{
@@ -11960,6 +12215,84 @@ packages:
}
engines: { node: ">= 0.6.0" }
+ prosemirror-changeset@2.4.1:
+ resolution:
+ {
+ integrity: sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==,
+ }
+
+ 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.1:
+ resolution:
+ {
+ integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==,
+ }
+
+ 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-model@1.25.9:
+ resolution:
+ {
+ integrity: sha512-pRTklkDDMMRopyoAcrr9wV/8g/RYgrLHBuJAb5hlEuYZRdm5yqmPjWId83fpBwPpSFqEdja0H7Dfd7z1X/npcA==,
+ }
+
+ 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-transform@1.12.0:
+ resolution:
+ {
+ integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==,
+ }
+
+ prosemirror-view@1.41.9:
+ resolution:
+ {
+ integrity: sha512-clTunTX+eaLbr87L1V1QPheRlEQJyTlL3gXe9x3jQIk3rL0RVWxviDGz8tFaydwIVm+hKhYCyr+R/zBtWr9s6A==,
+ }
+
proto-list@1.2.4:
resolution:
{
@@ -12302,6 +12635,12 @@ packages:
engines: { node: ">=18.0.0", npm: ">=8.0.0" }
hasBin: true
+ rope-sequence@1.3.4:
+ resolution:
+ {
+ integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==,
+ }
+
run-applescript@7.1.0:
resolution:
{
@@ -14159,6 +14498,12 @@ packages:
typescript:
optional: true
+ w3c-keyname@2.2.8:
+ resolution:
+ {
+ integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==,
+ }
+
w3c-xmlserializer@5.0.0:
resolution:
{
@@ -17778,6 +18123,171 @@ snapshots:
"@tanstack/virtual-core": 3.17.1
vue: 3.5.38(typescript@6.0.3)
+ "@tiptap/core@3.27.1(@tiptap/pm@3.27.1)":
+ dependencies:
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/extension-blockquote@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-bold@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-bubble-menu@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)":
+ dependencies:
+ "@floating-ui/dom": 1.7.6
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+ "@tiptap/pm": 3.27.1
+ optional: true
+
+ "@tiptap/extension-bullet-list@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/extension-list": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-code-block@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/extension-code@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-document@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-dropcursor@3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/extensions": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-floating-menu@3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)":
+ dependencies:
+ "@floating-ui/dom": 1.7.6
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+ "@tiptap/pm": 3.27.1
+ optional: true
+
+ "@tiptap/extension-gapcursor@3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/extensions": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-hard-break@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-heading@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-horizontal-rule@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/extension-italic@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-link@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+ "@tiptap/pm": 3.27.1
+ linkifyjs: 4.3.3
+
+ "@tiptap/extension-list-item@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/extension-list": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-list-keymap@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/extension-list": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/extension-ordered-list@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/extension-list": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-paragraph@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-strike@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-text@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+
+ "@tiptap/extension-underline@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+
+ "@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/pm@3.27.1":
+ 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.9
+ 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
+
+ "@tiptap/starter-kit@3.27.1":
+ dependencies:
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+ "@tiptap/extension-blockquote": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
+ "@tiptap/extension-bold": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
+ "@tiptap/extension-bullet-list": 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))
+ "@tiptap/extension-code": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
+ "@tiptap/extension-code-block": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+ "@tiptap/extension-document": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
+ "@tiptap/extension-dropcursor": 3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))
+ "@tiptap/extension-gapcursor": 3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))
+ "@tiptap/extension-hard-break": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
+ "@tiptap/extension-heading": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
+ "@tiptap/extension-horizontal-rule": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+ "@tiptap/extension-italic": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
+ "@tiptap/extension-link": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+ "@tiptap/extension-list": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+ "@tiptap/extension-list-item": 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))
+ "@tiptap/extension-list-keymap": 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))
+ "@tiptap/extension-ordered-list": 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))
+ "@tiptap/extension-paragraph": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
+ "@tiptap/extension-strike": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
+ "@tiptap/extension-text": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
+ "@tiptap/extension-underline": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
+ "@tiptap/extensions": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+ "@tiptap/pm": 3.27.1
+
+ "@tiptap/vue-3@3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(vue@3.5.38(typescript@6.0.3))":
+ dependencies:
+ "@floating-ui/dom": 1.7.6
+ "@tiptap/core": 3.27.1(@tiptap/pm@3.27.1)
+ "@tiptap/pm": 3.27.1
+ vue: 3.5.38(typescript@6.0.3)
+ optionalDependencies:
+ "@tiptap/extension-bubble-menu": 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+ "@tiptap/extension-floating-menu": 3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
+
"@tokenizer/inflate@0.4.1":
dependencies:
debug: 4.4.3
@@ -20844,6 +21354,8 @@ snapshots:
lines-and-columns@1.2.4: {}
+ linkifyjs@4.3.3: {}
+
lint-staged@17.0.8:
dependencies:
listr2: 10.2.1
@@ -21256,6 +21768,8 @@ snapshots:
strip-ansi: 6.0.1
wcwidth: 1.0.1
+ orderedmap@2.1.1: {}
+
own-keys@1.0.1:
dependencies:
get-intrinsic: 1.3.0
@@ -21608,6 +22122,80 @@ snapshots:
process@0.11.10: {}
+ prosemirror-changeset@2.4.1:
+ dependencies:
+ prosemirror-transform: 1.12.0
+
+ prosemirror-commands@1.7.1:
+ dependencies:
+ prosemirror-model: 1.25.9
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.12.0
+
+ prosemirror-dropcursor@1.8.2:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.12.0
+ prosemirror-view: 1.41.9
+
+ prosemirror-gapcursor@1.4.1:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.9
+ prosemirror-state: 1.4.4
+ prosemirror-view: 1.41.9
+
+ prosemirror-history@1.5.0:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.12.0
+ prosemirror-view: 1.41.9
+ rope-sequence: 1.3.4
+
+ prosemirror-inputrules@1.5.1:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.12.0
+
+ prosemirror-keymap@1.2.3:
+ dependencies:
+ prosemirror-state: 1.4.4
+ w3c-keyname: 2.2.8
+
+ prosemirror-model@1.25.9:
+ dependencies:
+ orderedmap: 2.1.1
+
+ prosemirror-schema-list@1.5.1:
+ dependencies:
+ prosemirror-model: 1.25.9
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.12.0
+
+ prosemirror-state@1.4.4:
+ dependencies:
+ prosemirror-model: 1.25.9
+ prosemirror-transform: 1.12.0
+ prosemirror-view: 1.41.9
+
+ prosemirror-tables@1.8.5:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.9
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.12.0
+ prosemirror-view: 1.41.9
+
+ prosemirror-transform@1.12.0:
+ dependencies:
+ prosemirror-model: 1.25.9
+
+ prosemirror-view@1.41.9:
+ dependencies:
+ prosemirror-model: 1.25.9
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.12.0
+
proto-list@1.2.4: {}
pump@3.0.4:
@@ -21868,6 +22456,8 @@ snapshots:
"@rollup/rollup-win32-x64-msvc": 4.62.2
fsevents: 2.3.3
+ rope-sequence@1.3.4: {}
+
run-applescript@7.1.0: {}
run-parallel@1.2.0:
@@ -23089,6 +23679,8 @@ snapshots:
optionalDependencies:
typescript: 6.0.3
+ w3c-keyname@2.2.8: {}
+
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0