mirror of https://github.com/fantasticit/think.git
commit
36f74a408a
|
@ -0,0 +1,146 @@
|
||||||
|
import { Extension } from '@tiptap/core';
|
||||||
|
import { Plugin } from 'prosemirror-state';
|
||||||
|
import { NodeSelection } from 'prosemirror-state';
|
||||||
|
import { __serializeForClipboard } from 'prosemirror-view';
|
||||||
|
|
||||||
|
function createRect(rect) {
|
||||||
|
if (rect == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const newRect = {
|
||||||
|
left: rect.left + document.body.scrollLeft,
|
||||||
|
top: rect.top + document.body.scrollTop,
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
};
|
||||||
|
newRect.bottom = newRect.top + newRect.height;
|
||||||
|
newRect.right = newRect.left + newRect.width;
|
||||||
|
return newRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
function absoluteRect(element) {
|
||||||
|
return createRect(element.getBoundingClientRect());
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Dragable = Extension.create({
|
||||||
|
name: 'dragable',
|
||||||
|
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
let dropElement;
|
||||||
|
let currentNode;
|
||||||
|
let editorView;
|
||||||
|
const WIDTH = 24;
|
||||||
|
|
||||||
|
function drag(e) {
|
||||||
|
if (!currentNode || currentNode.nodeType !== 1) return;
|
||||||
|
|
||||||
|
let pos = null;
|
||||||
|
const desc = editorView.docView.nearestDesc(currentNode, true);
|
||||||
|
|
||||||
|
if (!(!desc || desc === editorView.docView)) {
|
||||||
|
pos = desc.posBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pos) return;
|
||||||
|
|
||||||
|
editorView.dispatch(editorView.state.tr.setSelection(NodeSelection.create(editorView.state.doc, pos)));
|
||||||
|
const slice = editorView.state.selection.content();
|
||||||
|
const { dom, text } = __serializeForClipboard(editorView, slice);
|
||||||
|
e.dataTransfer.clearData();
|
||||||
|
e.dataTransfer.setData('text/html', dom.innerHTML);
|
||||||
|
e.dataTransfer.setData('text/plain', text);
|
||||||
|
editorView.dragging = { slice, move: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
new Plugin({
|
||||||
|
view(view) {
|
||||||
|
if (view.editable) {
|
||||||
|
editorView = view;
|
||||||
|
dropElement = document.createElement('div');
|
||||||
|
dropElement.setAttribute('draggable', 'true');
|
||||||
|
dropElement.className = 'drag-handler';
|
||||||
|
dropElement.addEventListener('dragstart', drag);
|
||||||
|
view.dom.parentElement.appendChild(dropElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
update(view) {
|
||||||
|
editorView = view;
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (dropElement && dropElement.parentNode) {
|
||||||
|
dropElement.removeEventListener('dragstart', drag);
|
||||||
|
dropElement.parentNode.removeChild(dropElement);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
handleDOMEvents: {
|
||||||
|
drop() {
|
||||||
|
if (!dropElement) return;
|
||||||
|
|
||||||
|
dropElement.style.opacity = 0;
|
||||||
|
setTimeout(() => {
|
||||||
|
const node = document.querySelector('.ProseMirror-hideselection');
|
||||||
|
if (node) {
|
||||||
|
node.classList.remove('ProseMirror-hideselection');
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
},
|
||||||
|
mousemove(view, event) {
|
||||||
|
if (!dropElement) return;
|
||||||
|
|
||||||
|
const coords = { left: event.clientX, top: event.clientY };
|
||||||
|
const pos = view.posAtCoords(coords);
|
||||||
|
|
||||||
|
if (!pos) {
|
||||||
|
dropElement.style.opacity = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = view.domAtPos(pos.pos);
|
||||||
|
|
||||||
|
node = node.node;
|
||||||
|
|
||||||
|
while (node && node.parentNode) {
|
||||||
|
if (node.parentNode?.classList?.contains?.('ProseMirror')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node || !node.getBoundingClientRect) {
|
||||||
|
dropElement.style.opacity = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node?.classList?.contains('node-title') || node?.classList?.contains('node-table')) {
|
||||||
|
dropElement.style.opacity = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentNode = node;
|
||||||
|
|
||||||
|
const rect = absoluteRect(node);
|
||||||
|
const win = node.ownerDocument.defaultView;
|
||||||
|
rect.top += win.pageYOffset;
|
||||||
|
rect.left += win.pageXOffset;
|
||||||
|
rect.width = WIDTH + 'px';
|
||||||
|
dropElement.style.left = rect.left - WIDTH + 'px';
|
||||||
|
dropElement.style.top = rect.top + 6 + 'px';
|
||||||
|
dropElement.style.opacity = 1;
|
||||||
|
},
|
||||||
|
mouseleave() {
|
||||||
|
if (!dropElement || currentNode) return;
|
||||||
|
dropElement.style.opacity = 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,12 +1,3 @@
|
||||||
import TitapParagraph from '@tiptap/extension-paragraph';
|
import TitapParagraph from '@tiptap/extension-paragraph';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
|
||||||
|
|
||||||
import { ParagraphWrapper } from '../wrappers/paragraph';
|
export const Paragraph = TitapParagraph.extend({});
|
||||||
|
|
||||||
export const Paragraph = TitapParagraph.extend({
|
|
||||||
draggable: true,
|
|
||||||
|
|
||||||
addNodeView() {
|
|
||||||
return ReactNodeViewRenderer(ParagraphWrapper);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,45 +1,135 @@
|
||||||
import { wrappingInputRule } from '@tiptap/core';
|
import { mergeAttributes, Node, wrappingInputRule } from '@tiptap/core';
|
||||||
import { TaskItem as BuiltInTaskItem } from '@tiptap/extension-task-item';
|
|
||||||
import { Plugin } from 'prosemirror-state';
|
export interface TaskItemOptions {
|
||||||
import { findParentNodeClosestToPos } from 'prosemirror-utils';
|
nested: boolean;
|
||||||
import { PARSE_HTML_PRIORITY_HIGHEST } from 'tiptap/core/constants';
|
HTMLAttributes: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const inputRegex = /^\s*(\[([ |x])\])\s$/;
|
||||||
|
|
||||||
|
export const TaskItem = Node.create<TaskItemOptions>({
|
||||||
|
name: 'taskItem',
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
nested: false,
|
||||||
|
HTMLAttributes: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
content() {
|
||||||
|
return this.options.nested ? 'paragraph block*' : 'paragraph+';
|
||||||
|
},
|
||||||
|
|
||||||
|
defining: true,
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
checked: {
|
||||||
|
default: false,
|
||||||
|
keepOnSplit: false,
|
||||||
|
parseHTML: (element) => element.getAttribute('data-checked') === 'true',
|
||||||
|
renderHTML: (attributes) => ({
|
||||||
|
'data-checked': attributes.checked,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
const CustomTaskItem = BuiltInTaskItem.extend({
|
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: 'li.task-list-item',
|
tag: `li.task-list-item`,
|
||||||
priority: PARSE_HTML_PRIORITY_HIGHEST,
|
priority: 51,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
addInputRules() {
|
renderHTML({ node, HTMLAttributes }) {
|
||||||
return [
|
return [
|
||||||
...this.parent(),
|
'li',
|
||||||
wrappingInputRule({
|
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { 'data-type': this.name }),
|
||||||
find: /^\s*([-+*])\s(\[(x|X| ?)\])\s$/,
|
[
|
||||||
type: this.type,
|
'label',
|
||||||
getAttributes: (match) => ({
|
[
|
||||||
checked: 'xX'.includes(match[match.length - 1]),
|
'input',
|
||||||
}),
|
{
|
||||||
}),
|
type: 'checkbox',
|
||||||
|
checked: node.attrs.checked ? 'checked' : null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['span'],
|
||||||
|
],
|
||||||
|
['div', 0],
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addKeyboardShortcuts() {
|
||||||
|
const shortcuts = {
|
||||||
|
'Enter': () => this.editor.commands.splitListItem(this.name),
|
||||||
|
'Shift-Tab': () => this.editor.commands.liftListItem(this.name),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this.options.nested) {
|
||||||
|
return shortcuts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...shortcuts,
|
||||||
|
Tab: () => this.editor.commands.sinkListItem(this.name),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
addNodeView() {
|
addNodeView() {
|
||||||
return ({ node, HTMLAttributes, getPos, editor }) => {
|
return ({ node, HTMLAttributes, getPos, editor }) => {
|
||||||
const listItem = document.createElement('li');
|
const listItem = document.createElement('li');
|
||||||
const checkboxWrapper = document.createElement('span');
|
const checkboxWrapper = document.createElement('label');
|
||||||
|
const checkboxStyler = document.createElement('span');
|
||||||
|
const checkbox = document.createElement('input');
|
||||||
const content = document.createElement('div');
|
const content = document.createElement('div');
|
||||||
|
|
||||||
checkboxWrapper.contentEditable = 'false';
|
checkboxWrapper.contentEditable = 'false';
|
||||||
|
checkbox.type = 'checkbox';
|
||||||
|
checkbox.addEventListener('change', (event) => {
|
||||||
|
// if the editor isn’t editable
|
||||||
|
// we have to undo the latest change
|
||||||
|
// if (!editor.isEditable) {
|
||||||
|
// checkbox.checked = !checkbox.checked;
|
||||||
|
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
const { checked } = event.target as any;
|
||||||
|
|
||||||
|
if (typeof getPos === 'function') {
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.focus(undefined, { scrollIntoView: false })
|
||||||
|
.command(({ tr }) => {
|
||||||
|
const position = getPos();
|
||||||
|
const currentNode = tr.doc.nodeAt(position);
|
||||||
|
|
||||||
|
tr.setNodeMarkup(position, undefined, {
|
||||||
|
...currentNode?.attrs,
|
||||||
|
checked,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => {
|
Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => {
|
||||||
listItem.setAttribute(key, value);
|
listItem.setAttribute(key, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
listItem.dataset.checked = node.attrs.checked;
|
listItem.dataset.checked = node.attrs.checked;
|
||||||
|
if (node.attrs.checked) {
|
||||||
|
checkbox.setAttribute('checked', 'checked');
|
||||||
|
}
|
||||||
|
|
||||||
|
checkboxWrapper.append(checkbox, checkboxStyler);
|
||||||
listItem.append(checkboxWrapper, content);
|
listItem.append(checkboxWrapper, content);
|
||||||
|
|
||||||
Object.entries(HTMLAttributes).forEach(([key, value]) => {
|
Object.entries(HTMLAttributes).forEach(([key, value]) => {
|
||||||
|
@ -55,61 +145,34 @@ const CustomTaskItem = BuiltInTaskItem.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
listItem.dataset.checked = updatedNode.attrs.checked;
|
listItem.dataset.checked = updatedNode.attrs.checked;
|
||||||
|
if (updatedNode.attrs.checked) {
|
||||||
|
checkbox.setAttribute('checked', 'checked');
|
||||||
|
} else {
|
||||||
|
checkbox.removeAttribute('checked');
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
addProseMirrorPlugins() {
|
addInputRules() {
|
||||||
const extensionThis = this;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
new Plugin({
|
wrappingInputRule({
|
||||||
props: {
|
find: inputRegex,
|
||||||
handleClick: (view, pos, event) => {
|
type: this.type,
|
||||||
const state = view.state;
|
getAttributes: (match) => ({
|
||||||
const schema = state.schema;
|
checked: match[match.length - 1] === 'x',
|
||||||
|
}),
|
||||||
const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY });
|
}),
|
||||||
const position = state.doc.resolve(coordinates.pos);
|
wrappingInputRule({
|
||||||
const parentList = findParentNodeClosestToPos(position, function (node) {
|
find: /^\s*([-+*])\s(\[(x|X| ?)\])\s$/,
|
||||||
return node.type === schema.nodes.taskItem || node.type === schema.nodes.listItem;
|
type: this.type,
|
||||||
});
|
getAttributes: (match) => ({
|
||||||
if (!parentList) {
|
checked: 'xX'.includes(match[match.length - 1]),
|
||||||
return false;
|
}),
|
||||||
}
|
|
||||||
const element = view.nodeDOM(parentList.pos) as HTMLLIElement;
|
|
||||||
if (element.tagName.toLowerCase() !== 'li') return false;
|
|
||||||
|
|
||||||
// 编辑模式:仅当点击 SPAN 时进行状态修改
|
|
||||||
if (view.editable) {
|
|
||||||
const target = event.target as HTMLElement;
|
|
||||||
if (target.tagName.toLowerCase() !== 'span') return false;
|
|
||||||
} else {
|
|
||||||
// 非编辑模式,仅支持配置 taskItemClickable 可点击
|
|
||||||
// @ts-ignore
|
|
||||||
if (!extensionThis.editor.options.editorProps.taskItemClickable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parentElement = element.parentElement;
|
|
||||||
const type = parentElement && parentElement.getAttribute('data-type');
|
|
||||||
if (!type || type.toLowerCase() !== 'tasklist') return false;
|
|
||||||
|
|
||||||
const tr = state.tr;
|
|
||||||
const nextValue = !(element.getAttribute('data-checked') === 'true');
|
|
||||||
tr.setNodeMarkup(parentList.pos, schema.nodes.taskItem, {
|
|
||||||
checked: nextValue,
|
|
||||||
});
|
|
||||||
view.dispatch(tr);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const TaskItem = CustomTaskItem.configure({ nested: true });
|
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
margin-top: 0.75rem;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
.drag-handler {
|
||||||
|
position: fixed;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
cursor: grab;
|
||||||
|
opacity: 1;
|
||||||
|
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 16"><path fill-opacity="0.2" d="M4 14c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM2 6C.9 6 0 6.9 0 8s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6C.9 0 0 .9 0 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" /></svg>');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
}
|
|
@ -17,3 +17,4 @@
|
||||||
@import './table.scss';
|
@import './table.scss';
|
||||||
@import './title.scss';
|
@import './title.scss';
|
||||||
@import './kityminder.scss';
|
@import './kityminder.scss';
|
||||||
|
@import './drag.scss';
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
> span {
|
> label {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 6px;
|
top: 6px;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -63,6 +63,10 @@
|
||||||
border: 1px solid var(--semi-color-border);
|
border: 1px solid var(--semi-color-border);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|
||||||
|
> input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -0.357143px;
|
top: -0.357143px;
|
||||||
|
@ -87,7 +91,7 @@
|
||||||
&[data-checked='true'] {
|
&[data-checked='true'] {
|
||||||
color: var(--semi-color-text-2);
|
color: var(--semi-color-text-2);
|
||||||
|
|
||||||
> span {
|
> label {
|
||||||
background-color: var(--semi-color-primary);
|
background-color: var(--semi-color-primary);
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
.dragHandle {
|
.dragHandle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.3rem;
|
top: 0.3rem;
|
||||||
left: -1.5rem;
|
left: -24px;
|
||||||
width: 1rem;
|
width: 16px;
|
||||||
height: 1rem;
|
height: 16px;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 16"><path fill-opacity="0.2" d="M4 14c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM2 6C.9 6 0 6.9 0 8s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6C.9 0 0 .9 0 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" /></svg>');
|
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 16"><path fill-opacity="0.2" d="M4 14c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM2 6C.9 6 0 6.9 0 8s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6C.9 0 0 .9 0 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" /></svg>');
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
.paragraph {
|
|
||||||
margin-top: 0.75em;
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { NodeViewContent } from '@tiptap/react';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
|
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
|
||||||
|
|
||||||
export const ParagraphWrapper = ({ editor }) => {
|
|
||||||
const prevent = useCallback((e) => {
|
|
||||||
e.prevntDefault();
|
|
||||||
return false;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DragableWrapper editor={editor} className={styles.paragraph}>
|
|
||||||
<NodeViewContent draggable="false" onDragStart={prevent} />
|
|
||||||
</DragableWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -16,6 +16,7 @@ import { Countdown } from 'tiptap/core/extensions/countdown';
|
||||||
import { Document } from 'tiptap/core/extensions/document';
|
import { Document } from 'tiptap/core/extensions/document';
|
||||||
import { DocumentChildren } from 'tiptap/core/extensions/document-children';
|
import { DocumentChildren } from 'tiptap/core/extensions/document-children';
|
||||||
import { DocumentReference } from 'tiptap/core/extensions/document-reference';
|
import { DocumentReference } from 'tiptap/core/extensions/document-reference';
|
||||||
|
import { Dragable } from 'tiptap/core/extensions/dragable';
|
||||||
import { Dropcursor } from 'tiptap/core/extensions/dropcursor';
|
import { Dropcursor } from 'tiptap/core/extensions/dropcursor';
|
||||||
import { Emoji } from 'tiptap/core/extensions/emoji';
|
import { Emoji } from 'tiptap/core/extensions/emoji';
|
||||||
import { EventEmitter } from 'tiptap/core/extensions/event-emitter';
|
import { EventEmitter } from 'tiptap/core/extensions/event-emitter';
|
||||||
|
@ -98,6 +99,7 @@ export const CollaborationKit = [
|
||||||
CodeBlock,
|
CodeBlock,
|
||||||
Color,
|
Color,
|
||||||
ColorHighlighter,
|
ColorHighlighter,
|
||||||
|
Dragable,
|
||||||
Dropcursor,
|
Dropcursor,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
Focus,
|
Focus,
|
||||||
|
|
Loading…
Reference in New Issue