feat(@vben/plugins): add tiptap rich text editor

This commit is contained in:
xingyu4j
2026-03-30 19:36:29 +08:00
committed by allen
parent 2930dcd78e
commit 5eca357a5c
20 changed files with 1861 additions and 4 deletions

View File

@@ -1,4 +1,7 @@
export {
TextAlignCenter as AlignCenter,
TextAlignStart as AlignLeft,
TextAlignEnd as AlignRight,
ArrowDown,
ArrowLeft,
ArrowLeftToLine,
@@ -7,6 +10,7 @@ export {
ArrowUp,
ArrowUpToLine,
Bell,
Bold,
BookOpenText,
Check,
ChevronDown,
@@ -22,6 +26,7 @@ export {
Copy,
CornerDownLeft,
Ellipsis,
Eraser,
Expand,
ExternalLink,
Eye,
@@ -32,12 +37,20 @@ export {
Grid,
Grip,
GripVertical,
Heading1,
Heading2,
Highlighter,
Menu as IconDefault,
ImagePlus,
Inbox,
Info,
InspectionPanel,
Italic,
Languages,
LayoutGrid,
Link2,
List,
ListOrdered,
LoaderCircle,
LockKeyhole,
LogOut,
@@ -46,15 +59,19 @@ export {
ArrowRightFromLine as MdiMenuClose,
ArrowLeftFromLine as MdiMenuOpen,
Menu,
MessageSquareCode,
Minimize,
Minimize2,
MoonStar,
Paintbrush,
Palette,
PanelLeft,
PanelRight,
Pin,
PinOff,
Plus,
Redo2,
RemoveFormatting,
RotateCw,
Search,
SearchX,
@@ -62,10 +79,16 @@ export {
Shrink,
Square,
SquareCheckBig,
SquareCode,
SquareMinus,
Strikethrough,
Sun,
SunMoon,
SwatchBook,
TextQuote,
Underline,
Undo2,
Unlink2,
UserRoundPen,
X,
} from 'lucide-vue-next';

View File

@@ -22,6 +22,10 @@
"types": "./src/echarts/index.ts",
"default": "./src/echarts/index.ts"
},
"./tiptap": {
"types": "./src/tiptap/index.ts",
"default": "./src/tiptap/index.ts"
},
"./vxe-table": {
"types": "./src/vxe-table/index.ts",
"default": "./src/vxe-table/index.ts"
@@ -32,8 +36,20 @@
}
},
"dependencies": {
"@tiptap/core": "catalog:",
"@tiptap/extension-doc": "catalog:",
"@tiptap/extension-highlight": "catalog:",
"@tiptap/extension-image": "catalog:",
"@tiptap/extension-link": "catalog:",
"@tiptap/extension-placeholder": "catalog:",
"@tiptap/extension-text-align": "catalog:",
"@tiptap/extension-text-style": "catalog:",
"@tiptap/extension-underline": "catalog:",
"@tiptap/starter-kit": "catalog:",
"@tiptap/vue-3": "catalog:",
"@vben-core/design": "workspace:*",
"@vben-core/form-ui": "workspace:*",
"@vben-core/popup-ui": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/shared": "workspace:*",
"@vben/hooks": "workspace:*",

View File

@@ -0,0 +1,55 @@
import type { Extensions } from '@tiptap/vue-3';
import type { VbenTiptapExtensionOptions } from './types';
import { $t } from '@vben/locales';
import Document from '@tiptap/extension-doc';
import Highlight from '@tiptap/extension-highlight';
import Image from '@tiptap/extension-image';
import Link from '@tiptap/extension-link';
import Placeholder from '@tiptap/extension-placeholder';
import TextAlign from '@tiptap/extension-text-align';
import { Color, TextStyle } from '@tiptap/extension-text-style';
import Underline from '@tiptap/extension-underline';
import StarterKit from '@tiptap/starter-kit';
export function createDefaultTiptapExtensions(
options: VbenTiptapExtensionOptions = {},
): Extensions {
return [
Document,
StarterKit.configure({
heading: {
levels: [1, 2, 3, 4],
},
}),
Underline,
TextAlign.configure({
types: ['heading', 'paragraph'],
}),
TextStyle,
Color.configure({
types: ['textStyle'],
}),
Highlight.configure({
multicolor: true,
}),
Link.configure({
autolink: true,
defaultProtocol: 'https',
enableClickSelection: true,
openOnClick: false,
protocols: ['mailto', { optionalSlashes: true, scheme: 'tel' }],
}),
Image.configure({
allowBase64: true,
HTMLAttributes: {
class: 'vben-tiptap__image',
},
}),
Placeholder.configure({
placeholder: options.placeholder ?? $t('ui.tiptap.placeholder'),
}),
];
}

View File

@@ -0,0 +1,4 @@
export { default as VbenTiptapPreview } from './preview.vue';
export { default as VbenTiptap } from './tiptap.vue';
export * from './types';

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import type { TipTapPreviewProps } from './types';
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import './style.css';
const props = withDefaults(defineProps<TipTapPreviewProps>(), {
content: '',
minHeight: 160,
});
const contentMinHeight = computed(() =>
typeof props.minHeight === 'number'
? `${props.minHeight}px`
: props.minHeight,
);
const previewClass = computed(() =>
cn(
'vben-tiptap-content bg-transparent p-0 leading-7 text-foreground',
props.class,
),
);
</script>
<template>
<!-- eslint-disable vue/no-v-html -->
<div
:class="previewClass"
:style="{ minHeight: contentMinHeight }"
v-html="content"
></div>
</template>

View File

@@ -0,0 +1,56 @@
@reference "@vben/tailwind-config/theme";
.vben-tiptap-content > * + * {
@apply mt-3;
}
.vben-tiptap-content h1 {
@apply text-2xl font-bold leading-[1.4];
}
.vben-tiptap-content h2 {
@apply text-xl font-bold leading-[1.45];
}
.vben-tiptap-content h3 {
@apply text-lg font-semibold leading-[1.5];
}
.vben-tiptap-content h4 {
@apply text-base font-semibold leading-[1.55];
}
.vben-tiptap-content ul {
@apply list-disc pl-6;
}
.vben-tiptap-content ol {
@apply list-decimal pl-6;
}
.vben-tiptap-content blockquote {
@apply border-l-4 border-primary pl-4 text-muted-foreground;
}
.vben-tiptap-content a {
@apply text-primary underline decoration-1 underline-offset-[3px];
}
.vben-tiptap-content code {
@apply rounded-[0.45rem] border border-border bg-secondary px-[0.35rem] py-[0.15rem] text-[0.9em] text-primary;
}
.vben-tiptap-content pre {
@apply overflow-x-auto rounded-[0.9rem] border border-border bg-popover p-4 text-popover-foreground;
}
.vben-tiptap-content pre code {
@apply border-none bg-transparent p-0 text-inherit;
}
.vben-tiptap-content img,
.vben-tiptap-content .vben-tiptap__image {
@apply my-4 block h-auto rounded-2xl border border-border;
max-width: min(100%, 640px);
}

View File

@@ -0,0 +1,285 @@
<script setup lang="ts">
import type {
TipTapProps,
ToolbarAction,
VbenTiptapChangeEvent,
} from './types';
import { computed, onBeforeUnmount, watch } from 'vue';
import { Check, ChevronDown, Eye } from '@vben/icons';
import { $t } from '@vben/locales';
import { useVbenModal } from '@vben-core/popup-ui';
import { VbenIconButton, VbenPopover } from '@vben-core/shadcn-ui';
import { cn } from '@vben-core/shared/utils';
import { EditorContent, useEditor } from '@tiptap/vue-3';
import { createDefaultTiptapExtensions } from './extensions';
import Preview from './preview.vue';
import { createToolbarGroups } from './toolbar';
import { useTiptapToolbar } from './use-tiptap-toolbar';
import './style.css';
const props = withDefaults(defineProps<TipTapProps>(), {
editable: true,
extensions: undefined,
minHeight: 240,
placeholder: $t('ui.tiptap.placeholder'),
previewable: true,
toolbar: true,
});
const emit = defineEmits<{
change: [payload: VbenTiptapChangeEvent];
}>();
const modelValue = defineModel<string>({ default: '' });
const contentMinHeight = computed(() =>
typeof props.minHeight === 'number'
? `${props.minHeight}px`
: props.minHeight,
);
const tiptapContentClass = cn(
'vben-tiptap-content vben-tiptap__content',
'min-h-(--vben-tiptap-min-height) leading-7 text-foreground outline-none',
);
const editor = useEditor({
content: modelValue.value,
editable: props.editable,
editorProps: {
attributes: {
class: tiptapContentClass,
},
},
extensions:
props.extensions ??
createDefaultTiptapExtensions({
placeholder: props.placeholder,
}),
onUpdate: ({ editor }) => {
const html = editor.getHTML();
if (html !== modelValue.value) {
modelValue.value = html;
}
emit('change', {
html,
json: editor.getJSON(),
text: editor.getText(),
});
},
});
const toolbarGroups = computed<ToolbarAction[][]>(() => {
return createToolbarGroups();
});
const previewContent = computed(
() => editor.value?.getHTML() ?? modelValue.value,
);
const [PreviewModal, previewModalApi] = useVbenModal({
footer: false,
fullscreenButton: false,
});
const {
applyPaletteColor,
canRunAction,
canRunMenuItem,
clearPaletteColor,
getActionIndicatorColor,
getMenuItemClass,
getPaletteCurrentColor,
getPaletteSwatchClass,
getToolbarButtonClass,
isMenuItemActive,
runAction,
runMenuItem,
} = useTiptapToolbar({
editable: () => props.editable,
editor,
});
function openPreviewModal() {
previewModalApi.open();
}
watch(
() => props.editable,
(editable) => {
editor.value?.setEditable(editable);
},
);
watch(
() => modelValue.value,
(nextValue = '') => {
if (!editor.value) {
return;
}
const currentValue = editor.value.getHTML();
if (nextValue === currentValue) {
return;
}
editor.value.commands.setContent(nextValue, {
emitUpdate: false,
});
},
);
onBeforeUnmount(() => {
editor.value?.destroy();
});
</script>
<template>
<div
:style="{ '--vben-tiptap-min-height': contentMinHeight }"
class="vben-tiptap overflow-hidden rounded-xl border border-border bg-card"
>
<div
v-if="toolbar"
class="sticky top-0 z-10 flex flex-wrap items-center gap-2 border-b border-border p-2 backdrop-blur-[14px]"
>
<div
v-for="(group, groupIndex) in toolbarGroups"
:key="groupIndex"
class="flex items-center gap-1"
>
<template v-for="action in group" :key="action.label">
<VbenPopover
v-if="action.menu || action.palette"
:content-props="{ align: 'start', side: 'bottom', sideOffset: 8 }"
content-class="w-auto p-2"
>
<template #trigger>
<VbenIconButton
:aria-label="action.label"
:class="getToolbarButtonClass(action)"
:disabled="!canRunAction(action)"
:tooltip="action.label"
tooltip-side="top"
variant="ghost"
>
<template v-if="action.triggerText">
<span class="text-xs font-semibold tracking-wide">
{{
typeof action.triggerText === 'function'
? action.triggerText(editor)
: action.triggerText
}}
</span>
<ChevronDown class="size-4 opacity-70" />
</template>
<component
v-else-if="action.icon"
:is="action.icon"
class="size-4"
/>
<span
v-if="getActionIndicatorColor(action)"
:style="{ backgroundColor: getActionIndicatorColor(action) }"
class="absolute bottom-1 left-1/2 h-1 w-4 -translate-x-1/2 rounded-full shadow-[0_0_0_1px_hsl(var(--card)/0.7)]"
></span>
</VbenIconButton>
</template>
<div
v-if="action.palette"
class="flex max-w-52 flex-wrap items-center gap-2"
>
<button
v-for="color in action.palette.colors"
:key="color"
:aria-label="`${action.label}-${color}`"
:class="getPaletteSwatchClass(action, color)"
:style="{ backgroundColor: color }"
type="button"
@click="applyPaletteColor(action, color)"
>
<Check
v-if="getPaletteCurrentColor(action) === color"
class="size-4 text-white drop-shadow-sm"
/>
</button>
<button
v-if="action.palette.clear"
class="h-8 w-full rounded-xl border border-border bg-secondary text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
type="button"
@click="clearPaletteColor(action)"
>
{{ $t('ui.tiptap.toolbar.clear') }}
</button>
</div>
<div v-else-if="action.menu" class="flex min-w-32 flex-col gap-1">
<button
v-for="item in action.menu.items"
:key="item.shortLabel"
:class="getMenuItemClass(item)"
:disabled="!canRunMenuItem(item)"
type="button"
@click="runMenuItem(item)"
>
<span class="w-7 text-xs font-semibold tracking-wide">
{{ item.shortLabel }}
</span>
<span class="flex-1">{{ item.label }}</span>
<Check
v-if="isMenuItemActive(item)"
class="size-4 text-primary"
/>
</button>
</div>
</VbenPopover>
<VbenIconButton
v-else
:aria-label="action.label"
:class="getToolbarButtonClass(action)"
:disabled="!canRunAction(action)"
:tooltip="action.label"
tooltip-side="top"
@click="runAction(action)"
>
<component :is="action.icon" class="size-4" />
<span
v-if="getActionIndicatorColor(action)"
:style="{ backgroundColor: getActionIndicatorColor(action) }"
class="absolute bottom-1 left-1/2 h-1 w-4 -translate-x-1/2 rounded-full shadow-[0_0_0_1px_hsl(var(--card)/0.7)]"
></span>
</VbenIconButton>
</template>
<div
v-if="groupIndex < toolbarGroups.length - 1"
class="ml-1 h-5 w-px bg-border"
></div>
</div>
<div v-if="previewable" class="ml-auto flex items-center">
<VbenIconButton
:aria-label="$t('ui.tiptap.toolbar.preview')"
:class="
getToolbarButtonClass({
action: () => {},
label: $t('ui.tiptap.toolbar.preview'),
})
"
:tooltip="$t('ui.tiptap.toolbar.preview')"
tooltip-side="top"
variant="ghost"
@click="openPreviewModal"
>
<Eye class="size-4" />
</VbenIconButton>
</div>
</div>
<EditorContent v-if="editor" :editor="editor" class="p-4" />
<PreviewModal
v-if="previewable"
:title="$t('ui.tiptap.toolbar.preview')"
class="w-4/5"
>
<Preview :content="previewContent" :min-height="320" />
</PreviewModal>
</div>
</template>
<style scoped>
.vben-tiptap
:deep(.vben-tiptap__content p.is-editor-empty:first-child::before) {
float: left;
height: 0;
color: hsl(var(--input-placeholder));
pointer-events: none;
content: attr(data-placeholder);
}
</style>

View File

@@ -0,0 +1,345 @@
import type { Editor } from '@tiptap/vue-3';
import type { ToolbarAction, ToolbarMenuItem } from './types';
import {
AlignCenter,
AlignLeft,
AlignRight,
Bold,
Highlighter,
ImagePlus,
Italic,
Link2,
List,
ListOrdered,
MessageSquareCode,
Paintbrush,
Redo2,
RemoveFormatting,
SquareCode,
Strikethrough,
TextQuote,
Underline,
Undo2,
Unlink2,
} from '@vben/icons';
import { $t } from '@vben/locales';
import { COLOR_PRESETS } from '@vben/preferences';
import { prompt } from '@vben-core/popup-ui';
const headingLevels = [1, 2, 3, 4] as const;
const editorColorPresets = [
'hsl(var(--foreground))',
'hsl(var(--warning))',
'hsl(var(--success))',
'hsl(var(--destructive))',
...COLOR_PRESETS.map((item) => item.color),
];
const editorHighlightPresets = [
withAlpha('hsl(var(--warning))', 0.45),
withAlpha('hsl(var(--success))', 0.35),
withAlpha('hsl(var(--primary))', 0.3),
withAlpha('hsl(var(--destructive))', 0.3),
...COLOR_PRESETS.map((item) => withAlpha(item.color, 0.4)),
];
function createHeadingMenuItems(): ToolbarMenuItem[] {
return [
{
action: (editor) => editor.chain().focus().setParagraph().run(),
can: (editor) => editor.can().chain().focus().setParagraph().run(),
isActive: (editor) => editor.isActive('paragraph'),
label: $t('ui.tiptap.toolbar.paragraph'),
shortLabel: 'P',
},
...headingLevels.map((level) => ({
action: (editor: Editor) =>
editor.chain().focus().toggleHeading({ level }).run(),
can: (editor: Editor) =>
editor.can().chain().focus().toggleHeading({ level }).run(),
isActive: (editor: Editor) => editor.isActive('heading', { level }),
label: $t(`ui.tiptap.toolbar.heading${level}`),
shortLabel: `H${level}`,
})),
];
}
function getHeadingTriggerText(editor?: Editor) {
if (editor?.isActive('paragraph')) {
return 'P';
}
const level = headingLevels.find((headingLevel) =>
editor?.isActive('heading', { level: headingLevel }),
);
return level ? `H${level}` : 'H';
}
function normalizeLinkUrl(url: string) {
if (/^(https?:|mailto:|tel:)/i.test(url)) {
return url;
}
return `https://${url}`;
}
function withAlpha(color: string, alpha: number) {
const normalizedAlpha = Math.min(Math.max(alpha, 0), 1);
const hslMatch = color.match(/^hsl\((.+)\)$/);
if (!hslMatch) {
return color;
}
return `hsl(${hslMatch[1]} / ${normalizedAlpha})`;
}
async function handleLinkAction(editor: Editor) {
const currentHref = editor.getAttributes('link').href as string | undefined;
let url: string | undefined;
try {
url = await prompt<string>({
componentProps: {
placeholder: 'https://example.com',
},
content: $t('ui.tiptap.prompts.link'),
defaultValue: currentHref ?? '',
});
} catch {
return;
}
const nextUrl = (url ?? '').trim();
if (!nextUrl) {
editor.chain().focus().extendMarkRange('link').unsetLink().run();
return;
}
editor
.chain()
.focus()
.extendMarkRange('link')
.setLink({
href: normalizeLinkUrl(nextUrl),
})
.run();
}
async function handleImageAction(editor: Editor) {
let url: string | undefined;
try {
url = await prompt<string>({
componentProps: {
placeholder: 'https://example.com/image.png',
},
content: $t('ui.tiptap.prompts.image'),
defaultValue: '',
});
} catch {
return;
}
const nextUrl = (url ?? '').trim();
if (!nextUrl) {
return;
}
editor.chain().focus().setImage({ src: nextUrl }).run();
}
export function createToolbarGroups(): ToolbarAction[][] {
const headingMenuItems = createHeadingMenuItems();
return [
[
{
action: (editor) => editor.chain().focus().undo().run(),
can: (editor) => editor.can().chain().focus().undo().run(),
icon: Undo2,
label: $t('ui.tiptap.toolbar.undo'),
},
{
action: (editor) => editor.chain().focus().redo().run(),
can: (editor) => editor.can().chain().focus().redo().run(),
icon: Redo2,
label: $t('ui.tiptap.toolbar.redo'),
},
{
action: (editor) =>
editor.chain().focus().clearNodes().unsetAllMarks().run(),
icon: RemoveFormatting,
label: $t('ui.tiptap.toolbar.clear'),
},
],
[
{
action: (editor) => editor.chain().focus().toggleBold().run(),
active: { name: 'bold' },
can: (editor) => editor.can().chain().focus().toggleBold().run(),
icon: Bold,
label: $t('ui.tiptap.toolbar.bold'),
},
{
action: (editor) => editor.chain().focus().toggleItalic().run(),
active: { name: 'italic' },
can: (editor) => editor.can().chain().focus().toggleItalic().run(),
icon: Italic,
label: $t('ui.tiptap.toolbar.italic'),
},
{
action: (editor) => editor.chain().focus().toggleUnderline().run(),
active: { name: 'underline' },
can: (editor) => editor.can().chain().focus().toggleUnderline().run(),
icon: Underline,
label: $t('ui.tiptap.toolbar.underline'),
},
{
action: (editor) => editor.chain().focus().toggleStrike().run(),
active: { name: 'strike' },
can: (editor) => editor.can().chain().focus().toggleStrike().run(),
icon: Strikethrough,
label: $t('ui.tiptap.toolbar.strike'),
},
{
action: (editor) => editor.chain().focus().toggleCode().run(),
active: { name: 'code' },
can: (editor) => editor.can().chain().focus().toggleCode().run(),
icon: SquareCode,
label: $t('ui.tiptap.toolbar.code'),
},
],
[
{
action: () => {},
can: (editor) =>
headingMenuItems.some((item) => (item.can ? item.can(editor) : true)),
isActive: (editor) =>
headingMenuItems.some((item) => item.isActive?.(editor)),
label: $t('ui.tiptap.toolbar.heading'),
menu: {
items: headingMenuItems,
},
triggerText: (editor) => getHeadingTriggerText(editor),
},
{
action: (editor) => editor.chain().focus().toggleBulletList().run(),
active: { name: 'bulletList' },
can: (editor) => editor.can().chain().focus().toggleBulletList().run(),
icon: List,
label: $t('ui.tiptap.toolbar.bulletList'),
},
{
action: (editor) => editor.chain().focus().toggleOrderedList().run(),
active: { name: 'orderedList' },
can: (editor) => editor.can().chain().focus().toggleOrderedList().run(),
icon: ListOrdered,
label: $t('ui.tiptap.toolbar.orderedList'),
},
{
action: (editor) => editor.chain().focus().toggleBlockquote().run(),
active: { name: 'blockquote' },
can: (editor) => editor.can().chain().focus().toggleBlockquote().run(),
icon: TextQuote,
label: $t('ui.tiptap.toolbar.blockquote'),
},
{
action: (editor) => editor.chain().focus().toggleCodeBlock().run(),
active: { name: 'codeBlock' },
can: (editor) => editor.can().chain().focus().toggleCodeBlock().run(),
icon: MessageSquareCode,
label: $t('ui.tiptap.toolbar.codeBlock'),
},
],
[
{
action: (editor) => handleLinkAction(editor),
active: { name: 'link' },
can: (editor) =>
editor.can().chain().focus().extendMarkRange('link').run(),
icon: Link2,
label: $t('ui.tiptap.toolbar.link'),
},
{
action: (editor) => editor.chain().focus().unsetLink().run(),
can: (editor) => editor.can().chain().focus().unsetLink().run(),
icon: Unlink2,
isActive: (editor) => editor.isActive('link'),
label: $t('ui.tiptap.toolbar.unlink'),
},
{
action: (editor) => handleImageAction(editor),
icon: ImagePlus,
label: $t('ui.tiptap.toolbar.image'),
},
],
[
{
action: () => {},
icon: Paintbrush,
indicatorColor: (editor) =>
editor.getAttributes('textStyle').color as string | undefined,
isActive: (editor) => Boolean(editor.getAttributes('textStyle').color),
label: $t('ui.tiptap.toolbar.textColor'),
palette: {
apply: (editor, color) =>
editor.chain().focus().setColor(color).run(),
clear: (editor) => editor.chain().focus().unsetColor().run(),
colors: editorColorPresets,
currentColor: (editor) =>
editor.getAttributes('textStyle').color as string | undefined,
},
},
{
action: () => {},
icon: Highlighter,
indicatorColor: (editor) =>
(editor.getAttributes('highlight').color as string | undefined) ??
'#fef08a',
isActive: (editor) => editor.isActive('highlight'),
label: $t('ui.tiptap.toolbar.highlightColor'),
palette: {
apply: (editor, color) =>
editor.chain().focus().setHighlight({ color }).run(),
clear: (editor) => editor.chain().focus().unsetHighlight().run(),
colors: editorHighlightPresets,
currentColor: (editor) =>
editor.getAttributes('highlight').color as string | undefined,
},
},
],
[
{
action: (editor) => editor.chain().focus().setTextAlign('left').run(),
can: (editor) =>
editor.can().chain().focus().setTextAlign('left').run(),
icon: AlignLeft,
isActive: (editor) => editor.isActive({ textAlign: 'left' }),
label: $t('ui.tiptap.toolbar.alignLeft'),
},
{
action: (editor) => editor.chain().focus().setTextAlign('center').run(),
can: (editor) =>
editor.can().chain().focus().setTextAlign('center').run(),
icon: AlignCenter,
isActive: (editor) => editor.isActive({ textAlign: 'center' }),
label: $t('ui.tiptap.toolbar.alignCenter'),
},
{
action: (editor) => editor.chain().focus().setTextAlign('right').run(),
can: (editor) =>
editor.can().chain().focus().setTextAlign('right').run(),
icon: AlignRight,
isActive: (editor) => editor.isActive({ textAlign: 'right' }),
label: $t('ui.tiptap.toolbar.alignRight'),
},
],
];
}

View File

@@ -0,0 +1,60 @@
import type { Extensions, JSONContent } from '@tiptap/core';
import type { Editor } from '@tiptap/vue-3';
import type { Component } from 'vue';
export interface TipTapProps {
editable?: boolean;
extensions?: Extensions;
minHeight?: number | string;
placeholder?: string;
previewable?: boolean;
toolbar?: boolean;
}
export interface TipTapPreviewProps {
class?: any;
content?: string;
minHeight?: number | string;
}
export interface VbenTiptapChangeEvent {
html: string;
json: JSONContent;
text: string;
}
export interface VbenTiptapExtensionOptions {
placeholder?: string;
}
export interface ToolbarAction {
action: (editor: Editor) => void;
active?: {
attrs?: Record<string, unknown>;
name: string;
};
can?: (editor: Editor) => boolean;
icon?: Component;
indicatorColor?: (editor: Editor) => string | undefined;
isActive?: (editor: Editor) => boolean;
label: string;
menu?: {
items: ToolbarMenuItem[];
};
palette?: {
apply: (editor: Editor, color: string) => void;
clear?: (editor: Editor) => void;
colors: string[];
currentColor?: (editor: Editor) => string | undefined;
};
triggerText?: ((editor?: Editor) => string) | string;
}
export interface ToolbarMenuItem {
action: (editor: Editor) => void;
can?: (editor: Editor) => boolean;
isActive?: (editor: Editor) => boolean;
label: string;
shortLabel: string;
}

View File

@@ -0,0 +1,176 @@
import type { Editor } from '@tiptap/vue-3';
import type { ShallowRef } from 'vue';
import type { ToolbarAction, ToolbarMenuItem } from './types';
import { cn } from '@vben-core/shared/utils';
interface UseTiptapToolbarOptions {
editable: () => boolean;
editor: Readonly<ShallowRef<Editor | undefined>>;
}
export function useTiptapToolbar(options: UseTiptapToolbarOptions) {
const getEditor = () => options.editor.value;
function getActionIndicatorColor(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor || !action.indicatorColor) {
return undefined;
}
return action.indicatorColor(currentEditor);
}
function getPaletteCurrentColor(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor || !action.palette?.currentColor) {
return undefined;
}
return action.palette.currentColor(currentEditor);
}
function canRunAction(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor || !options.editable()) {
return false;
}
return action.can ? action.can(currentEditor) : true;
}
function canRunMenuItem(item: ToolbarMenuItem) {
const currentEditor = getEditor();
if (!currentEditor || !options.editable()) {
return false;
}
return item.can ? item.can(currentEditor) : true;
}
function isActionActive(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor) {
return false;
}
if (action.isActive) {
return action.isActive(currentEditor);
}
if (!action.active) {
return false;
}
return currentEditor.isActive(action.active.name, action.active.attrs);
}
function isMenuItemActive(item: ToolbarMenuItem, currentEditor?: Editor) {
const targetEditor = currentEditor ?? getEditor();
if (!targetEditor || !item.isActive) {
return false;
}
return item.isActive(targetEditor);
}
function runAction(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor || !options.editable()) {
return;
}
if (action.menu || action.palette) {
return;
}
action.action(currentEditor);
}
function runMenuItem(item: ToolbarMenuItem) {
const currentEditor = getEditor();
if (!currentEditor || !options.editable()) {
return;
}
item.action(currentEditor);
}
function applyPaletteColor(action: ToolbarAction, color: string) {
const currentEditor = getEditor();
if (!currentEditor || !action.palette) {
return;
}
action.palette.apply(currentEditor, color);
}
function clearPaletteColor(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor || !action.palette?.clear) {
return;
}
action.palette.clear(currentEditor);
}
function getToolbarButtonClass(action: ToolbarAction) {
return cn(
'relative rounded-[10px] border border-transparent bg-transparent text-muted-foreground shadow-none',
'transition-[transform,color,background-color,border-color,box-shadow] duration-200 ease-out',
'enabled:hover:-translate-y-px enabled:hover:border-border disabled:opacity-45',
'enabled:hover:bg-accent enabled:hover:text-foreground',
isActionActive(action) &&
'border-primary/30 bg-accent text-primary shadow-primary',
);
}
function getPaletteSwatchClass(action: ToolbarAction, color: string) {
return cn(
'inline-flex size-8 items-center justify-center rounded-full border border-border',
'shadow-accent',
'transition-[transform,box-shadow,border-color] duration-200 ease-out',
'hover:-translate-y-px hover:scale-[1.04]',
getPaletteCurrentColor(action) === color &&
'border-primary shadow-primary',
);
}
function getMenuItemClass(item: ToolbarMenuItem) {
return cn(
'flex items-center gap-2 rounded-lg p-2 text-left text-sm transition-colors',
'disabled:cursor-not-allowed disabled:opacity-45',
isMenuItemActive(item)
? 'bg-accent text-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-foreground',
);
}
return {
applyPaletteColor,
canRunAction,
canRunMenuItem,
clearPaletteColor,
getActionIndicatorColor,
getMenuItemClass,
getPaletteCurrentColor,
getPaletteSwatchClass,
getToolbarButtonClass,
isActionActive,
isMenuItemActive,
runAction,
runMenuItem,
};
}

View File

@@ -61,6 +61,42 @@
"cancel": "Cancel cropping",
"errorTip": "Cropping error"
},
"tiptap": {
"placeholder": "Please enter content...",
"prompts": {
"image": "Enter image URL",
"link": "Enter link URL"
},
"toolbar": {
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"code": "Code",
"codeBlock": "Code Block",
"heading": "Heading",
"paragraph": "Paragraph",
"heading1": "H1",
"heading2": "H2",
"heading3": "H3",
"heading4": "H4",
"bulletList": "Bullets",
"orderedList": "Numbering",
"blockquote": "Quote",
"link": "Link",
"unlink": "Unlink",
"image": "Image",
"textColor": "Text Color",
"highlightColor": "Highlight Color",
"alignLeft": "Left",
"alignCenter": "Center",
"alignRight": "Right",
"preview": "Preview",
"undo": "Undo",
"redo": "Redo",
"clear": "Clear"
}
},
"fallback": {
"pageNotFound": "Oops! Page Not Found",
"pageNotFoundDesc": "Sorry, we couldn't find the page you were looking for.",

View File

@@ -61,6 +61,42 @@
"cancel": "取消裁剪",
"errorTip": "裁剪错误"
},
"tiptap": {
"placeholder": "请输入内容...",
"prompts": {
"image": "请输入图片地址",
"link": "请输入链接地址"
},
"toolbar": {
"bold": "加粗",
"italic": "斜体",
"underline": "下划线",
"strike": "删除线",
"code": "行内代码",
"codeBlock": "代码块",
"heading": "标题",
"paragraph": "正文",
"heading1": "标题1",
"heading2": "标题2",
"heading3": "标题3",
"heading4": "标题4",
"bulletList": "无序列表",
"orderedList": "有序列表",
"blockquote": "引用",
"link": "链接",
"unlink": "移除链接",
"image": "图片",
"textColor": "文字颜色",
"highlightColor": "高亮颜色",
"alignLeft": "左对齐",
"alignCenter": "居中",
"alignRight": "右对齐",
"preview": "预览",
"undo": "撤销",
"redo": "重做",
"clear": "清除"
}
},
"fallback": {
"pageNotFound": "哎呀!未找到页面",
"pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。",

View File

@@ -40,6 +40,7 @@ import {
import { useSortable } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { VbenTiptap } from '@vben/plugins/tiptap';
import { isEmpty } from '@vben/utils';
import { message, Modal, notification } from 'ant-design-vue';
@@ -583,6 +584,7 @@ export type ComponentType =
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'RichEditor'
| 'Select'
| 'Space'
| 'Switch'
@@ -646,6 +648,7 @@ async function initComponentAdapter() {
RadioGroup,
RangePicker,
Rate,
RichEditor: withDefaultPlaceholder(VbenTiptap, 'input'),
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,

View File

@@ -79,5 +79,8 @@
},
"cropper": {
"title": "Cropper"
},
"tiptap": {
"title": "Rich Text Editor"
}
}

View File

@@ -79,5 +79,8 @@
},
"cropper": {
"title": "图片裁剪"
},
"tiptap": {
"title": "富文本编辑器"
}
}

View File

@@ -346,6 +346,15 @@ const routes: RouteRecordRaw[] = [
title: $t('examples.cropper.title'),
},
},
{
name: 'TiptapExample',
path: '/examples/tiptap',
component: () => import('#/views/examples/tiptap/index.vue'),
meta: {
icon: 'lucide:square-pen',
title: $t('examples.tiptap.title'),
},
},
],
},
];

View File

@@ -409,6 +409,12 @@ const [BaseForm, baseFormApi] = useVbenForm({
},
rules: 'selectRequired',
},
{
component: 'RichEditor',
fieldName: 'richEditor',
label: '富文本',
formItemClass: 'col-span-3 items-baseline',
},
],
// 大屏一行显示3个中屏一行显示2个小屏一行显示1个
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
@@ -485,6 +491,12 @@ function handleSetFormValue() {
timePicker: dayjs('2022-01-01 12:00:00'),
treeSelect: 'leaf1',
username: '1',
richEditor: `
<h1>Vben Tiptap</h1>
<p>这个编辑器已经被封装在 <code>packages/effects/plugins/src/tiptap</code> 中。</p>
<p>你可以直接在各个 app 里通过 <code>@vben/plugins/tiptap</code> 引入。</p>
<blockquote>默认内置 StarterKit、Underline、TextAlign、Placeholder。</blockquote>
`,
});
// 设置单个表单值

View File

@@ -0,0 +1,39 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { Page } from '@vben/common-ui';
import { VbenTiptap, VbenTiptapPreview } from '@vben/plugins/tiptap';
import { Card } from 'ant-design-vue';
const content = ref(`
<h1>Vben Tiptap</h1>
<p>这个编辑器已经被封装在 <code>packages/effects/plugins/src/tiptap</code> 中。</p>
<p>你可以直接在各个 app 里通过 <code>@vben/plugins/tiptap</code> 引入。</p>
<blockquote>默认内置 StarterKit、Underline、TextAlign、Placeholder。</blockquote>
`);
const previewContent = computed(() => content.value);
</script>
<template>
<Page title="Tiptap 富文本">
<template #description>
<div class="mt-2 text-foreground/80">
统一封装后的富文本编辑器适合在各个 app 中直接复用
</div>
</template>
<Card class="mb-5" title="编辑器">
<VbenTiptap v-model="content" />
</Card>
<Card class="mb-5" title="富文本预览">
<VbenTiptapPreview :content="previewContent" />
</Card>
<Card title="HTML 输出">
<pre class="overflow-auto rounded-xl border border-border bg-muted p-4">
{{ previewContent }}
</pre>
</Card>
</Page>
</template>

660
pnpm-lock.yaml generated
View File

@@ -78,6 +78,36 @@ catalogs:
'@tanstack/vue-store':
specifier: ^0.9.3
version: 0.9.3
'@tiptap/core':
specifier: ^3.21.0
version: 3.21.0
'@tiptap/extension-highlight':
specifier: ^3.21.0
version: 3.21.0
'@tiptap/extension-image':
specifier: ^3.21.0
version: 3.21.0
'@tiptap/extension-link':
specifier: ^3.21.0
version: 3.21.0
'@tiptap/extension-placeholder':
specifier: ^3.21.0
version: 3.21.0
'@tiptap/extension-text-align':
specifier: ^3.21.0
version: 3.21.0
'@tiptap/extension-text-style':
specifier: ^3.21.0
version: 3.21.0
'@tiptap/extension-underline':
specifier: ^3.21.0
version: 3.21.0
'@tiptap/starter-kit':
specifier: ^3.21.0
version: 3.21.0
'@tiptap/vue-3':
specifier: ^3.21.0
version: 3.21.0
'@tsdown/css':
specifier: ^0.21.7
version: 0.21.7
@@ -1758,12 +1788,45 @@ importers:
packages/effects/plugins:
dependencies:
'@tiptap/core':
specifier: 'catalog:'
version: 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-highlight':
specifier: 'catalog:'
version: 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-image':
specifier: 'catalog:'
version: 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-link':
specifier: 'catalog:'
version: 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-placeholder':
specifier: 'catalog:'
version: 3.21.0(@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))
'@tiptap/extension-text-align':
specifier: 'catalog:'
version: 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-text-style':
specifier: 'catalog:'
version: 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-underline':
specifier: 'catalog:'
version: 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/starter-kit':
specifier: 'catalog:'
version: 3.21.0
'@tiptap/vue-3':
specifier: 'catalog:'
version: 3.21.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)(vue@3.5.31(typescript@5.9.3))
'@vben-core/design':
specifier: workspace:*
version: link:../../@core/base/design
'@vben-core/form-ui':
specifier: workspace:*
version: link:../../@core/ui-kit/form-ui
'@vben-core/popup-ui':
specifier: workspace:*
version: link:../../@core/ui-kit/popup-ui
'@vben-core/shadcn-ui':
specifier: workspace:*
version: link:../../@core/ui-kit/shadcn-ui
@@ -4474,6 +4537,9 @@ packages:
'@quansync/fs@1.0.0':
resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==}
'@remirror/core-constants@3.0.0':
resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
'@rolldown/binding-android-arm64@1.0.0-rc.12':
resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -5035,6 +5101,178 @@ packages:
peerDependencies:
vue: ^3.5.31
'@tiptap/core@3.21.0':
resolution: {integrity: sha512-IfnQiuEeabDSPr1C/zHFTbnvlTf5z0DE/d/xz4C6bkL4ZBDJ3rr99h2qsaV0l8F+kbNswZMlQdM8rxNlMy95fQ==}
peerDependencies:
'@tiptap/pm': ^3.21.0
'@tiptap/extension-blockquote@3.21.0':
resolution: {integrity: sha512-JDM/RR6rM0dMCZ1UnEf7eqmN6pAdIa2llhN+E24HdTGNJCklMFhLAGE/OT8/1r7M0WWA9GVO7/PTe4EdGh6+lQ==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-bold@3.21.0':
resolution: {integrity: sha512-iyEJRzG7XTCPlHwEDzUw3HnuYYCfL7lNpcCHmxcpYMrIUA8rv7EUxerIwApT6xY8hQ/07ljuJKgOyPvnJOOzuA==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-bubble-menu@3.21.0':
resolution: {integrity: sha512-/fabRRhhf8i4LAx9e8xz9ppqN5KgdJk3TxMuxAD5vAWGsejvhSoPa8O8H/QwwyntXm1Vue8aQiMHsUk48b2hGQ==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/pm': ^3.21.0
'@tiptap/extension-bullet-list@3.21.0':
resolution: {integrity: sha512-PWNF+xwxgOeXYGD88sCQLKL0eBoQqjUnZNALxBjN3Y7x4llalh42rHOp2Nt2t6UbQgqTBtBzU/uFcussTpxreQ==}
peerDependencies:
'@tiptap/extension-list': ^3.21.0
'@tiptap/extension-code-block@3.21.0':
resolution: {integrity: sha512-zrVOcOzDCjHQ8NJcC+qHmZZKiwnP/NMSb3qVJlSMN8TzuHept1MZCDa2Mbo70O6I0txo456SGuXB9sqV1vHmGg==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/pm': ^3.21.0
'@tiptap/extension-code@3.21.0':
resolution: {integrity: sha512-D7wA9jp+4X2r1f3FIoga73s6Rn4rmZY57Jes6a4rK3HY+3yHk1r057pPIZSY8Drfs97jxHQVFdfUYUomLSFYBA==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-document@3.21.0':
resolution: {integrity: sha512-7oCyzXI9ChvJQUlr23AURdfVar4OIsrYUvqdhEwo3bjcI/Q/j0KJiXfuh6ZzL5eVaINSailH53sZaGg4THQtUg==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-dropcursor@3.21.0':
resolution: {integrity: sha512-6fsDSVAM2iz7eElvT6iivMrGBGjIP/oPigVZ/SPm6f31phaYhz6TIOEgV/Lr2jaPIOgyK4U0cU4Yd4KUBCmhzQ==}
peerDependencies:
'@tiptap/extensions': ^3.21.0
'@tiptap/extension-floating-menu@3.21.0':
resolution: {integrity: sha512-n2HzTB+I/5rAl8R/1sKMv92JiY1oDK1hroXizxEKYa6dskJcAMW0CfYyPcPOZWQQEe7qoeOvQISr2ooLAKW+Mw==}
peerDependencies:
'@floating-ui/dom': ^1.0.0
'@tiptap/core': ^3.21.0
'@tiptap/pm': ^3.21.0
'@tiptap/extension-gapcursor@3.21.0':
resolution: {integrity: sha512-wGjgAoYBTvPAe9QYMI5px355XcNeMkaUrMY9IHbMqgqdmHcDxqooxM4H6sYVX2CRcHwXy4I8NQAoOhSYrQJDMg==}
peerDependencies:
'@tiptap/extensions': ^3.21.0
'@tiptap/extension-hard-break@3.21.0':
resolution: {integrity: sha512-6JFVSAOQ1qhQHi9mVcdn2/XO8YIMgYV8zjarzNUzP6Sf2waeE5BLXjlg6rIH/945sY1J+FndTojLru6gQ07a5A==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-heading@3.21.0':
resolution: {integrity: sha512-ji6VJmoRnDzAHYflEYEZohMHRi77UGLW1o3ua7UhI32iJ9nuYssbPNuzEeE4SvENMQwZRszad5+a+dKAa+NC7g==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-highlight@3.21.0':
resolution: {integrity: sha512-3f/bVgfm2dJZxalh07TThDxcTaeXJ+dpYyRY9trnFeHbhyYQXSy6yzkNhNcYB4Ua5jxpKQv4b9Q448QVh+KNzA==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-horizontal-rule@3.21.0':
resolution: {integrity: sha512-vNBnOfFEY62CoJPGo4nonRM7RiOvhII1vhoO+WFr1GxDqCAfmEFjToflt7JT1UJdo6lMVcD+aaaAgOiuSz5p6g==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/pm': ^3.21.0
'@tiptap/extension-image@3.21.0':
resolution: {integrity: sha512-W9786a2K4LSZJMPeRLmoDulJeXOsM0ueRV2MHjTol7ikPRauROB7GUbAz9DyPAJHA2AGUfpswnGAYPO3tz5CLg==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-italic@3.21.0':
resolution: {integrity: sha512-2I8oPvwyXhRn1k8lbDFIutzvhtLEjoO5mmQCNX4TnT4PdxxaSrK9+ihYg12VeqhUeO7dg1MKiFqws0HVBrwzWg==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-link@3.21.0':
resolution: {integrity: sha512-oMU7Yve1sbgBsaFAUc2R0GPf4d3ZPVJeMUFC6b6X9rJIvx/IhEUEn9toQcSBGfp02uWK9NdQyIFYFdWlVXH++w==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/pm': ^3.21.0
'@tiptap/extension-list-item@3.21.0':
resolution: {integrity: sha512-1ZymZmlQVbAoC4q5x3cro0v5+3I6l+BHqbhIMQLjQFlAOJfcE0pvqRzAFW7PduxUj41tXEtsYqp2NREvO9F5Fg==}
peerDependencies:
'@tiptap/extension-list': ^3.21.0
'@tiptap/extension-list-keymap@3.21.0':
resolution: {integrity: sha512-EzrfW3ASNFPWKhR8sNOq7Kqw4hvaTAOn4dlI7chB8HIANSrlyPOUn+eKAnO6HQgsUgsbjg2GbTUrGrxcoLykUg==}
peerDependencies:
'@tiptap/extension-list': ^3.21.0
'@tiptap/extension-list@3.21.0':
resolution: {integrity: sha512-KeBlEtLrGce2d3dgL89hmwWEtREuzlW4XY5bYWpKNvCbFqvdSb3n7vkdkw32YclZmMWxAcABgW6ucCStkE0rsQ==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/pm': ^3.21.0
'@tiptap/extension-ordered-list@3.21.0':
resolution: {integrity: sha512-+d+0orokMfqaBfvr9tUBgGvo2ZCV+fR3JzsJTmnLBWOkhBSJN7H4pnfXPTue0qwspUwRmkLJxdIlU+J7HkMrng==}
peerDependencies:
'@tiptap/extension-list': ^3.21.0
'@tiptap/extension-paragraph@3.21.0':
resolution: {integrity: sha512-cMPG/jCoZ9NmLZ5ctFziILaxJGfDtMTb5OLBhifMFZeMVwF1pEJIygDEfnX/HSruv507weZSQG4pERO2tRszMg==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-placeholder@3.21.0':
resolution: {integrity: sha512-fs+cQqMh1d1naV6OgOhP/0qbRJwtw8DpQMj3/oqGKbaRRKIeecEaZPXYRd7MYa4e9K0Cfk5Bm0MNs9lwu/BYsw==}
peerDependencies:
'@tiptap/extensions': ^3.21.0
'@tiptap/extension-strike@3.21.0':
resolution: {integrity: sha512-easnVaN11Wl+5fOtfvzJ10J762S9TRXZaMj5rLBGavgf82DCYHqhGhBqpLQrJ41r4nPABGlYvTRoxfvBLB74Lg==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-text-align@3.21.0':
resolution: {integrity: sha512-XaoUaq45nai9LLoUStumMItHhhCnmXirPR8mTjEDKZ2QD0Kg/YHEr6guJYc6qKB7YA+Wa1EgBIrZOv1+d2Pdag==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-text-style@3.21.0':
resolution: {integrity: sha512-MI/3X75D45Wa/+0Fp8nYfNJq5makkjtG7B2/lIzNUe0kEKJ56RVQoV1GQSXxAiFNyAYRfKFq8dJslhesW3EkWA==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-text@3.21.0':
resolution: {integrity: sha512-Zx8QdB8a5iBuE4uO21c3BjmpBfaJEr2Jd1QFnsdgx11fm6P7dGgZaGko1FaINhfOPRGTN6O/kiF02cDMdOHa/w==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extension-underline@3.21.0':
resolution: {integrity: sha512-gGmBEymbWnr8AIS8bI/bPw5rcwo7wAFcBw/TsLd1nAanu1dDqSRNDBrit3m02Ru+D88u2SfNvmbOPI1pz+1f5w==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/extensions@3.21.0':
resolution: {integrity: sha512-MN1uh5PmHT1F2BNsbc21MIS0AMFFA73oODlp/4ckpBR4o5AxRwV+8f43Cd52UL4MgMkKj/A+QfZ7iK9IDb0h5A==}
peerDependencies:
'@tiptap/core': ^3.21.0
'@tiptap/pm': ^3.21.0
'@tiptap/pm@3.21.0':
resolution: {integrity: sha512-I3sNo7oMMsR6FFz1ecvPb9uCF0VQuS2WV67j8Io2M7DJicRWCE/GM5DaiYjTeWBbnByk6BuG0txoJATAqPVliQ==}
'@tiptap/starter-kit@3.21.0':
resolution: {integrity: sha512-w7fWxglDtqXFBgRYH+LforJyUboSAQllnWQbGVSTyX4rsICqZjkb3f6CTSUWpGoGKmlmbb2ZpEuoik7tur9d8Q==}
'@tiptap/vue-3@3.21.0':
resolution: {integrity: sha512-dfjxBwxg9+GNvsgkCbxLnj/vmG+YZMdcD/qF7bKM710bANWfqzimRUhH5W2KZcxqlYzqpz0u/P0zi7dUMR5IZA==}
peerDependencies:
'@floating-ui/dom': ^1.0.0
'@tiptap/core': ^3.21.0
'@tiptap/pm': ^3.21.0
vue: ^3.5.31
'@tsdown/css@0.21.7':
resolution: {integrity: sha512-kydfZ109LIXwoBDrdIeEVi+PtM8375X9d/6UtYtjhj6TS94J25gJVUXw9AyJE6THEqB6OdGKM5MLqJPutO4kkA==}
engines: {node: '>=20.19.0'}
@@ -6045,8 +6283,8 @@ packages:
bare-buffer:
optional: true
bare-os@3.8.4:
resolution: {integrity: sha512-4JboWUl7/2LhgU536tjUszzaVC8/WEWKtyX5crayvlN71ih8+O2SdvBhotQeDsuhhmPZmLCrPBJEcwVPhI/kkQ==}
bare-os@3.8.6:
resolution: {integrity: sha512-l8xaNWWb/bXuzgsrlF5jaa5QYDJ9S0ddd54cP6CH+081+5iPrbJiCfBWQqrWYzmUhCbsH+WR6qxo9MeHVCr0MQ==}
engines: {bare: '>=1.14.0'}
bare-path@3.0.0:
@@ -6502,6 +6740,9 @@ packages:
resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==}
engines: {node: '>= 14'}
crelt@1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
croner@10.0.1:
resolution: {integrity: sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g==}
engines: {node: '>=18.0'}
@@ -8303,6 +8544,12 @@ packages:
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
linkifyjs@4.3.2:
resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
listhen@1.9.0:
resolution: {integrity: sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==}
hasBin: true
@@ -8457,6 +8704,10 @@ packages:
mark.js@8.11.1:
resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==}
markdown-it@14.1.1:
resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
hasBin: true
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -8476,6 +8727,9 @@ packages:
mdn-data@2.27.1:
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
mdurl@2.0.0:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
medium-zoom@1.1.0:
resolution: {integrity: sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ==}
@@ -8802,6 +9056,9 @@ packages:
resolution: {integrity: sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==}
engines: {node: '>=20'}
orderedmap@2.1.1:
resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
outdent@0.5.0:
resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==}
@@ -9159,6 +9416,64 @@ packages:
property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
prosemirror-changeset@2.4.0:
resolution: {integrity: sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==}
prosemirror-collab@1.3.1:
resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==}
prosemirror-commands@1.7.1:
resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==}
prosemirror-dropcursor@1.8.2:
resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==}
prosemirror-gapcursor@1.4.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-markdown@1.13.4:
resolution: {integrity: sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==}
prosemirror-menu@1.3.0:
resolution: {integrity: sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==}
prosemirror-model@1.25.4:
resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==}
prosemirror-schema-basic@1.2.4:
resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==}
prosemirror-schema-list@1.5.1:
resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==}
prosemirror-state@1.4.4:
resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==}
prosemirror-tables@1.8.5:
resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==}
prosemirror-trailing-node@3.0.0:
resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==}
peerDependencies:
prosemirror-model: ^1.22.1
prosemirror-state: ^1.4.2
prosemirror-view: ^1.33.8
prosemirror-transform@1.12.0:
resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==}
prosemirror-view@1.41.7:
resolution: {integrity: sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w==}
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
@@ -9174,6 +9489,10 @@ packages:
engines: {node: '>=18'}
hasBin: true
punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -9429,6 +9748,9 @@ 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: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
engines: {node: '>=18'}
@@ -10258,6 +10580,9 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
ufo@1.6.3:
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
@@ -10812,6 +11137,9 @@ packages:
vxe-table@4.18.10:
resolution: {integrity: sha512-8V9WL83pB4PrvgBS6DoDU7dSRxOlJw9ZkVcxDnKBFQkTTnLVvC8HYOJ75uwfRNyo9cALNIpJnSkow2uqaMKbNQ==}
w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
warning@4.0.3:
resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
@@ -13573,6 +13901,8 @@ snapshots:
dependencies:
quansync: 1.0.0
'@remirror/core-constants@3.0.0': {}
'@rolldown/binding-android-arm64@1.0.0-rc.12':
optional: true
@@ -14007,6 +14337,196 @@ snapshots:
'@tanstack/virtual-core': 3.13.23
vue: 3.5.31(typescript@5.9.3)
'@tiptap/core@3.21.0(@tiptap/pm@3.21.0)':
dependencies:
'@tiptap/pm': 3.21.0
'@tiptap/extension-blockquote@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-bold@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-bubble-menu@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)':
dependencies:
'@floating-ui/dom': 1.7.6
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/pm': 3.21.0
optional: true
'@tiptap/extension-bullet-list@3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/extension-list': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-code-block@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/pm': 3.21.0
'@tiptap/extension-code@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-document@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-dropcursor@3.21.0(@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/extensions': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-floating-menu@3.21.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)':
dependencies:
'@floating-ui/dom': 1.7.6
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/pm': 3.21.0
optional: true
'@tiptap/extension-gapcursor@3.21.0(@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/extensions': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-hard-break@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-heading@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-highlight@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-horizontal-rule@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/pm': 3.21.0
'@tiptap/extension-image@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-italic@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-link@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/pm': 3.21.0
linkifyjs: 4.3.2
'@tiptap/extension-list-item@3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/extension-list': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-list-keymap@3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/extension-list': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/pm': 3.21.0
'@tiptap/extension-ordered-list@3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/extension-list': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-paragraph@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-placeholder@3.21.0(@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/extensions': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-strike@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-text-align@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-text-style@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-text@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-underline@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/pm': 3.21.0
'@tiptap/pm@3.21.0':
dependencies:
prosemirror-changeset: 2.4.0
prosemirror-collab: 1.3.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-markdown: 1.13.4
prosemirror-menu: 1.3.0
prosemirror-model: 1.25.4
prosemirror-schema-basic: 1.2.4
prosemirror-schema-list: 1.5.1
prosemirror-state: 1.4.4
prosemirror-tables: 1.8.5
prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)
prosemirror-transform: 1.12.0
prosemirror-view: 1.41.7
'@tiptap/starter-kit@3.21.0':
dependencies:
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/extension-blockquote': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-bold': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-bullet-list': 3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))
'@tiptap/extension-code': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-code-block': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-document': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-dropcursor': 3.21.0(@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))
'@tiptap/extension-gapcursor': 3.21.0(@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))
'@tiptap/extension-hard-break': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-heading': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-horizontal-rule': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-italic': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-link': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-list': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-list-item': 3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))
'@tiptap/extension-list-keymap': 3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))
'@tiptap/extension-ordered-list': 3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))
'@tiptap/extension-paragraph': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-strike': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-text': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extension-underline': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))
'@tiptap/extensions': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/pm': 3.21.0
'@tiptap/vue-3@3.21.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)(vue@3.5.31(typescript@5.9.3))':
dependencies:
'@floating-ui/dom': 1.7.6
'@tiptap/core': 3.21.0(@tiptap/pm@3.21.0)
'@tiptap/pm': 3.21.0
vue: 3.5.31(typescript@5.9.3)
optionalDependencies:
'@tiptap/extension-bubble-menu': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tiptap/extension-floating-menu': 3.21.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)
'@tsdown/css@0.21.7(jiti@2.6.1)(postcss@8.5.8)(sass-embedded@1.98.0)(sass@1.98.0)(tsdown@0.21.7)(yaml@2.8.3)':
dependencies:
lightningcss: 1.32.0
@@ -15217,11 +15737,11 @@ snapshots:
- bare-abort-controller
- react-native-b4a
bare-os@3.8.4: {}
bare-os@3.8.6: {}
bare-path@3.0.0:
dependencies:
bare-os: 3.8.4
bare-os: 3.8.6
bare-stream@2.11.0(bare-events@2.8.2):
dependencies:
@@ -15686,6 +16206,8 @@ snapshots:
crc-32: 1.2.2
readable-stream: 4.7.0
crelt@1.0.6: {}
croner@10.0.1: {}
cross-env@10.1.0:
@@ -17672,6 +18194,12 @@ snapshots:
lines-and-columns@1.2.4: {}
linkify-it@5.0.0:
dependencies:
uc.micro: 2.1.0
linkifyjs@4.3.2: {}
listhen@1.9.0:
dependencies:
'@parcel/watcher': 2.5.6
@@ -17833,6 +18361,15 @@ snapshots:
mark.js@8.11.1: {}
markdown-it@14.1.1:
dependencies:
argparse: 2.0.1
entities: 4.5.0
linkify-it: 5.0.0
mdurl: 2.0.0
punycode.js: 2.3.1
uc.micro: 2.1.0
math-intrinsics@1.1.0: {}
mathml-tag-names@4.0.0: {}
@@ -17855,6 +18392,8 @@ snapshots:
mdn-data@2.27.1: {}
mdurl@2.0.0: {}
medium-zoom@1.1.0: {}
memoize-one@6.0.0: {}
@@ -18275,6 +18814,8 @@ snapshots:
stdin-discarder: 0.3.1
string-width: 8.2.0
orderedmap@2.1.1: {}
outdent@0.5.0: {}
own-keys@1.0.1:
@@ -18610,6 +19151,109 @@ snapshots:
property-information@7.1.0: {}
prosemirror-changeset@2.4.0:
dependencies:
prosemirror-transform: 1.12.0
prosemirror-collab@1.3.1:
dependencies:
prosemirror-state: 1.4.4
prosemirror-commands@1.7.1:
dependencies:
prosemirror-model: 1.25.4
prosemirror-state: 1.4.4
prosemirror-transform: 1.12.0
prosemirror-dropcursor@1.8.2:
dependencies:
prosemirror-state: 1.4.4
prosemirror-transform: 1.12.0
prosemirror-view: 1.41.7
prosemirror-gapcursor@1.4.1:
dependencies:
prosemirror-keymap: 1.2.3
prosemirror-model: 1.25.4
prosemirror-state: 1.4.4
prosemirror-view: 1.41.7
prosemirror-history@1.5.0:
dependencies:
prosemirror-state: 1.4.4
prosemirror-transform: 1.12.0
prosemirror-view: 1.41.7
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-markdown@1.13.4:
dependencies:
'@types/markdown-it': 14.1.2
markdown-it: 14.1.1
prosemirror-model: 1.25.4
prosemirror-menu@1.3.0:
dependencies:
crelt: 1.0.6
prosemirror-commands: 1.7.1
prosemirror-history: 1.5.0
prosemirror-state: 1.4.4
prosemirror-model@1.25.4:
dependencies:
orderedmap: 2.1.1
prosemirror-schema-basic@1.2.4:
dependencies:
prosemirror-model: 1.25.4
prosemirror-schema-list@1.5.1:
dependencies:
prosemirror-model: 1.25.4
prosemirror-state: 1.4.4
prosemirror-transform: 1.12.0
prosemirror-state@1.4.4:
dependencies:
prosemirror-model: 1.25.4
prosemirror-transform: 1.12.0
prosemirror-view: 1.41.7
prosemirror-tables@1.8.5:
dependencies:
prosemirror-keymap: 1.2.3
prosemirror-model: 1.25.4
prosemirror-state: 1.4.4
prosemirror-transform: 1.12.0
prosemirror-view: 1.41.7
prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7):
dependencies:
'@remirror/core-constants': 3.0.0
escape-string-regexp: 4.0.0
prosemirror-model: 1.25.4
prosemirror-state: 1.4.4
prosemirror-view: 1.41.7
prosemirror-transform@1.12.0:
dependencies:
prosemirror-model: 1.25.4
prosemirror-view@1.41.7:
dependencies:
prosemirror-model: 1.25.4
prosemirror-state: 1.4.4
prosemirror-transform: 1.12.0
proto-list@1.2.4: {}
proxy-from-env@2.1.0: {}
@@ -18624,6 +19268,8 @@ snapshots:
picocolors: 1.1.1
sade: 1.8.1
punycode.js@2.3.1: {}
punycode@2.3.1: {}
pupa@3.3.0:
@@ -18939,6 +19585,8 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.60.1
fsevents: 2.3.3
rope-sequence@1.3.4: {}
run-applescript@7.1.0: {}
run-parallel@1.2.0:
@@ -19835,6 +20483,8 @@ snapshots:
typescript@5.9.3: {}
uc.micro@2.1.0: {}
ufo@1.6.3: {}
ultrahtml@1.6.0: {}
@@ -20439,6 +21089,8 @@ snapshots:
transitivePeerDependencies:
- vue
w3c-keyname@2.2.8: {}
warning@4.0.3:
dependencies:
loose-envify: 1.4.0

View File

@@ -47,6 +47,17 @@ catalog:
'@tailwindcss/vite': ^4.2.2
'@tanstack/vue-query': ^5.95.2
'@tanstack/vue-store': ^0.9.3
'@tiptap/core': ^3.21.0
'@tiptap/extension-doc': ^3.21.0
'@tiptap/extension-highlight': ^3.21.0
'@tiptap/extension-image': ^3.21.0
'@tiptap/extension-link': ^3.21.0
'@tiptap/extension-placeholder': ^3.21.0
'@tiptap/extension-text-align': ^3.21.0
'@tiptap/extension-text-style': ^3.21.0
'@tiptap/extension-underline': ^3.21.0
'@tiptap/starter-kit': ^3.21.0
'@tiptap/vue-3': ^3.21.0
'@tsdown/css': ^0.21.7
'@types/archiver': ^7.0.0
'@types/html-minifier-terser': ^7.0.2