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
This commit is contained in:
neon
2026-06-30 20:21:25 +00:00
parent cfc48b6218
commit 6f2fe8612e
8 changed files with 1110 additions and 12 deletions
+4
View File
@@ -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",
@@ -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"
>
<textarea
v-model="form.description"
rows="6"
class="w-full resize-y rounded-lg border border-input bg-background px-3 py-2 pr-12 text-sm outline-none focus:ring-1 focus:ring-ring transition-shadow disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="isLocked('description')"
/>
<RichDescriptionEditor v-model="form.description" :disabled="isLocked('description')" />
</MetadataFieldLabel>
<!-- Mobile: sticky Save/Cancel bar -->
@@ -0,0 +1,452 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import {
Bold,
Check,
Eraser,
Eye,
IndentDecrease,
IndentIncrease,
Italic,
Link as LinkIcon,
List,
ListOrdered,
PencilLine,
Quote,
Redo2,
Strikethrough,
Underline,
Undo2,
Unlink,
X,
} from '@lucide/vue'
import Link from '@tiptap/extension-link'
import StarterKit from '@tiptap/starter-kit'
import { EditorContent, useEditor } from '@tiptap/vue-3'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { normalizeDescriptionHtml } from '@/features/book/lib/description-html'
const props = defineProps<{
modelValue: string | null
disabled?: boolean
}>()
const emit = defineEmits<{
'update:modelValue': [value: string | null]
}>()
const linkPanelOpen = ref(false)
const linkUrl = ref('')
const linkError = ref<string | null>(null)
const previewOpen = ref(false)
const editor = useEditor({
content: props.modelValue ?? '',
editable: !props.disabled,
extensions: [
StarterKit.configure({
code: false,
codeBlock: false,
heading: false,
horizontalRule: false,
link: false,
}),
Link.configure({
autolink: false,
defaultProtocol: 'https',
linkOnPaste: false,
openOnClick: false,
protocols: ['http', 'https', 'mailto'],
HTMLAttributes: {
rel: null,
target: null,
},
}),
],
editorProps: {
attributes: {
class:
'min-h-[10rem] w-full outline-none px-3 py-2 text-sm leading-relaxed [&_a]:text-primary [&_a]:underline [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground [&_ol]:list-decimal [&_ol]:pl-5 [&_p]:my-1.5 [&_ul]:list-disc [&_ul]:pl-5',
},
},
onUpdate: ({ editor: instance }) => {
emit('update:modelValue', normalizeDescriptionHtml(instance.getHTML()))
},
})
const canEdit = computed(() => !!editor.value && !props.disabled)
const isBoldActive = computed(() => editor.value?.isActive('bold') ?? false)
const isItalicActive = computed(() => editor.value?.isActive('italic') ?? false)
const isUnderlineActive = computed(() => editor.value?.isActive('underline') ?? false)
const isStrikeActive = computed(() => editor.value?.isActive('strike') ?? false)
const isBulletListActive = computed(() => editor.value?.isActive('bulletList') ?? false)
const isOrderedListActive = computed(() => editor.value?.isActive('orderedList') ?? false)
const isBlockquoteActive = computed(() => editor.value?.isActive('blockquote') ?? false)
const isLinkActive = computed(() => editor.value?.isActive('link') ?? false)
const canUndo = computed(() => canEdit.value && (editor.value?.can().undo() ?? false))
const canRedo = computed(() => canEdit.value && (editor.value?.can().redo() ?? false))
const canIndentList = computed(() => canEdit.value && (editor.value?.can().sinkListItem('listItem') ?? false))
const canOutdentList = computed(() => canEdit.value && (editor.value?.can().liftListItem('listItem') ?? false))
const previewHtml = computed(() => normalizeDescriptionHtml(editor.value?.getHTML() ?? props.modelValue ?? '') ?? '')
const boldButtonClass = computed(() => buttonClass(isBoldActive.value))
const italicButtonClass = computed(() => buttonClass(isItalicActive.value))
const underlineButtonClass = computed(() => buttonClass(isUnderlineActive.value))
const strikeButtonClass = computed(() => buttonClass(isStrikeActive.value))
const bulletListButtonClass = computed(() => buttonClass(isBulletListActive.value))
const orderedListButtonClass = computed(() => buttonClass(isOrderedListActive.value))
const blockquoteButtonClass = computed(() => buttonClass(isBlockquoteActive.value))
const linkButtonClass = computed(() => buttonClass(isLinkActive.value || linkPanelOpen.value))
const previewButtonClass = computed(() => buttonClass(previewOpen.value))
const plainButtonClass = computed(() => buttonClass(false))
watch(
() => props.disabled,
(disabled) => {
editor.value?.setEditable(!disabled)
if (disabled) closeLinkPanel()
},
)
watch(
() => props.modelValue,
(value) => {
const instance = editor.value
if (!instance) return
const current = normalizeDescriptionHtml(instance.getHTML())
const next = normalizeDescriptionHtml(value)
if (current === next) return
instance.commands.setContent(value ?? '', { emitUpdate: false })
},
)
onBeforeUnmount(() => {
editor.value?.destroy()
})
function buttonClass(isActive: boolean) {
return [
'inline-flex size-8 shrink-0 items-center justify-center rounded-md border text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40',
isActive ? 'border-primary bg-primary text-primary-foreground' : 'border-transparent text-muted-foreground hover:bg-muted hover:text-foreground',
]
}
function toggleBold() {
editor.value?.chain().focus().toggleBold().run()
}
function toggleItalic() {
editor.value?.chain().focus().toggleItalic().run()
}
function toggleUnderline() {
editor.value?.chain().focus().toggleUnderline().run()
}
function toggleStrike() {
editor.value?.chain().focus().toggleStrike().run()
}
function toggleBulletList() {
editor.value?.chain().focus().toggleBulletList().run()
}
function toggleOrderedList() {
editor.value?.chain().focus().toggleOrderedList().run()
}
function toggleBlockquote() {
editor.value?.chain().focus().toggleBlockquote().run()
}
function indentList() {
editor.value?.chain().focus().sinkListItem('listItem').run()
}
function outdentList() {
editor.value?.chain().focus().liftListItem('listItem').run()
}
function undo() {
editor.value?.chain().focus().undo().run()
}
function redo() {
editor.value?.chain().focus().redo().run()
}
function clearFormatting() {
editor.value?.chain().focus().unsetAllMarks().clearNodes().run()
closeLinkPanel()
}
function togglePreview() {
previewOpen.value = !previewOpen.value
closeLinkPanel()
}
function toggleLinkPanel() {
if (!canEdit.value) return
if (linkPanelOpen.value) {
closeLinkPanel()
return
}
const href = editor.value?.getAttributes('link')['href']
linkUrl.value = typeof href === 'string' ? href : ''
linkError.value = null
linkPanelOpen.value = true
}
function applyLink() {
const instance = editor.value
if (!instance) return
const href = normalizeLinkUrl(linkUrl.value)
if (!href) {
instance.chain().focus().extendMarkRange('link').unsetLink().run()
closeLinkPanel()
return
}
if (!isAllowedLinkUrl(href)) {
linkError.value = 'Use http, https, or mailto.'
return
}
instance.chain().focus().extendMarkRange('link').setLink({ href }).run()
closeLinkPanel()
}
function removeLink() {
editor.value?.chain().focus().extendMarkRange('link').unsetLink().run()
closeLinkPanel()
}
function closeLinkPanel() {
linkPanelOpen.value = false
linkUrl.value = ''
linkError.value = null
}
function normalizeLinkUrl(raw: string): string {
const value = raw.trim()
if (!value) return ''
if (/^[a-z][a-z\d+.-]*:/i.test(value)) return value
return `https://${value}`
}
function isAllowedLinkUrl(value: string): boolean {
try {
const url = new URL(value)
return url.protocol === 'http:' || url.protocol === 'https:' || url.protocol === 'mailto:'
} catch {
return false
}
}
</script>
<template>
<div class="overflow-hidden rounded-lg border border-input bg-background transition-shadow focus-within:ring-1 focus-within:ring-ring">
<div class="flex min-h-10 flex-wrap items-center gap-1 border-b border-border bg-muted/30 px-1.5 py-1">
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="boldButtonClass" :disabled="!canEdit" aria-label="Bold" @click="toggleBold">
<Bold class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Bold</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="italicButtonClass" :disabled="!canEdit" aria-label="Italic" @click="toggleItalic">
<Italic class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Italic</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="underlineButtonClass" :disabled="!canEdit" aria-label="Underline" @click="toggleUnderline">
<Underline class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Underline</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="strikeButtonClass" :disabled="!canEdit" aria-label="Strikethrough" @click="toggleStrike">
<Strikethrough class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Strikethrough</TooltipContent>
</Tooltip>
<div class="mx-0.5 h-5 w-px bg-border" />
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="bulletListButtonClass" :disabled="!canEdit" aria-label="Bullet list" @click="toggleBulletList">
<List class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Bullet list</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="orderedListButtonClass" :disabled="!canEdit" aria-label="Numbered list" @click="toggleOrderedList">
<ListOrdered class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Numbered list</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="plainButtonClass" :disabled="!canOutdentList" aria-label="Outdent list item" @click="outdentList">
<IndentDecrease class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Outdent</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="plainButtonClass" :disabled="!canIndentList" aria-label="Indent list item" @click="indentList">
<IndentIncrease class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Indent</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="blockquoteButtonClass" :disabled="!canEdit" aria-label="Quote" @click="toggleBlockquote">
<Quote class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Quote</TooltipContent>
</Tooltip>
<div class="mx-0.5 h-5 w-px bg-border" />
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="linkButtonClass" :disabled="!canEdit" aria-label="Edit link" @click="toggleLinkPanel">
<LinkIcon class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Link</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="plainButtonClass" :disabled="!canEdit || !isLinkActive" aria-label="Remove link" @click="removeLink">
<Unlink class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Remove link</TooltipContent>
</Tooltip>
<div class="mx-0.5 h-5 w-px bg-border" />
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="plainButtonClass" :disabled="!canUndo" aria-label="Undo" @click="undo">
<Undo2 class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Undo</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="plainButtonClass" :disabled="!canRedo" aria-label="Redo" @click="redo">
<Redo2 class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Redo</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="plainButtonClass" :disabled="!canEdit" aria-label="Clear formatting" @click="clearFormatting">
<Eraser class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Clear formatting</TooltipContent>
</Tooltip>
<div class="mx-0.5 h-5 w-px bg-border" />
<Tooltip>
<TooltipTrigger as-child>
<button type="button" :class="previewButtonClass" aria-label="Preview description" @click="togglePreview">
<PencilLine v-if="previewOpen" class="size-4" />
<Eye v-else class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>{{ previewOpen ? 'Edit' : 'Preview' }}</TooltipContent>
</Tooltip>
</div>
<div v-if="linkPanelOpen" class="flex flex-wrap items-start gap-2 border-b border-border bg-card px-2 py-2">
<label class="min-w-0 flex-1">
<span class="sr-only">Link URL</span>
<input
v-model="linkUrl"
type="url"
class="h-8 w-full rounded-md border border-input bg-background px-2.5 text-sm outline-none focus:ring-1 focus:ring-ring"
placeholder="https://example.com"
@keydown.enter.prevent="applyLink"
@keydown.esc.prevent="closeLinkPanel"
/>
<span v-if="linkError" class="mt-1 block text-xs text-destructive">{{ linkError }}</span>
</label>
<div class="flex shrink-0 items-center gap-1">
<Tooltip>
<TooltipTrigger as-child>
<button
type="button"
class="inline-flex size-8 items-center justify-center rounded-md border border-input hover:bg-muted"
aria-label="Apply link"
@click="applyLink"
>
<Check class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Apply</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<button
type="button"
class="inline-flex size-8 items-center justify-center rounded-md border border-input hover:bg-muted"
aria-label="Cancel link"
@click="closeLinkPanel"
>
<X class="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>Cancel</TooltipContent>
</Tooltip>
</div>
</div>
<div
v-if="previewOpen"
class="min-h-[10rem] px-3 py-2 text-sm leading-relaxed [&_a]:text-primary [&_a]:underline [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground [&_ol]:list-decimal [&_ol]:pl-5 [&_p]:my-1.5 [&_ul]:list-disc [&_ul]:pl-5"
>
<!-- eslint-disable-next-line vue/no-v-html -- sanitized by sanitizeDescriptionHtml -->
<div v-if="previewHtml" v-html="previewHtml"></div>
<p v-else class="italic text-muted-foreground">No description available.</p>
</div>
<EditorContent v-else-if="editor" :editor="editor" :class="{ 'pointer-events-none opacity-50': disabled }" />
</div>
</template>
@@ -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(() => '<b>Hello</b>')
const _ = html.value
expect(sanitizeMock).toHaveBeenCalledWith('<b>Hello</b>', {
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'],
})
})
@@ -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)
})
}
@@ -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(
'<blockquote><p>Hello <strong>world</strong> <u>underlined</u> <s>removed</s> <a href="https://bookorbit.app">BookOrbit</a></p></blockquote>',
)
expect(html).toBe(
'<blockquote><p>Hello <strong>world</strong> <u>underlined</u> <s>removed</s> <a href="https://bookorbit.app">BookOrbit</a></p></blockquote>',
)
})
it('removes unsupported tags and unsafe attributes', () => {
const html = sanitizeDescriptionHtml('<p onclick="alert(1)">Hello</p><script>alert(1)</script><img src="x">')
expect(html).toBe('<p>Hello</p>')
})
it('normalizes empty editor output to null', () => {
expect(normalizeDescriptionHtml('<p><br></p>')).toBeNull()
expect(normalizeDescriptionHtml('<ul><li></li></ul>')).toBeNull()
})
it('returns sanitized HTML for visible content', () => {
expect(normalizeDescriptionHtml('<p><em>Visible</em></p>')).toBe('<p><em>Visible</em></p>')
})
})
@@ -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(/<br\s*\/?>/gi, '')
.replace(/<[^>]*>/g, '')
.replace(/&nbsp;/gi, ' ')
.trim()
return text.length > 0
}
+592
View File
@@ -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