feat: 增加Tiptap富文本编辑器

This commit is contained in:
sunlei 2026-07-01 16:58:26 +08:00
parent 083d5a4720
commit adab366275
7 changed files with 1579 additions and 0 deletions

View File

@ -29,6 +29,17 @@
"@antdv-next/icons": "catalog:",
"@milkdown/crepe": "catalog:",
"@tanstack/vue-query": "catalog:",
"@tiptap/extension-image": "catalog:",
"@tiptap/extension-link": "catalog:",
"@tiptap/extension-placeholder": "catalog:",
"@tiptap/extension-table": "catalog:",
"@tiptap/extension-table-cell": "catalog:",
"@tiptap/extension-table-header": "catalog:",
"@tiptap/extension-table-row": "catalog:",
"@tiptap/extension-text-align": "catalog:",
"@tiptap/extension-underline": "catalog:",
"@tiptap/starter-kit": "catalog:",
"@tiptap/vue-3": "catalog:",
"@vben-core/menu-ui": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben/access": "workspace:*",

View File

@ -0,0 +1,134 @@
.kt-tiptap-editor {
display: flex;
min-height: var(--kt-tiptap-min-height, 360px);
width: 100%;
flex-direction: column;
overflow: hidden;
border: 1px solid hsl(var(--border));
border-radius: 8px;
background: hsl(var(--background));
&--disabled {
opacity: 0.72;
}
&__toolbar {
display: flex;
flex: 0 0 auto;
align-items: center;
gap: 4px;
padding: 8px 10px;
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--background));
}
&__toolbar-button {
display: inline-flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
&--active {
color: hsl(var(--primary));
background: hsl(var(--accent));
}
}
&__toolbar-group {
display: inline-flex;
align-items: center;
gap: 4px;
}
&__toolbar-icon {
width: 16px;
height: 16px;
}
&__content {
min-height: calc(var(--kt-tiptap-min-height, 360px) - 47px);
flex: 1;
overflow: auto;
}
&__prosemirror {
min-height: calc(var(--kt-tiptap-min-height, 360px) - 47px);
padding: 18px 22px;
outline: none;
> *:first-child {
margin-top: 0;
}
> *:last-child {
margin-bottom: 0;
}
p {
margin: 0 0 12px;
line-height: 1.75;
}
h1,
h2,
h3 {
margin: 18px 0 12px;
font-weight: 600;
line-height: 1.35;
}
blockquote {
margin: 12px 0;
padding-left: 14px;
border-left: 3px solid hsl(var(--border));
color: hsl(var(--muted-foreground));
}
pre {
overflow: auto;
padding: 14px;
border-radius: 6px;
background: hsl(var(--muted));
font-size: 13px;
line-height: 1.6;
}
img {
max-width: 100%;
height: auto;
border-radius: 6px;
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
th,
td {
min-width: 80px;
padding: 8px 10px;
border: 1px solid hsl(var(--border));
vertical-align: top;
}
th {
background: hsl(var(--muted));
font-weight: 600;
}
.is-empty::before {
float: left;
height: 0;
color: hsl(var(--muted-foreground));
content: attr(data-placeholder);
pointer-events: none;
}
}
.ProseMirror-selectednode {
outline: 2px solid hsl(var(--primary));
}
}

View File

@ -0,0 +1,381 @@
/* @vitest-environment happy-dom */
/* eslint-disable vue/one-component-per-file */
import type { ComponentPublicInstance, Ref } from 'vue';
import { mount } from '@vue/test-utils';
import { defineComponent, h, nextTick, ref } from 'vue';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import KtTiptapHtmlEditor from './KtTiptapHtmlEditor';
type FakeEditorOptions = {
content?: string;
editable?: boolean;
onBlur?: () => void;
onFocus?: () => void;
onUpdate?: (payload: { editor: FakeEditor }) => void;
};
type TiptapExpose = ComponentPublicInstance & {
getHtml: () => string;
setHtml: (value: string) => void;
};
/**
* Tiptap Editor HTML
*/
class FakeEditor {
public html = '';
public commands = {
focus: () => true,
setContent: (value: string) => {
this.html = value;
return true;
},
};
public destroyed = false;
public editable = true;
public options: FakeEditorOptions;
/**
* HTML
*
* @param options
*/
constructor(options: FakeEditorOptions) {
this.options = options;
this.html = options.content || '';
this.editable = options.editable !== false;
}
public chain = () => ({
focus: () => ({
run: () => true,
setImage: ({ src }: { src: string }) => ({
run: () => {
this.html = `<img src="${src}">`;
this.options.onUpdate?.({ editor: this });
return true;
},
}),
setLink: ({ href }: { href: string }) => ({
run: () => {
this.html = `<p><a href="${href}">${href}</a></p>`;
this.options.onUpdate?.({ editor: this });
return true;
},
}),
toggleBold: () => ({
run: () => {
this.html = '<p><strong>粗体</strong></p>';
this.options.onUpdate?.({ editor: this });
return true;
},
}),
}),
});
/**
* 便
*/
public destroy() {
this.destroyed = true;
}
/**
* HTML Tiptap getHTML
*/
public getHTML() {
return this.html;
}
/**
*
*/
public isActive() {
return false;
}
/**
*
*
* @param value false readonly/disabled
*/
public setEditable(value: boolean) {
this.editable = value;
}
}
const editorRefs: Array<Ref<FakeEditor | null>> = [];
vi.mock('@tiptap/vue-3', () => ({
EditorContent: defineComponent({
name: 'MockEditorContent',
props: {
editor: {
default: null,
type: Object,
},
},
/**
* FakeEditor HTML使
*
* @param props EditorContent
*/
setup(props) {
return () =>
h('div', {
class: 'kt-tiptap-editor__prosemirror ProseMirror',
innerHTML: (props.editor as FakeEditor | null)?.getHTML() || '',
});
},
}),
useEditor: (options: FakeEditorOptions) => {
const editor = ref(new FakeEditor(options));
editorRefs.push(editor);
return editor;
},
}));
vi.mock('antdv-next', () => ({
Button: defineComponent({
name: 'MockButton',
props: {
disabled: Boolean,
type: {
default: undefined,
type: String,
},
},
emits: ['click'],
/**
* button antdv-next Button attrs/disabled/click
*
* @param props disabled
* @param context Vue setup context attrs slots
* @param context.attrs button
* @param context.emit click
* @param context.slots
*/
setup(props, { attrs, emit, slots }) {
return () =>
h(
'button',
{
...attrs,
disabled: props.disabled,
onClick: () => emit('click'),
type: 'button',
},
slots.default?.(),
);
},
}),
Divider: defineComponent({
name: 'MockDivider',
/**
*
*/
setup() {
return () => h('span', { class: 'mock-divider' });
},
}),
Input: defineComponent({
name: 'MockInput',
props: {
value: {
default: '',
type: String,
},
},
emits: ['update:value'],
/**
* input antdv-next Input value
*
* @param props 使 value
* @param context Vue setup context update:value
* @param context.emit update:value
*/
setup(props, { emit }) {
return () =>
h('input', {
value: props.value,
onInput: (event: Event) =>
emit('update:value', (event.target as HTMLInputElement).value),
});
},
}),
Modal: defineComponent({
name: 'MockModal',
props: {
open: Boolean,
title: {
default: '',
type: String,
},
},
emits: ['cancel', 'ok', 'update:open'],
/**
* Modal open=true
*
* @param props open
* @param context Vue setup context
* @param context.slots
*/
setup(props, { slots }) {
return () =>
props.open ? h('div', { role: 'dialog' }, slots.default?.()) : null;
},
}),
Space: defineComponent({
name: 'MockSpace',
/**
* div Space
*
* @param _props 使 Space props
* @param context Vue setup context slot
* @param context.slots Space
*/
setup(_props, { slots }) {
return () => h('div', slots.default?.());
},
}),
Tooltip: defineComponent({
name: 'MockTooltip',
props: {
title: {
default: '',
type: String,
},
},
/**
* Tooltip slot
*
* @param _props 使 Tooltip props
* @param context Vue setup context slot
* @param context.slots Tooltip
*/
setup(_props, { slots }) {
return () => h('span', slots.default?.());
},
}),
}));
vi.mock('@vben/icons', () => ({
IconifyIcon: defineComponent({
name: 'MockIconifyIcon',
props: {
icon: {
default: '',
type: String,
},
},
/**
*
*
* @param props data
*/
setup(props) {
return () => h('i', { 'data-icon': props.icon });
},
}),
}));
describe('ktTiptapHtmlEditor', () => {
beforeEach(() => {
editorRefs.length = 0;
});
it('renders incoming html content', () => {
const wrapper = mount(KtTiptapHtmlEditor, {
props: {
modelValue: '<p>旧正文</p>',
},
});
expect(wrapper.find('.kt-tiptap-editor__prosemirror').html()).toContain(
'<p>旧正文</p>',
);
});
it('emits normalized html from setHtml', async () => {
const wrapper = mount(KtTiptapHtmlEditor, {
props: {
modelValue: '<p>旧正文</p>',
},
});
(wrapper.vm as unknown as TiptapExpose).setHtml(
'<h2>新标题</h2><p>新正文</p>',
);
await nextTick();
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([
'<h2>新标题</h2><p>新正文</p>',
]);
expect(wrapper.emitted('change')?.at(-1)).toEqual([
'<h2>新标题</h2><p>新正文</p>',
'<p>旧正文</p>',
]);
expect((wrapper.vm as unknown as TiptapExpose).getHtml()).toBe(
'<h2>新标题</h2><p>新正文</p>',
);
});
it('marks disabled editor and disables bold command', () => {
const wrapper = mount(KtTiptapHtmlEditor, {
props: {
disabled: true,
modelValue: '<p>旧正文</p>',
},
});
expect(wrapper.classes()).toContain('kt-tiptap-editor--disabled');
expect(
wrapper.find('[data-kt-tiptap-command="bold"]').attributes('disabled'),
).toBeDefined();
});
it('syncs setHtml changes through a v-model parent', async () => {
const Parent = defineComponent({
name: 'TiptapParentHarness',
/**
* v-model emit
*/
setup() {
const html = ref('<p>旧正文</p>');
const editorRef = ref<null | TiptapExpose>(null);
return () => (
<section>
<KtTiptapHtmlEditor ref={editorRef} v-model={html.value} />
<output data-testid="parent-html">{html.value}</output>
<button
data-testid="parent-update"
onClick={() =>
editorRef.value?.setHtml('<h2>新标题</h2><p>新正文</p>')
}
type="button"
>
</button>
</section>
);
},
});
const wrapper = mount(Parent);
await wrapper.find('[data-testid="parent-update"]').trigger('click');
await nextTick();
expect(wrapper.find('[data-testid="parent-html"]').text()).toBe(
'<h2>新标题</h2><p>新正文</p>',
);
});
});

View File

@ -0,0 +1,588 @@
import type { Editor } from '@tiptap/vue-3';
import type { CSSProperties, PropType, VNodeChild } from 'vue';
import { computed, defineComponent, onBeforeUnmount, ref, watch } from 'vue';
import { IconifyIcon } from '@vben/icons';
import Image from '@tiptap/extension-image';
import Link from '@tiptap/extension-link';
import Placeholder from '@tiptap/extension-placeholder';
import { Table } from '@tiptap/extension-table';
import TableCell from '@tiptap/extension-table-cell';
import TableHeader from '@tiptap/extension-table-header';
import TableRow from '@tiptap/extension-table-row';
import TextAlign from '@tiptap/extension-text-align';
import Underline from '@tiptap/extension-underline';
import StarterKit from '@tiptap/starter-kit';
import { EditorContent, useEditor } from '@tiptap/vue-3';
import { Button, Divider, Input, Modal, Space, Tooltip } from 'antdv-next';
import './KtTiptapHtmlEditor.scss';
const AButton = Button as any;
const ADivider = Divider as any;
const AInput = Input as any;
const AModal = Modal as any;
const ASpace = Space as any;
const ATooltip = Tooltip as any;
const TiptapEditorContent = EditorContent as any;
export type KtTiptapHtmlEditorEmit = {
blur: [];
change: [value: string, previousValue: string];
focus: [];
'update:modelValue': [value: string];
};
export interface KtTiptapHtmlEditorExpose {
getEditor: () => Editor | null;
getHtml: () => string;
setHtml: (value: string) => void;
}
export interface KtTiptapHtmlEditorProps {
disabled?: boolean;
minHeight?: number | string;
modelValue?: string;
placeholder?: string;
readonly?: boolean;
}
type ToolbarCommandKey =
| 'align-center'
| 'align-left'
| 'align-right'
| 'blockquote'
| 'bold'
| 'bullet-list'
| 'code-block'
| 'heading-2'
| 'image'
| 'italic'
| 'link'
| 'ordered-list'
| 'redo'
| 'table'
| 'underline'
| 'undo';
type ToolbarCommand = {
icon: string;
isActive?: (editor: Editor) => boolean;
key: ToolbarCommandKey;
run: (editor: Editor) => void;
title: string;
};
/**
* CSS
*
* @param value minHeight CSS
* @returns CSS
*/
function toCssSize(value?: number | string) {
if (value === undefined || value === null || value === '') return undefined;
return typeof value === 'number' ? `${value}px` : value;
}
/**
* HTML退
*
* @param editor Tiptap
* @param fallback HTML
* @returns HTML
*/
function readEditorHtml(editor: Editor | null | undefined, fallback: string) {
return editor?.getHTML() || fallback;
}
/**
* 使 Iconify
*
* @param icon Vben/Iconify lucide
* @returns
*/
const renderIcon = (icon: string) => (
<IconifyIcon class="kt-tiptap-editor__toolbar-icon" icon={icon} />
);
export default defineComponent({
name: 'KtTiptapHtmlEditor',
props: {
disabled: {
default: false,
type: Boolean,
},
minHeight: {
default: 360,
type: [Number, String] as PropType<number | string>,
},
modelValue: {
default: '',
type: String,
},
placeholder: {
default: '请输入正文内容',
type: String,
},
readonly: {
default: false,
type: Boolean,
},
},
emits: {
blur: () => true,
change: (_value: string, _previousValue: string) => true,
focus: () => true,
'update:modelValue': (_value: string) => true,
},
/**
* Tiptap HTML toolbar v-model expose API
*
* @param props HTML
* @param context Vue setup context
* @param context.emit HTMLchangefocus blur
* @param context.expose HTML
*/
setup(props, { emit, expose }) {
const currentHtml = ref(props.modelValue || '');
const linkModalOpen = ref(false);
const linkValue = ref('');
const imageModalOpen = ref(false);
const imageValue = ref('');
const readonlyState = computed(() => props.disabled || props.readonly);
const editorStyle = computed<CSSProperties>(() => ({
'--kt-tiptap-min-height': toCssSize(props.minHeight),
}));
/**
* HTML
*
* @param nextHtml HTML
* @param previousHtml HTML change
*/
function emitHtmlChange(
nextHtml: string,
previousHtml = currentHtml.value,
) {
if (nextHtml === previousHtml) {
currentHtml.value = nextHtml;
return;
}
currentHtml.value = nextHtml;
emit('update:modelValue', nextHtml);
emit('change', nextHtml, previousHtml);
}
const editor = useEditor({
content: currentHtml.value,
editable: !readonlyState.value,
editorProps: {
attributes: {
class: 'kt-tiptap-editor__prosemirror',
},
},
extensions: [
StarterKit.configure({
heading: {
levels: [1, 2, 3],
},
link: false,
underline: false,
}),
Link.configure({
autolink: true,
defaultProtocol: 'https',
openOnClick: false,
}),
Image.configure({
allowBase64: true,
inline: false,
}),
Table.configure({
resizable: true,
}),
TableRow,
TableHeader,
TableCell,
Underline,
TextAlign.configure({
types: ['heading', 'paragraph'],
}),
Placeholder.configure({
placeholder: props.placeholder,
}),
],
onBlur: () => emit('blur'),
onFocus: () => emit('focus'),
onUpdate: ({ editor }) => {
emitHtmlChange(editor.getHTML());
},
});
/**
*
*
* @returns true
*/
function isToolbarDisabled() {
return readonlyState.value || !editor.value;
}
/**
*
*
* @param command
*/
function runToolbarCommand(command: ToolbarCommand) {
const currentEditor = editor.value;
if (!currentEditor || readonlyState.value) return;
command.run(currentEditor);
}
/**
*
*/
function openLinkModal() {
const currentEditor = editor.value;
if (!currentEditor || readonlyState.value) return;
linkValue.value = currentEditor.getAttributes('link')?.href || '';
linkModalOpen.value = true;
}
/**
* URL
*/
function confirmLink() {
const currentEditor = editor.value;
if (!currentEditor) return;
const href = linkValue.value.trim();
if (href) {
currentEditor
.chain()
.focus()
.extendMarkRange('link')
.setLink({ href })
.run();
} else {
currentEditor.chain().focus().extendMarkRange('link').unsetLink().run();
}
linkModalOpen.value = false;
}
/**
*
*/
function openImageModal() {
if (!editor.value || readonlyState.value) return;
imageValue.value = '';
imageModalOpen.value = true;
}
/**
* URL
*/
function confirmImage() {
const currentEditor = editor.value;
const src = imageValue.value.trim();
if (!currentEditor || !src) {
imageModalOpen.value = false;
return;
}
currentEditor.chain().focus().setImage({ src }).run();
imageModalOpen.value = false;
}
const toolbarCommands: ToolbarCommand[] = [
{
icon: 'lucide:bold',
isActive: (editor) => editor.isActive('bold'),
key: 'bold',
run: (editor) => editor.chain().focus().toggleBold().run(),
title: '加粗',
},
{
icon: 'lucide:italic',
isActive: (editor) => editor.isActive('italic'),
key: 'italic',
run: (editor) => editor.chain().focus().toggleItalic().run(),
title: '斜体',
},
{
icon: 'lucide:underline',
isActive: (editor) => editor.isActive('underline'),
key: 'underline',
run: (editor) => editor.chain().focus().toggleUnderline().run(),
title: '下划线',
},
{
icon: 'lucide:heading-2',
isActive: (editor) => editor.isActive('heading', { level: 2 }),
key: 'heading-2',
run: (editor) =>
editor.chain().focus().toggleHeading({ level: 2 }).run(),
title: '二级标题',
},
{
icon: 'lucide:list',
isActive: (editor) => editor.isActive('bulletList'),
key: 'bullet-list',
run: (editor) => editor.chain().focus().toggleBulletList().run(),
title: '无序列表',
},
{
icon: 'lucide:list-ordered',
isActive: (editor) => editor.isActive('orderedList'),
key: 'ordered-list',
run: (editor) => editor.chain().focus().toggleOrderedList().run(),
title: '有序列表',
},
{
icon: 'lucide:quote',
isActive: (editor) => editor.isActive('blockquote'),
key: 'blockquote',
run: (editor) => editor.chain().focus().toggleBlockquote().run(),
title: '引用',
},
{
icon: 'lucide:code-2',
isActive: (editor) => editor.isActive('codeBlock'),
key: 'code-block',
run: (editor) => editor.chain().focus().toggleCodeBlock().run(),
title: '代码块',
},
{
icon: 'lucide:align-left',
isActive: (editor) => editor.isActive({ textAlign: 'left' }),
key: 'align-left',
run: (editor) => editor.chain().focus().setTextAlign('left').run(),
title: '左对齐',
},
{
icon: 'lucide:align-center',
isActive: (editor) => editor.isActive({ textAlign: 'center' }),
key: 'align-center',
run: (editor) => editor.chain().focus().setTextAlign('center').run(),
title: '居中',
},
{
icon: 'lucide:align-right',
isActive: (editor) => editor.isActive({ textAlign: 'right' }),
key: 'align-right',
run: (editor) => editor.chain().focus().setTextAlign('right').run(),
title: '右对齐',
},
{
icon: 'lucide:link',
isActive: (editor) => editor.isActive('link'),
key: 'link',
run: openLinkModal,
title: '链接',
},
{
icon: 'lucide:image',
key: 'image',
run: openImageModal,
title: '图片',
},
{
icon: 'lucide:table',
key: 'table',
run: (editor) =>
editor
.chain()
.focus()
.insertTable({ cols: 3, rows: 3, withHeaderRow: true })
.run(),
title: '表格',
},
{
icon: 'lucide:undo-2',
key: 'undo',
run: (editor) => editor.chain().focus().undo().run(),
title: '撤销',
},
{
icon: 'lucide:redo-2',
key: 'redo',
run: (editor) => editor.chain().focus().redo().run(),
title: '重做',
},
];
/**
*
*
* @param command
* @returns
*/
const renderCommandButton = (command: ToolbarCommand): VNodeChild => {
const currentEditor = editor.value;
const active = currentEditor ? command.isActive?.(currentEditor) : false;
return (
<ATooltip key={command.key} title={command.title}>
<AButton
aria-label={command.title}
class={[
'kt-tiptap-editor__toolbar-button',
active ? 'kt-tiptap-editor__toolbar-button--active' : '',
]}
data-kt-tiptap-command={command.key}
disabled={isToolbarDisabled()}
onClick={() => runToolbarCommand(command)}
shape="circle"
type="text"
>
{renderIcon(command.icon)}
</AButton>
</ATooltip>
);
};
/**
*
*/
const renderToolbar = () => {
const groups = [
toolbarCommands.slice(0, 4),
toolbarCommands.slice(4, 8),
toolbarCommands.slice(8, 11),
toolbarCommands.slice(11, 14),
toolbarCommands.slice(14),
];
return (
<div class="kt-tiptap-editor__toolbar">
<ASpace size={4} wrap>
{groups.map((group, index) => (
<span class="kt-tiptap-editor__toolbar-group" key={index}>
{index > 0 ? <ADivider type="vertical" /> : null}
{group.map((command) => renderCommandButton(command))}
</span>
))}
</ASpace>
</div>
);
};
/**
*
*/
const renderModals = () => (
<>
<AModal
destroyOnHidden
okText="应用"
onCancel={() => {
linkModalOpen.value = false;
}}
onOk={confirmLink}
open={linkModalOpen.value}
title="设置链接"
>
<AInput
allowClear
placeholder="https://example.com"
v-model:value={linkValue.value}
/>
</AModal>
<AModal
destroyOnHidden
okText="插入"
onCancel={() => {
imageModalOpen.value = false;
}}
onOk={confirmImage}
open={imageModalOpen.value}
title="插入图片"
>
<AInput
allowClear
placeholder="https://example.com/image.png"
v-model:value={imageValue.value}
/>
</AModal>
</>
);
/**
*
*/
function getEditor() {
return editor.value || null;
}
/**
* HTML
*/
function getHtml() {
return readEditorHtml(editor.value, currentHtml.value);
}
/**
* Tiptap HTML
*
* @param value HTML
*/
function setHtml(value: string) {
const currentEditor = editor.value;
const previousHtml = currentHtml.value;
if (!currentEditor) {
emitHtmlChange(value, previousHtml);
return;
}
currentEditor.commands.setContent(value);
emitHtmlChange(readEditorHtml(currentEditor, value), previousHtml);
}
expose({
getEditor,
getHtml,
setHtml,
} satisfies KtTiptapHtmlEditorExpose);
watch(
() => props.modelValue,
(value = '') => {
const nextHtml = value || '';
if (nextHtml === currentHtml.value) return;
currentHtml.value = nextHtml;
editor.value?.commands.setContent(nextHtml);
},
);
watch(readonlyState, (value) => {
editor.value?.setEditable(!value);
});
onBeforeUnmount(() => {
editor.value?.destroy();
});
return () => (
<div
class={[
'kt-tiptap-editor',
readonlyState.value ? 'kt-tiptap-editor--disabled' : '',
]}
style={editorStyle.value}
>
{renderToolbar()}
<div class="kt-tiptap-editor__content">
<TiptapEditorContent editor={editor.value} />
</div>
{renderModals()}
</div>
);
},
});

View File

@ -0,0 +1,6 @@
export { default as KtTiptapHtmlEditor } from './KtTiptapHtmlEditor';
export type {
KtTiptapHtmlEditorEmit,
KtTiptapHtmlEditorExpose,
KtTiptapHtmlEditorProps,
} from './KtTiptapHtmlEditor';

View File

@ -57,6 +57,39 @@ catalogs:
'@tanstack/vue-store':
specifier: ^0.8.0
version: 0.8.0
'@tiptap/extension-image':
specifier: ^3.27.1
version: 3.27.1
'@tiptap/extension-link':
specifier: ^3.27.1
version: 3.27.1
'@tiptap/extension-placeholder':
specifier: ^3.27.1
version: 3.27.1
'@tiptap/extension-table':
specifier: ^3.27.1
version: 3.27.1
'@tiptap/extension-table-cell':
specifier: ^3.27.1
version: 3.27.1
'@tiptap/extension-table-header':
specifier: ^3.27.1
version: 3.27.1
'@tiptap/extension-table-row':
specifier: ^3.27.1
version: 3.27.1
'@tiptap/extension-text-align':
specifier: ^3.27.1
version: 3.27.1
'@tiptap/extension-underline':
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
'@types/archiver':
specifier: ^6.0.4
version: 6.0.4
@ -536,6 +569,39 @@ importers:
'@tanstack/vue-query':
specifier: 'catalog:'
version: 5.92.9(vue@3.5.27(typescript@5.9.3))
'@tiptap/extension-image':
specifier: 'catalog:'
version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
'@tiptap/extension-link':
specifier: 'catalog:'
version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
'@tiptap/extension-placeholder':
specifier: 'catalog:'
version: 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-table':
specifier: 'catalog:'
version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
'@tiptap/extension-table-cell':
specifier: 'catalog:'
version: 3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))
'@tiptap/extension-table-header':
specifier: 'catalog:'
version: 3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))
'@tiptap/extension-table-row':
specifier: 'catalog:'
version: 3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))
'@tiptap/extension-text-align':
specifier: 'catalog:'
version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
'@tiptap/extension-underline':
specifier: 'catalog:'
version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
'@tiptap/starter-kit':
specifier: 'catalog:'
version: 3.27.1
'@tiptap/vue-3':
specifier: 'catalog:'
version: 3.27.1(@floating-ui/dom@1.7.5)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(vue@3.5.27(typescript@5.9.3))
'@vben-core/menu-ui':
specifier: workspace:*
version: link:../../packages/@core/ui-kit/menu-ui
@ -3588,6 +3654,189 @@ packages:
peerDependencies:
vue: ^3.5.27
'@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-image@3.27.1':
resolution: {integrity: sha512-+JTahgQT+NxiGjduaB3qJVyhU/wh4m3pVkht1Earioku2bm/apj5Lb8rSowa/NJYP3B+oQgV/V4YLw5dtDgBoA==}
peerDependencies:
'@tiptap/core': 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-placeholder@3.27.1':
resolution: {integrity: sha512-lhcNDcczQ75yJOSywCHb58Hmtg1aPL/2TdYmeLPxVrP048D7rRs133sfONcgyyw0AvhnfmPOkHLTv3QtKSowhw==}
peerDependencies:
'@tiptap/extensions': 3.27.1
'@tiptap/extension-strike@3.27.1':
resolution: {integrity: sha512-Y3DW1jlSlCNCyMGHP3+3qBNNPS83wuFz4RTYGjZtvRRTCRh7apZme9XRWMq1rN5mJ2Cr7fKocA2/5Bs13KgN6Q==}
peerDependencies:
'@tiptap/core': 3.27.1
'@tiptap/extension-table-cell@3.27.1':
resolution: {integrity: sha512-aopweoHugP+ZR5WTgb7mccXXWmjoNv7PBjtQDl8rvS4IWsJE1w23B+rkj7VuoncO8WK5bU3SS9O3l5aObql68w==}
peerDependencies:
'@tiptap/extension-table': 3.27.1
'@tiptap/extension-table-header@3.27.1':
resolution: {integrity: sha512-+J9EyKP6RMInfMJbIoNemqWzHxZe9XZv/7OjpsvtVZO1cxMTd5W1xIg0Ntr7YMdG9eKBC8Vwx5jjFbRm2tbM3A==}
peerDependencies:
'@tiptap/extension-table': 3.27.1
'@tiptap/extension-table-row@3.27.1':
resolution: {integrity: sha512-peEcbNRrJJ9Qc/WNKQNFbkwBrJvBvq22Dp5WNqEygn/ZJez1qD9ctd10+D6f3b9GtSyx12sfDgn8yBufWa+Iug==}
peerDependencies:
'@tiptap/extension-table': 3.27.1
'@tiptap/extension-table@3.27.1':
resolution: {integrity: sha512-tNB8kjxo0+XPremnWkd6NUikpeJ+JQrss7HntL8GgIxX4t0gkdnLn9yUWb0JzcaYP7Y8iTZZHdkPs0P154DG8Q==}
peerDependencies:
'@tiptap/core': 3.27.1
'@tiptap/pm': 3.27.1
'@tiptap/extension-text-align@3.27.1':
resolution: {integrity: sha512-EXawuJBO55wd8WcTbHTMoPhv0CGQxza4yCCPB5Hqz4ZPQwahIr3ej+8yp/kimIl0xokabwZ0/Fu8STQ4AkZv5g==}
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.5.27
'@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
@ -6436,6 +6685,9 @@ packages:
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
linkifyjs@4.3.3:
resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==}
listhen@1.9.0:
resolution: {integrity: sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==}
hasBin: true
@ -11983,6 +12235,200 @@ snapshots:
'@tanstack/virtual-core': 3.13.18
vue: 3.5.27(typescript@5.9.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.5
'@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.5)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)':
dependencies:
'@floating-ui/dom': 1.7.5
'@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-image@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-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-placeholder@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-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-table-cell@3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))':
dependencies:
'@tiptap/extension-table': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
'@tiptap/extension-table-header@3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))':
dependencies:
'@tiptap/extension-table': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
'@tiptap/extension-table-row@3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))':
dependencies:
'@tiptap/extension-table': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
'@tiptap/extension-table@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-text-align@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.7
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.8
'@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.5)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(vue@3.5.27(typescript@5.9.3))':
dependencies:
'@floating-ui/dom': 1.7.5
'@tiptap/core': 3.27.1(@tiptap/pm@3.27.1)
'@tiptap/pm': 3.27.1
vue: 3.5.27(typescript@5.9.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.5)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
'@tybys/wasm-util@0.10.1':
dependencies:
tslib: 2.8.1
@ -15172,6 +15618,8 @@ snapshots:
lines-and-columns@1.2.4: {}
linkifyjs@4.3.3: {}
listhen@1.9.0:
dependencies:
'@parcel/watcher': 2.5.6

View File

@ -39,6 +39,17 @@ catalog:
'@tailwindcss/typography': ^0.5.19
'@tanstack/vue-query': ^5.92.9
'@tanstack/vue-store': ^0.8.0
'@tiptap/extension-image': ^3.27.1
'@tiptap/extension-link': ^3.27.1
'@tiptap/extension-placeholder': ^3.27.1
'@tiptap/extension-table': ^3.27.1
'@tiptap/extension-table-cell': ^3.27.1
'@tiptap/extension-table-header': ^3.27.1
'@tiptap/extension-table-row': ^3.27.1
'@tiptap/extension-text-align': ^3.27.1
'@tiptap/extension-underline': ^3.27.1
'@tiptap/starter-kit': ^3.27.1
'@tiptap/vue-3': ^3.27.1
'@types/archiver': ^6.0.4
'@types/eslint': ^9.6.1
'@types/html-minifier-terser': ^7.0.2