tiptap: refactor utils

This commit is contained in:
fantasticit 2022-04-29 11:56:44 +08:00
parent 6d0a596496
commit a066cea4a7
102 changed files with 334 additions and 306 deletions

View File

@ -18,7 +18,7 @@ import {
getIndexdbProvider, getIndexdbProvider,
destoryIndexdbProvider, destoryIndexdbProvider,
} from 'tiptap'; } from 'tiptap';
import { findMentions } from 'tiptap/utils/find-mention'; import { findMentions } from 'tiptap/prose-utils';
import { useCollaborationDocument } from 'data/document'; import { useCollaborationDocument } from 'data/document';
import { DataRender } from 'components/data-render'; import { DataRender } from 'components/data-render';
import { Banner } from 'components/banner'; import { Banner } from 'components/banner';

View File

@ -1,7 +1,7 @@
import { Node, mergeAttributes } from '@tiptap/core'; import { Node, mergeAttributes } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { AttachmentWrapper } from '../wrappers/attachment'; import { getDatasetAttribute } from 'tiptap/prose-utils';
import { getDatasetAttribute } from '../utils/dataset'; import { AttachmentWrapper } from 'tiptap/wrappers/attachment';
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {

View File

@ -1,28 +1,24 @@
import { Blockquote as BuiltInBlockquote } from '@tiptap/extension-blockquote'; import { Blockquote as BuiltInBlockquote } from '@tiptap/extension-blockquote';
import { wrappingInputRule } from '@tiptap/core'; import { wrappingInputRule } from '@tiptap/core';
import { getParents } from '../utils/dom'; import { getParents } from 'tiptap/prose-utils';
import { getMarkdownSource } from '../markdown/markdown-to-prosemirror'; import { getMarkdownSource } from 'tiptap/markdown/markdown-to-prosemirror';
export const Blockquote = BuiltInBlockquote.extend({ export const Blockquote = BuiltInBlockquote.extend({
addAttributes() { addAttributes() {
return { return {
...this.parent?.(), ...this.parent?.(),
multiline: { multiline: {
default: false, default: false,
parseHTML: (element) => { parseHTML: (element) => {
const source = getMarkdownSource(element); const source = getMarkdownSource(element);
const parentsIncludeBlockquote = getParents(element).some((p) => p.nodeName.toLowerCase() === 'blockquote'); const parentsIncludeBlockquote = getParents(element).some((p) => p.nodeName.toLowerCase() === 'blockquote');
return source && !source.startsWith('>') && !parentsIncludeBlockquote; return source && !source.startsWith('>') && !parentsIncludeBlockquote;
}, },
}, },
}; };
}, },
addInputRules() { addInputRules() {
const multilineInputRegex = /^\s*>>>\s$/gm; const multilineInputRegex = /^\s*>>>\s$/gm;
return [ return [
...this.parent?.(), ...this.parent?.(),
wrappingInputRule({ wrappingInputRule({

View File

@ -1,16 +1,14 @@
import { BulletList as BuiltInBulletList } from '@tiptap/extension-bullet-list'; import { BulletList as BuiltInBulletList } from '@tiptap/extension-bullet-list';
import { getMarkdownSource } from '../markdown/markdown-to-prosemirror'; import { getMarkdownSource } from 'tiptap/markdown/markdown-to-prosemirror';
export const BulletList = BuiltInBulletList.extend({ export const BulletList = BuiltInBulletList.extend({
addAttributes() { addAttributes() {
return { return {
...this.parent?.(), ...this.parent?.(),
bullet: { bullet: {
default: '*', default: '*',
parseHTML(element) { parseHTML(element) {
const bullet = getMarkdownSource(element)?.charAt(0); const bullet = getMarkdownSource(element)?.charAt(0);
return '*+-'.includes(bullet) ? bullet : '*'; return '*+-'.includes(bullet) ? bullet : '*';
}, },
}, },

View File

@ -1,7 +1,6 @@
import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core'; import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { CalloutWrapper } from '../wrappers/callout'; import { CalloutWrapper } from 'tiptap/wrappers/callout';
import { getDatasetAttribute } from '../utils/dataset';
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {
@ -73,7 +72,7 @@ export const Callout = Node.create({
addInputRules() { addInputRules() {
return [ return [
wrappingInputRule({ wrappingInputRule({
find: /^:::([\dA-Za-z]*) $/, find: /^:::callout $/,
type: this.type, type: this.type,
getAttributes: (match) => { getAttributes: (match) => {
return { type: match[1] }; return { type: match[1] };

View File

@ -1,9 +1,9 @@
import { lowlight } from 'lowlight/lib/all';
import { Node, textblockTypeInputRule, mergeAttributes } from '@tiptap/core'; import { Node, textblockTypeInputRule, mergeAttributes } from '@tiptap/core';
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state'; import { Plugin, PluginKey, TextSelection } from 'prosemirror-state';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { LowlightPlugin } from '../utils/lowlight-plugin'; import { lowlight } from 'lowlight/lib/all';
import { CodeBlockWrapper } from '../wrappers/code-block'; import { LowlightPlugin } from 'tiptap/prose-utils';
import { CodeBlockWrapper } from 'tiptap/wrappers/code-block';
export interface CodeBlockOptions { export interface CodeBlockOptions {
/** /**

View File

@ -1,5 +1,5 @@
import BuiltInCode from '@tiptap/extension-code'; import BuiltInCode from '@tiptap/extension-code';
import { EXTENSION_PRIORITY_LOWER } from '../constants'; import { EXTENSION_PRIORITY_LOWER } from 'tiptap/constants';
export const Code = BuiltInCode.extend({ export const Code = BuiltInCode.extend({
excludes: null, excludes: null,

View File

@ -1,6 +1,6 @@
import { Extension } from '@tiptap/core'; import { Extension } from '@tiptap/core';
import { Plugin } from 'prosemirror-state'; import { Plugin } from 'prosemirror-state';
import { findColors } from '../utils/color'; import { findColors } from 'tiptap/prose-utils';
export const ColorHighlighter = Extension.create({ export const ColorHighlighter = Extension.create({
name: 'colorHighlighter', name: 'colorHighlighter',

View File

@ -1,7 +1,7 @@
import { Node, mergeAttributes } from '@tiptap/core'; import { Node, mergeAttributes } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { CountdownWrapper } from '../wrappers/countdown'; import { CountdownWrapper } from 'tiptap/wrappers/countdown';
import { getDatasetAttribute } from '../utils/dataset'; import { getDatasetAttribute } from 'tiptap/prose-utils';
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {

View File

@ -1,7 +1,7 @@
import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core'; import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { DocumentChildrenWrapper } from '../wrappers/document-children'; import { DocumentChildrenWrapper } from 'tiptap/wrappers/document-children';
import { getDatasetAttribute } from '../utils/dataset'; import { getDatasetAttribute } from 'tiptap/prose-utils';
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {

View File

@ -1,7 +1,7 @@
import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core'; import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { DocumentReferenceWrapper } from '../wrappers/document-reference'; import { DocumentReferenceWrapper } from 'tiptap/wrappers/document-reference';
import { getDatasetAttribute } from '../utils/dataset'; import { getDatasetAttribute } from 'tiptap/prose-utils';
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {

View File

@ -1,12 +1,11 @@
import { Node, findParentNode } from '@tiptap/core'; import { Node } from '@tiptap/core';
import { ReactRenderer } from '@tiptap/react'; import { ReactRenderer } from '@tiptap/react';
import { Plugin, PluginKey } from 'prosemirror-state'; import { Plugin, PluginKey } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
import Suggestion from '@tiptap/suggestion'; import Suggestion from '@tiptap/suggestion';
import tippy from 'tippy.js'; import tippy from 'tippy.js';
import { EXTENSION_PRIORITY_HIGHEST } from 'tiptap/constants'; import { EXTENSION_PRIORITY_HIGHEST } from 'tiptap/constants';
import { EmojiList } from '../wrappers/emoji-list'; import { EmojiList } from 'tiptap/wrappers/emoji-list';
import { emojiSearch, emojisToName } from '../wrappers/emoji-list/emojis'; import { emojiSearch, emojisToName } from 'tiptap/wrappers/emoji-list/emojis';
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {

View File

@ -1,6 +1,6 @@
import { Mark, mergeAttributes, markInputRule } from '@tiptap/core'; import { Mark, mergeAttributes, markInputRule } from '@tiptap/core';
import { PARSE_HTML_PRIORITY_LOWEST } from '../constants'; import { PARSE_HTML_PRIORITY_LOWEST } from 'tiptap/constants';
import { markInputRegex, extractMarkAttributesFromMatch } from '../utils/mark-utils'; import { markInputRegex, extractMarkAttributesFromMatch } from 'tiptap/prose-utils';
export const marks = [{ name: 'underline', tag: 'u' }]; export const marks = [{ name: 'underline', tag: 'u' }];

View File

@ -1,7 +1,7 @@
import { Node, mergeAttributes } from '@tiptap/core'; import { Node, mergeAttributes } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { IframeWrapper } from '../wrappers/iframe'; import { IframeWrapper } from 'tiptap/wrappers/iframe';
import { getDatasetAttribute } from '../utils/dataset'; import { getDatasetAttribute } from 'tiptap/prose-utils';
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {

View File

@ -1,6 +1,6 @@
import { Image as BuiltInImage } from '@tiptap/extension-image'; import { Image as BuiltInImage } from '@tiptap/extension-image';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { ImageWrapper } from '../wrappers/image'; import { ImageWrapper } from 'tiptap/wrappers/image';
const resolveImageEl = (element) => (element.nodeName === 'IMG' ? element : element.querySelector('img')); const resolveImageEl = (element) => (element.nodeName === 'IMG' ? element : element.querySelector('img'));

View File

@ -1,16 +1,7 @@
import { Command, Extension } from '@tiptap/core'; import { Command, Extension } from '@tiptap/core';
import { sinkListItem, liftListItem } from 'prosemirror-schema-list'; import { sinkListItem, liftListItem } from 'prosemirror-schema-list';
import { TextSelection, AllSelection, Transaction } from 'prosemirror-state'; import { TextSelection, AllSelection, Transaction } from 'prosemirror-state';
import { isListActive } from '../utils/is-active'; import { isListActive, getNodeType } from 'tiptap/prose-utils';
import { clamp } from '../utils/clamp';
import { getNodeType } from '../utils/type';
import { isListNode } from '../utils/node';
type IndentOptions = {
types: string[];
indentLevels: number[];
defaultIndentLevel: number;
};
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface Commands { interface Commands {
@ -21,35 +12,12 @@ declare module '@tiptap/core' {
} }
} }
export enum IndentProps { export type Options = {
min = 0, type: 'space' | 'tab';
max = 210, size: number;
more = 30,
less = -30,
}
function setNodeIndentMarkup(tr: Transaction, pos: number, delta: number): Transaction {
if (!tr.doc) return tr;
const node = tr.doc.nodeAt(pos);
if (!node) return tr;
const minIndent = IndentProps.min;
const maxIndent = IndentProps.max;
const indent = clamp((node.attrs.indent || 0) + delta, minIndent, maxIndent);
if (indent === node.attrs.indent) return tr;
const nodeAttrs = {
...node.attrs,
indent,
}; };
return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks); const updateIndent = (tr: Transaction, options: Options): Transaction => {
}
function updateIndentLevel(tr: Transaction, delta: number): Transaction {
const { doc, selection } = tr; const { doc, selection } = tr;
if (!doc || !selection) return tr; if (!doc || !selection) return tr;
@ -58,50 +26,23 @@ function updateIndentLevel(tr: Transaction, delta: number): Transaction {
return tr; return tr;
} }
const { from, to } = selection; const { to } = selection;
doc.nodesBetween(from, to, (node, pos) => { const text = options.type === 'space' ? Array(options.size).fill(' ').join('') : '\t';
const nodeType = node.type;
if (nodeType.name === 'paragraph' || nodeType.name === 'heading') { return tr.insertText(text, to);
tr = setNodeIndentMarkup(tr, pos, delta); };
return false;
}
if (isListNode(node)) {
return false;
}
return true;
});
return tr; export const Indent = Extension.create<Options>({
}
export const Indent = Extension.create<IndentOptions>({
name: 'indent', name: 'indent',
addOptions() { addOptions() {
return { const options: Options = {
types: ['heading', 'paragraph'], type: 'space',
indentLevels: [0, 30, 60, 90, 120, 150, 180, 210], size: 2,
defaultIndentLevel: 0,
}; };
},
addGlobalAttributes() { return options;
return [
{
types: this.options.types,
attributes: {
indent: {
default: this.options.defaultIndentLevel,
renderHTML: (attributes) => ({
style: `margin-left: ${attributes.indent}px!important;`,
}),
parseHTML: (element) => parseInt(element.style.marginLeft) || this.options.defaultIndentLevel,
},
},
},
];
}, },
addCommands() { addCommands() {
@ -115,15 +56,11 @@ export const Indent = Extension.create<IndentOptions>({
return sinkListItem(type)(state, dispatch); return sinkListItem(type)(state, dispatch);
} }
const { selection } = state; const _tr = updateIndent(tr, this.options);
tr = tr.setSelection(selection); if (_tr.docChanged) {
tr = updateIndentLevel(tr, IndentProps.more); dispatch?.(_tr);
if (tr.docChanged) {
dispatch && dispatch(tr);
return true; return true;
} }
return false; return false;
}, },
outdent: outdent:
@ -135,15 +72,11 @@ export const Indent = Extension.create<IndentOptions>({
return liftListItem(type)(state, dispatch); return liftListItem(type)(state, dispatch);
} }
const { selection } = state; const _tr = updateIndent(tr, this.options);
tr = tr.setSelection(selection); if (_tr.docChanged) {
tr = updateIndentLevel(tr, IndentProps.less); dispatch?.(_tr);
if (tr.docChanged) {
dispatch && dispatch(tr);
return true; return true;
} }
return false; return false;
}, },
}; };

View File

@ -1,6 +1,6 @@
import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core'; import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { KatexWrapper } from '../wrappers/katex'; import { KatexWrapper } from 'tiptap/wrappers/katex';
type IKatexAttrs = { type IKatexAttrs = {
text?: string; text?: string;

View File

@ -1,6 +1,6 @@
import { Node } from '@tiptap/core'; import { Node } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { LoadingWrapper } from '../wrappers/loading'; import { LoadingWrapper } from 'tiptap/wrappers/loading';
export const Loading = Node.create({ export const Loading = Node.create({
name: 'loading', name: 'loading',

View File

@ -2,8 +2,8 @@ import BulitInMention from '@tiptap/extension-mention';
import { ReactRenderer } from '@tiptap/react'; import { ReactRenderer } from '@tiptap/react';
import tippy from 'tippy.js'; import tippy from 'tippy.js';
import { getUsers } from 'services/user'; import { getUsers } from 'services/user';
import { getDatasetAttribute } from '../utils/dataset'; import { getDatasetAttribute } from 'tiptap/prose-utils';
import { MentionList } from '../wrappers/mention-list'; import { MentionList } from 'tiptap/wrappers/mention-list';
const suggestion = { const suggestion = {
items: async ({ query }) => { items: async ({ query }) => {

View File

@ -1,7 +1,7 @@
import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core'; import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { MindWrapper } from '../wrappers/mind'; import { MindWrapper } from 'tiptap/wrappers/mind';
import { getDatasetAttribute } from '../utils/dataset'; import { getDatasetAttribute } from 'tiptap/prose-utils';
const DEFAULT_MIND_DATA = { const DEFAULT_MIND_DATA = {
root: { data: { text: '中心节点' }, children: [] }, root: { data: { text: '中心节点' }, children: [] },

View File

@ -1,11 +1,10 @@
import { OrderedList as BuiltInOrderedList } from '@tiptap/extension-ordered-list'; import { OrderedList as BuiltInOrderedList } from '@tiptap/extension-ordered-list';
import { getMarkdownSource } from '../markdown/markdown-to-prosemirror'; import { getMarkdownSource } from 'tiptap/markdown/markdown-to-prosemirror';
export const OrderedList = BuiltInOrderedList.extend({ export const OrderedList = BuiltInOrderedList.extend({
addAttributes() { addAttributes() {
return { return {
...this.parent?.(), ...this.parent?.(),
parens: { parens: {
default: false, default: false,
parseHTML: (element) => /^[0-9]+\)/.test(getMarkdownSource(element)), parseHTML: (element) => /^[0-9]+\)/.test(getMarkdownSource(element)),

View File

@ -1,15 +1,29 @@
import { Extension } from '@tiptap/core'; import { Extension } from '@tiptap/core';
import { Plugin, PluginKey } from 'prosemirror-state'; import { Plugin, PluginKey } from 'prosemirror-state';
import { EXTENSION_PRIORITY_HIGHEST } from '../constants'; import { EXTENSION_PRIORITY_HIGHEST } from 'tiptap/constants';
import { handleFileEvent } from '../utils/upload'; import { handleFileEvent, isInCode, LANGUAGES, isTitleNode } from 'tiptap/prose-utils';
import { isInCode, LANGUAGES } from '../utils/code';
import { import {
isMarkdown, isMarkdown,
normalizePastedMarkdown, normalizePastedMarkdown,
markdownToProsemirror, markdownToProsemirror,
prosemirrorToMarkdown, prosemirrorToMarkdown,
} from '../markdown/markdown-to-prosemirror'; } from 'tiptap/markdown/markdown-to-prosemirror';
import { isTitleNode } from '../utils/node';
const isPureText = (content): boolean => {
if (!content) return false;
if (Array.isArray(content)) {
if (content.length > 1) return false;
return isPureText(content[0]);
}
const child = content['content'];
if (child) {
return isPureText(child);
}
return content['type'] === 'text';
};
export const Paste = Extension.create({ export const Paste = Extension.create({
name: 'paste', name: 'paste',
@ -25,6 +39,7 @@ export const Paste = Extension.create({
if (view.props.editable && !view.props.editable(view.state)) { if (view.props.editable && !view.props.editable(view.state)) {
return false; return false;
} }
if (!event.clipboardData) return false; if (!event.clipboardData) return false;
const files = Array.from(event.clipboardData.files); const files = Array.from(event.clipboardData.files);
@ -116,16 +131,18 @@ export const Paste = Extension.create({
return false; return false;
}, },
clipboardTextSerializer: (slice) => { clipboardTextSerializer: (slice) => {
const doc = slice.content; const isText = isPureText(slice.content.toJSON());
if (isText) {
return slice.content.textBetween(0, slice.content.size, '\n\n');
}
const doc = slice.content;
if (!doc) { if (!doc) {
return ''; return '';
} }
const content = prosemirrorToMarkdown({ const content = prosemirrorToMarkdown({
content: doc, content: doc,
}); });
return content; return content;
}, },
}, },

View File

@ -1,11 +1,10 @@
import { Node, findParentNode } from '@tiptap/core'; import tippy from 'tippy.js';
import { Node } from '@tiptap/core';
import { ReactRenderer } from '@tiptap/react'; import { ReactRenderer } from '@tiptap/react';
import { Plugin, PluginKey } from 'prosemirror-state'; import { Plugin, PluginKey } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
import Suggestion from '@tiptap/suggestion'; import Suggestion from '@tiptap/suggestion';
import tippy from 'tippy.js'; import { MenuList } from 'tiptap/wrappers/menu-list';
import { MenuList } from '../wrappers/menu-list'; import { QUICK_INSERT_ITEMS } from 'tiptap/menus/quick-insert';
import { QUICK_INSERT_ITEMS } from '../menus/quick-insert';
import { EXTENSION_PRIORITY_HIGHEST } from 'tiptap/constants'; import { EXTENSION_PRIORITY_HIGHEST } from 'tiptap/constants';
export const QuickInsertPluginKey = new PluginKey('quickInsert'); export const QuickInsertPluginKey = new PluginKey('quickInsert');

View File

@ -1,8 +1,8 @@
import { Extension } from '@tiptap/core'; import { Extension } from '@tiptap/core';
import { Plugin, PluginKey, NodeSelection, TextSelection, Selection, AllSelection } from 'prosemirror-state'; import { Plugin, PluginKey, NodeSelection, TextSelection, Selection, AllSelection } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view'; import { Decoration, DecorationSet } from 'prosemirror-view';
import { getCurrentNode, isInCodeBlock, isInCallout } from '../utils/node'; import { getCurrentNode, isInCodeBlock, isInCallout } from 'tiptap/prose-utils';
import { EXTENSION_PRIORITY_HIGHEST } from '../constants'; import { EXTENSION_PRIORITY_HIGHEST } from 'tiptap/constants';
export const selectionPluginKey = new PluginKey('selection'); export const selectionPluginKey = new PluginKey('selection');

View File

@ -1,7 +1,7 @@
import { Node, mergeAttributes } from '@tiptap/core'; import { Node, mergeAttributes } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { StatusWrapper } from '../wrappers/status'; import { StatusWrapper } from 'tiptap/wrappers/status';
import { getDatasetAttribute } from '../utils/dataset'; import { getDatasetAttribute } from 'tiptap/prose-utils';
type IStatusAttrs = { type IStatusAttrs = {
color?: string; color?: string;

View File

@ -13,8 +13,8 @@ import {
isTableSelected, isTableSelected,
selectRow, selectRow,
selectTable, selectTable,
} from '../utils/table'; } from 'tiptap/prose-utils';
import { FloatMenuView } from '../views/float-menu'; import { FloatMenuView } from 'tiptap/views/float-menu';
export const TableCell = BuiltInTableCell.extend({ export const TableCell = BuiltInTableCell.extend({
addAttributes() { addAttributes() {

View File

@ -6,7 +6,7 @@ import { IconDelete, IconPlus } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { Plugin, PluginKey } from 'prosemirror-state'; import { Plugin, PluginKey } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view'; import { Decoration, DecorationSet } from 'prosemirror-view';
import { getCellsInRow, isColumnSelected, isTableSelected, selectColumn } from '../utils/table'; import { getCellsInRow, isColumnSelected, isTableSelected, selectColumn } from 'tiptap/prose-utils';
import { FloatMenuView } from '../views/float-menu'; import { FloatMenuView } from '../views/float-menu';
export const TableHeader = BuiltInTableHeader.extend({ export const TableHeader = BuiltInTableHeader.extend({

View File

@ -1,10 +1,8 @@
import { wrappingInputRule } from '@tiptap/core'; import { wrappingInputRule } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react';
import { TaskItem as BuiltInTaskItem } from '@tiptap/extension-task-item'; import { TaskItem as BuiltInTaskItem } from '@tiptap/extension-task-item';
import { Plugin } from 'prosemirror-state'; import { Plugin } from 'prosemirror-state';
import { findParentNodeClosestToPos } from 'prosemirror-utils'; import { findParentNodeClosestToPos } from 'prosemirror-utils';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; import { PARSE_HTML_PRIORITY_HIGHEST } from 'tiptap/constants';
import { TaskItemWrapper } from '../wrappers/task-item';
const CustomTaskItem = BuiltInTaskItem.extend({ const CustomTaskItem = BuiltInTaskItem.extend({
parseHTML() { parseHTML() {

View File

@ -1,5 +1,5 @@
import { TaskList as BuiltInTaskList } from '@tiptap/extension-task-list'; import { TaskList as BuiltInTaskList } from '@tiptap/extension-task-list';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; import { PARSE_HTML_PRIORITY_HIGHEST } from 'tiptap/constants';
export const TaskList = BuiltInTaskList.extend({ export const TaskList = BuiltInTaskList.extend({
parseHTML() { parseHTML() {

View File

@ -1,7 +1,6 @@
import { Node, mergeAttributes } from '@tiptap/core'; import { Node, mergeAttributes } from '@tiptap/core';
import { Plugin, PluginKey } from 'prosemirror-state'; import { Plugin, PluginKey, TextSelection } from 'prosemirror-state';
import { isInTitle } from '../utils/node'; import { isInTitle } from 'tiptap/prose-utils';
import { TextSelection } from 'prosemirror-state';
export interface TitleOptions { export interface TitleOptions {
HTMLAttributes: Record<string, any>; HTMLAttributes: Record<string, any>;

View File

@ -1,6 +1,6 @@
import { HocuspocusProvider } from '@hocuspocus/provider'; import { HocuspocusProvider } from '@hocuspocus/provider';
import { Collaboration } from './collaboration'; import { Collaboration } from './extensions/collaboration';
import { CollaborationCursor } from './collaboration-cursor'; import { CollaborationCursor } from './extensions/collaboration-cursor';
import History from '@tiptap/extension-history'; import History from '@tiptap/extension-history';
import { getRandomColor } from 'helpers/color'; import { getRandomColor } from 'helpers/color';
import { Document } from './extensions/document'; import { Document } from './extensions/document';

View File

@ -1,5 +1,5 @@
import container from 'markdown-it-container'; import container from 'markdown-it-container';
import { strToJSON, jsonToDOMDataset } from '../../../utils/dataset'; import { strToJSON, jsonToDOMDataset } from 'tiptap/prose-utils';
export const createMarkdownContainer = (types: string | Array<string>) => (md) => { export const createMarkdownContainer = (types: string | Array<string>) => (md) => {
if (!Array.isArray(types)) { if (!Array.isArray(types)) {

View File

@ -1,4 +1,4 @@
import { jsonToStr } from '../../utils/dataset'; import { jsonToStr } from 'tiptap/prose-utils';
const uniq = (arr: string[]) => [...new Set(arr)]; const uniq = (arr: string[]) => [...new Set(arr)];

View File

@ -1,40 +1,40 @@
import { MarkdownSerializer as ProseMirrorMarkdownSerializer, defaultMarkdownSerializer } from 'prosemirror-markdown'; import { MarkdownSerializer as ProseMirrorMarkdownSerializer, defaultMarkdownSerializer } from 'prosemirror-markdown';
import { Attachment } from '../../extensions/attachment'; import { Attachment } from 'tiptap/extensions/attachment';
import { Bold } from '../../extensions/bold'; import { Bold } from 'tiptap/extensions/bold';
import { BulletList } from '../../extensions/bullet-list'; import { BulletList } from 'tiptap/extensions/bullet-list';
import { Callout } from '../../extensions/callout'; import { Callout } from 'tiptap/extensions/callout';
import { Code } from '../../extensions/code'; import { Code } from 'tiptap/extensions/code';
import { CodeBlock } from '../../extensions/code-block'; import { CodeBlock } from 'tiptap/extensions/code-block';
import { Countdown } from '../../extensions/countdown'; import { Countdown } from 'tiptap/extensions/countdown';
import { DocumentChildren } from '../../extensions/document-children'; import { DocumentChildren } from 'tiptap/extensions/document-children';
import { DocumentReference } from '../../extensions/document-reference'; import { DocumentReference } from 'tiptap/extensions/document-reference';
import { HardBreak } from '../../extensions/hard-break'; import { HardBreak } from 'tiptap/extensions/hard-break';
import { Heading } from '../../extensions/heading'; import { Heading } from 'tiptap/extensions/heading';
import { HorizontalRule } from '../../extensions/horizontal-rule'; import { HorizontalRule } from 'tiptap/extensions/horizontal-rule';
import { marks } from '../../extensions/html-marks'; import { marks } from 'tiptap/extensions/html-marks';
import { Mention } from '../../extensions/mention'; import { Mention } from 'tiptap/extensions/mention';
import { Iframe } from '../../extensions/iframe'; import { Iframe } from 'tiptap/extensions/iframe';
import { Image } from '../../extensions/image'; import { Image } from 'tiptap/extensions/image';
import { Italic } from '../../extensions/italic'; import { Italic } from 'tiptap/extensions/italic';
import { Katex } from '../../extensions/katex'; import { Katex } from 'tiptap/extensions/katex';
import { Link } from '../../extensions/link'; import { Link } from 'tiptap/extensions/link';
import { ListItem } from '../../extensions/listItem'; import { ListItem } from 'tiptap/extensions/listItem';
import { Mind } from '../../extensions/mind'; import { Mind } from 'tiptap/extensions/mind';
import { OrderedList } from '../../extensions/ordered-list'; import { OrderedList } from 'tiptap/extensions/ordered-list';
import { Paragraph } from '../../extensions/paragraph'; import { Paragraph } from 'tiptap/extensions/paragraph';
import { Status } from '../../extensions/status'; import { Status } from 'tiptap/extensions/status';
import { Strike } from '../../extensions/strike'; import { Strike } from 'tiptap/extensions/strike';
import { Subscript } from '../../extensions/subscript'; import { Subscript } from 'tiptap/extensions/subscript';
import { Superscript } from '../../extensions/superscript'; import { Superscript } from 'tiptap/extensions/superscript';
import { Table } from '../../extensions/table'; import { Table } from 'tiptap/extensions/table';
import { TableCell } from '../../extensions/table-cell'; import { TableCell } from 'tiptap/extensions/table-cell';
import { TableHeader } from '../../extensions/table-header'; import { TableHeader } from 'tiptap/extensions/table-header';
import { TableRow } from '../../extensions/table-row'; import { TableRow } from 'tiptap/extensions/table-row';
import { Text } from '../../extensions/text'; import { Text } from 'tiptap/extensions/text';
import { TaskItem } from '../../extensions/task-item'; import { TaskItem } from 'tiptap/extensions/task-item';
import { TaskList } from '../../extensions/task-list'; import { TaskList } from 'tiptap/extensions/task-list';
import { TextStyle } from '../../extensions/text-style'; import { TextStyle } from 'tiptap/extensions/text-style';
import { Title } from '../../extensions/title'; import { Title } from 'tiptap/extensions/title';
import { import {
isPlainURL, isPlainURL,
renderHardBreak, renderHardBreak,

View File

@ -1,7 +1,6 @@
import { useCallback, useRef } from 'react'; import { useCallback, useRef } from 'react';
import { Button, Form, Dropdown } from '@douyinfe/semi-ui'; import { Button, Form, Dropdown } from '@douyinfe/semi-ui';
import { FormApi } from '@douyinfe/semi-ui/lib/es/form'; import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
import { number } from 'lib0';
type ISize = { width: number; height: number }; type ISize = { width: number; height: number };

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Editor } from '@tiptap/core'; import { Editor } from '@tiptap/core';
import { Button, Dropdown, Tooltip } from '@douyinfe/semi-ui'; import { Button, Dropdown, Tooltip } from '@douyinfe/semi-ui';
import { IconAlignLeft, IconAlignCenter, IconAlignRight, IconAlignJustify } from '@douyinfe/semi-icons'; import { IconAlignLeft, IconAlignCenter, IconAlignRight, IconAlignJustify } from '@douyinfe/semi-icons';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const Align: React.FC<{ editor: Editor }> = ({ editor }) => { export const Align: React.FC<{ editor: Editor }> = ({ editor }) => {
const current = (() => { const current = (() => {

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconMark } from '@douyinfe/semi-icons'; import { IconMark } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
import { ColorPicker } from '../_components/color-picker'; import { ColorPicker } from '../_components/color-picker';
export const BackgroundColor: React.FC<{ editor: Editor }> = ({ editor }) => { export const BackgroundColor: React.FC<{ editor: Editor }> = ({ editor }) => {

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { IconQuote } from 'components/icons'; import { IconQuote } from 'components/icons';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const Blockquote: React.FC<{ editor: Editor }> = ({ editor }) => { export const Blockquote: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconBold } from '@douyinfe/semi-icons'; import { IconBold } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const Bold: React.FC<{ editor: Editor }> = ({ editor }) => { export const Bold: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconList } from 'components/icons'; import { IconList } from 'components/icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const BulletList: React.FC<{ editor: Editor }> = ({ editor }) => { export const BulletList: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -1,14 +1,14 @@
import { useCallback } from 'react';
import { Editor } from '@tiptap/core'; import { Editor } from '@tiptap/core';
import { Space, Button, Popover, Typography } from '@douyinfe/semi-ui'; import { Space, Button, Popover, Typography } from '@douyinfe/semi-ui';
import { IconDelete } from '@douyinfe/semi-icons'; import { IconDelete } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { IconDrawBoard } from 'components/icons'; import { IconDrawBoard } from 'components/icons';
import { BubbleMenu } from '../../views/bubble-menu'; import { BubbleMenu } from 'tiptap/views/bubble-menu';
import { Divider } from '../../divider'; import { Divider } from 'tiptap/divider';
import { Callout } from '../../extensions/callout'; import { Callout } from 'tiptap/extensions/callout';
import { deleteNode } from '../../utils/delete-node'; import { deleteNode } from 'tiptap/prose-utils';
import styles from './bubble.module.scss'; import styles from './bubble.module.scss';
import { useCallback } from 'react';
const { Text } = Typography; const { Text } = Typography;

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconCode } from '@douyinfe/semi-icons'; import { IconCode } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const Code: React.FC<{ editor: Editor }> = ({ editor }) => { export const Code: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -2,9 +2,9 @@ import { useCallback } from 'react';
import { Space, Button } from '@douyinfe/semi-ui'; import { Space, Button } from '@douyinfe/semi-ui';
import { IconEdit, IconDelete } from '@douyinfe/semi-icons'; import { IconEdit, IconDelete } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { BubbleMenu } from '../../views/bubble-menu'; import { BubbleMenu } from 'tiptap/views/bubble-menu';
import { Countdown } from '../../extensions/countdown'; import { Countdown } from 'tiptap/extensions/countdown';
import { Divider } from '../../divider'; import { Divider } from 'tiptap/divider';
import { triggerOpenCountSettingModal } from '../_event'; import { triggerOpenCountSettingModal } from '../_event';
export const CountdownBubbleMenu = ({ editor }) => { export const CountdownBubbleMenu = ({ editor }) => {

View File

@ -2,8 +2,8 @@ import { useCallback } from 'react';
import { Space, Button } from '@douyinfe/semi-ui'; import { Space, Button } from '@douyinfe/semi-ui';
import { IconDelete } from '@douyinfe/semi-icons'; import { IconDelete } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { BubbleMenu } from '../../views/bubble-menu'; import { BubbleMenu } from 'tiptap/views/bubble-menu';
import { DocumentChildren } from '../../extensions/document-children'; import { DocumentChildren } from 'tiptap/extensions/document-children';
export const DocumentChildrenBubbleMenu = ({ editor }) => { export const DocumentChildrenBubbleMenu = ({ editor }) => {
const deleteNode = useCallback(() => editor.chain().deleteSelection().run(), [editor]); const deleteNode = useCallback(() => editor.chain().deleteSelection().run(), [editor]);

View File

@ -6,9 +6,9 @@ import { Tooltip } from 'components/tooltip';
import { DataRender } from 'components/data-render'; import { DataRender } from 'components/data-render';
import { IconDocument } from 'components/icons'; import { IconDocument } from 'components/icons';
import { useWikiTocs } from 'data/wiki'; import { useWikiTocs } from 'data/wiki';
import { BubbleMenu } from '../../views/bubble-menu'; import { BubbleMenu } from 'tiptap/views/bubble-menu';
import { DocumentReference } from '../../extensions/document-reference'; import { DocumentReference } from 'tiptap/extensions/document-reference';
import { Divider } from '../../divider'; import { Divider } from 'tiptap/divider';
const { Text } = Typography; const { Text } = Typography;

View File

@ -1,7 +1,7 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { Select } from '@douyinfe/semi-ui'; import { Select } from '@douyinfe/semi-ui';
import { Editor } from '@tiptap/core'; import { Editor } from '@tiptap/core';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const FONT_SIZES = [12, 13, 14, 15, 16, 19, 22, 24, 29, 32, 40, 48]; export const FONT_SIZES = [12, 13, 14, 15, 16, 19, 22, 24, 29, 32, 40, 48];

View File

@ -1,7 +1,7 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { Editor } from '@tiptap/core'; import { Editor } from '@tiptap/core';
import { Select } from '@douyinfe/semi-ui'; import { Select } from '@douyinfe/semi-ui';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
const getCurrentCaretTitle = (editor) => { const getCurrentCaretTitle = (editor) => {
if (editor.isActive('heading', { level: 1 })) return 1; if (editor.isActive('heading', { level: 1 })) return 1;

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { IconHorizontalRule } from 'components/icons'; import { IconHorizontalRule } from 'components/icons';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const HorizontalRule: React.FC<{ editor: Editor }> = ({ editor }) => { export const HorizontalRule: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconIndentLeft, IconIndentRight } from '@douyinfe/semi-icons'; import { IconIndentLeft, IconIndentRight } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const Ident: React.FC<{ editor: Editor }> = ({ editor }) => { export const Ident: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -4,9 +4,9 @@ import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
import { IconEdit, IconExternalOpen, IconLineHeight, IconDelete } from '@douyinfe/semi-icons'; import { IconEdit, IconExternalOpen, IconLineHeight, IconDelete } from '@douyinfe/semi-icons';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { BubbleMenu } from '../../views/bubble-menu'; import { BubbleMenu } from 'tiptap/views/bubble-menu';
import { Iframe } from '../../extensions/iframe'; import { Iframe } from 'tiptap/extensions/iframe';
import { Divider } from '../../divider'; import { Divider } from 'tiptap/divider';
import { Size } from '../_components/size'; import { Size } from '../_components/size';
const { Text } = Typography; const { Text } = Typography;

View File

@ -2,11 +2,11 @@ import React, { useEffect, useState } from 'react';
import { Space, Button } from '@douyinfe/semi-ui'; import { Space, Button } from '@douyinfe/semi-ui';
import { IconAlignLeft, IconAlignCenter, IconAlignRight, IconLineHeight, IconDelete } from '@douyinfe/semi-icons'; import { IconAlignLeft, IconAlignCenter, IconAlignRight, IconLineHeight, IconDelete } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { BubbleMenu } from '../../views/bubble-menu'; import { BubbleMenu } from 'tiptap/views/bubble-menu';
import { Divider } from '../../divider'; import { Divider } from 'tiptap/divider';
import { Image } from '../../extensions/image'; import { Image } from 'tiptap/extensions/image';
import { getEditorContainerDOMSize } from 'tiptap/prose-utils';
import { Size } from '../_components/size'; import { Size } from '../_components/size';
import { getEditorContainerDOMSize } from '../../utils/editor';
export const ImageBubbleMenu = ({ editor }) => { export const ImageBubbleMenu = ({ editor }) => {
const attrs = editor.getAttributes(Image.name); const attrs = editor.getAttributes(Image.name);

View File

@ -20,8 +20,7 @@ import { GridSelect } from 'components/grid-select';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { useUser } from 'data/user'; import { useUser } from 'data/user';
import { createKeysLocalStorageLRUCache } from 'helpers/lru-cache'; import { createKeysLocalStorageLRUCache } from 'helpers/lru-cache';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive, getEditorContainerDOMSize } from 'tiptap/prose-utils';
import { getEditorContainerDOMSize } from '../../utils/editor';
import { createCountdown } from '../countdown/service'; import { createCountdown } from '../countdown/service';
const insertMenuLRUCache = createKeysLocalStorageLRUCache('TIPTAP_INSERT_MENU', 3); const insertMenuLRUCache = createKeysLocalStorageLRUCache('TIPTAP_INSERT_MENU', 3);

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconItalic } from '@douyinfe/semi-icons'; import { IconItalic } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const Italic: React.FC<{ editor: Editor }> = ({ editor }) => { export const Italic: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -2,11 +2,10 @@ import { useEffect, useState, useRef, useCallback } from 'react';
import { Space, Button } from '@douyinfe/semi-ui'; import { Space, Button } from '@douyinfe/semi-ui';
import { IconExternalOpen, IconUnlink, IconEdit } from '@douyinfe/semi-icons'; import { IconExternalOpen, IconUnlink, IconEdit } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { Divider } from '../../divider'; import { Divider } from 'tiptap/divider';
import { BubbleMenu } from '../../views/bubble-menu'; import { BubbleMenu } from 'tiptap/views/bubble-menu';
import { Link } from '../../extensions/link'; import { Link } from 'tiptap/extensions/link';
import { isMarkActive } from '../../utils/is-active'; import { isMarkActive, findMarkPosition } from 'tiptap/prose-utils';
import { findMarkPosition } from '../../utils/find-position';
import { triggerOpenLinkSettingModal } from '../_event'; import { triggerOpenLinkSettingModal } from '../_event';
export const LinkBubbleMenu = ({ editor }) => { export const LinkBubbleMenu = ({ editor }) => {

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { IconLink } from 'components/icons'; import { IconLink } from 'components/icons';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
import { createOrToggleLink } from './service'; import { createOrToggleLink } from './service';
import { LinkBubbleMenu } from './bubble'; import { LinkBubbleMenu } from './bubble';
import { LinkSettingModal } from './modal'; import { LinkSettingModal } from './modal';

View File

@ -3,7 +3,7 @@ import { Form, Modal } from '@douyinfe/semi-ui';
import { FormApi } from '@douyinfe/semi-ui/lib/es/form'; import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
import { Editor } from '@tiptap/core'; import { Editor } from '@tiptap/core';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { isValidURL } from '../../utils/valid-url'; import { isValidURL } from 'tiptap/prose-utils';
import { event, OPEN_LINK_SETTING_MODAL } from '../_event'; import { event, OPEN_LINK_SETTING_MODAL } from '../_event';
type IProps = { editor: Editor }; type IProps = { editor: Editor };

View File

@ -1,5 +1,5 @@
import { Editor } from '@tiptap/core'; import { Editor } from '@tiptap/core';
import { isMarkActive } from '../../utils/is-active'; import { isMarkActive } from 'tiptap/prose-utils';
import { triggerOpenLinkSettingModal } from '../_event'; import { triggerOpenLinkSettingModal } from '../_event';
/** /**

View File

@ -2,11 +2,11 @@ import { useCallback } from 'react';
import { Space, Button } from '@douyinfe/semi-ui'; import { Space, Button } from '@douyinfe/semi-ui';
import { IconLineHeight, IconDelete } from '@douyinfe/semi-icons'; import { IconLineHeight, IconDelete } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { BubbleMenu } from '../../views/bubble-menu'; import { BubbleMenu } from 'tiptap/views/bubble-menu';
import { Mind } from '../../extensions/mind'; import { Mind } from 'tiptap/extensions/mind';
import { Divider } from 'tiptap/divider';
import { getEditorContainerDOMSize } from 'tiptap/prose-utils';
import { Size } from '../_components/size'; import { Size } from '../_components/size';
import { Divider } from '../../divider';
import { getEditorContainerDOMSize } from '../../utils/editor';
export const MindBubbleMenu = ({ editor }) => { export const MindBubbleMenu = ({ editor }) => {
const attrs = editor.getAttributes(Mind.name); const attrs = editor.getAttributes(Mind.name);

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconOrderedList } from 'components/icons'; import { IconOrderedList } from 'components/icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const OrderedList: React.FC<{ editor: Editor }> = ({ editor }) => { export const OrderedList: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -3,7 +3,7 @@ import { Popover, Button, Typography, Input, Space } from '@douyinfe/semi-ui';
import { Editor } from '@tiptap/core'; import { Editor } from '@tiptap/core';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { IconSearchReplace } from 'components/icons'; import { IconSearchReplace } from 'components/icons';
import { SearchNReplace } from '../../extensions/search'; import { SearchNReplace } from 'tiptap/extensions/search';
const { Text } = Typography; const { Text } = Typography;

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconStrikeThrough } from '@douyinfe/semi-icons'; import { IconStrikeThrough } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const Strike: React.FC<{ editor: Editor }> = ({ editor }) => { export const Strike: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconSub } from 'components/icons'; import { IconSub } from 'components/icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const Subscript: React.FC<{ editor: any }> = ({ editor }) => { export const Subscript: React.FC<{ editor: any }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconSup } from 'components/icons'; import { IconSup } from 'components/icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const Superscript: React.FC<{ editor: any }> = ({ editor }) => { export const Superscript: React.FC<{ editor: any }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -14,9 +14,9 @@ import {
IconTableHeaderCell, IconTableHeaderCell,
} from 'components/icons'; } from 'components/icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { Divider } from '../../divider'; import { Divider } from 'tiptap/divider';
import { BubbleMenu } from '../../views/bubble-menu'; import { BubbleMenu } from 'tiptap/views/bubble-menu';
import { Table } from '../../extensions/table'; import { Table } from 'tiptap/extensions/table';
export const TableBubbleMenu = ({ editor }) => { export const TableBubbleMenu = ({ editor }) => {
return ( return (

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { IconTask } from 'components/icons'; import { IconTask } from 'components/icons';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const TaskList: React.FC<{ editor: Editor }> = ({ editor }) => { export const TaskList: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconFont } from '@douyinfe/semi-icons'; import { IconFont } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
import { ColorPicker } from '../_components/color-picker'; import { ColorPicker } from '../_components/color-picker';
export const TextColor: React.FC<{ editor: Editor }> = ({ editor }) => { export const TextColor: React.FC<{ editor: Editor }> = ({ editor }) => {

View File

@ -3,7 +3,7 @@ import { Editor } from '@tiptap/core';
import { Button } from '@douyinfe/semi-ui'; import { Button } from '@douyinfe/semi-ui';
import { IconUnderline } from '@douyinfe/semi-icons'; import { IconUnderline } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from 'tiptap/prose-utils';
export const Underline: React.FC<{ editor: Editor }> = ({ editor }) => { export const Underline: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) { if (!editor) {

View File

@ -1,7 +1,7 @@
import { EditorState } from 'prosemirror-state'; import { EditorState } from 'prosemirror-state';
// @ts-ignore // @ts-ignore
import { lowlight } from 'lowlight'; import { lowlight } from 'lowlight';
import { isMarkActive } from './is-active'; import { isMarkActive } from './active';
export const LANGUAGES = lowlight.listLanguages().reduce((a, language) => { export const LANGUAGES = lowlight.listLanguages().reduce((a, language) => {
a[language] = language; a[language] = language;

View File

@ -53,6 +53,23 @@ export const normalizeFileType = (fileType): FileType => {
return 'file'; return 'file';
}; };
export const readImageAsBase64 = (file: File): Promise<{ alt: string; src: string }> => {
return new Promise((resolve) => {
const reader = new FileReader();
reader.addEventListener(
'load',
() => {
resolve({
alt: file.name,
src: reader.result as string,
});
},
false
);
reader.readAsDataURL(file);
});
};
export const getImageWidthHeight = (url: string): Promise<{ width: number | string; height: number | string }> => { export const getImageWidthHeight = (url: string): Promise<{ width: number | string; height: number | string }> => {
return new Promise((resolve) => { return new Promise((resolve) => {
const img = document.createElement('img'); const img = document.createElement('img');

View File

@ -0,0 +1,20 @@
export * from './active';
export * from './clamp';
export * from './code';
export * from './color';
export * from './delete-node';
export * from './dom-dataset';
export * from './dom';
export * from './download';
export * from './editor-container-size';
export * from './file';
export * from './lowlight-plugin';
export * from './mark';
export * from './mention';
export * from './node';
export * from './position';
export * from './table';
export * from './text';
export * from './type';
export * from './upload';
export * from './url';

View File

@ -1,5 +1,22 @@
import { EditorState } from 'prosemirror-state'; import { EditorState } from 'prosemirror-state';
export const markInputRegex = (tag) => new RegExp(`(<(${tag})((?: \\w+=".+?")+)?>([^<]+)</${tag}>)$`, 'gm');
export const extractMarkAttributesFromMatch = ([, , , attrsString]) => {
const attrRegex = /(\w+)="(.+?)"/g;
const attrs = {};
let key;
let value;
do {
[, key, value] = attrRegex.exec(attrsString) || [];
if (key) attrs[key] = value;
} while (key);
return attrs;
};
export function findMarkPosition(state: EditorState, mark, from, to) { export function findMarkPosition(state: EditorState, mark, from, to) {
let markPos = { start: -1, end: -1 }; let markPos = { start: -1, end: -1 };
state.doc.nodesBetween(from, to, (node, pos) => { state.doc.nodesBetween(from, to, (node, pos) => {

View File

@ -0,0 +1,61 @@
/* Copyright 2021, Milkdown by Mirone. */
import type { EditorView } from 'prosemirror-view';
type Point = [top: number, left: number];
export const calculateNodePosition = (
view: EditorView,
target: HTMLElement,
handler: (selectedRect: DOMRect, targetRect: DOMRect, parentRect: DOMRect) => Point
) => {
const state = view.state;
const { from } = state.selection;
const { node } = view.domAtPos(from);
const element = node instanceof Text ? node.parentElement : node;
if (!(element instanceof HTMLElement)) {
throw new Error();
}
const selectedNodeRect = element.getBoundingClientRect();
const targetNodeRect = target.getBoundingClientRect();
const parentNodeRect = target.parentElement?.getBoundingClientRect();
if (!parentNodeRect) {
throw new Error();
}
const [top, left] = handler(selectedNodeRect, targetNodeRect, parentNodeRect);
target.style.top = top + 'px';
target.style.left = left + 'px';
};
type Rect = {
left: number;
right: number;
top: number;
bottom: number;
};
export const calculateTextPosition = (
view: EditorView,
target: HTMLElement,
handler: (start: Rect, end: Rect, targetRect: DOMRect, parentRect: DOMRect) => Point
) => {
const state = view.state;
const { from, to } = state.selection;
const start = view.coordsAtPos(from);
const end = view.coordsAtPos(to);
const targetNodeRect = target.getBoundingClientRect();
const parent = target.parentElement;
if (!parent) {
throw new Error();
}
const parentNodeRect = parent.getBoundingClientRect();
const [top, left] = handler(start, end, targetNodeRect, parentNodeRect);
target.style.top = top + 'px';
target.style.left = left + 'px';
};

View File

@ -0,0 +1,16 @@
type R = Record<string, unknown>;
export const isPureText = (content: R | R[] | undefined | null): boolean => {
if (!content) return false;
if (Array.isArray(content)) {
if (content.length > 1) return false;
return isPureText(content[0]);
}
const child = content['content'];
if (child) {
return isPureText(child as R[]);
}
return content['type'] === 'text';
};

View File

@ -1,21 +0,0 @@
import { InputRule, wrappingInputRule } from '@tiptap/core';
/**
* Wrapping input handler that will append the content of the last match
*
* @param {RegExp} find find param for the wrapping input rule
* @param {object} type Node Type object
* @param {*} getAttributes handler to get the attributes
*/
export function listInputRule(find, type, getAttributes = null) {
const handler = ({ state, range, match }) => {
const wrap = wrappingInputRule({ find, type, getAttributes });
// @ts-ignore
wrap.handler({ state, range, match });
// Insert the first character after bullet if there is one
if (match.length >= 3) {
state.tr.insertText(match[2]);
}
};
return new InputRule({ find, handler });
}

View File

@ -1,16 +0,0 @@
export const markInputRegex = (tag) => new RegExp(`(<(${tag})((?: \\w+=".+?")+)?>([^<]+)</${tag}>)$`, 'gm');
export const extractMarkAttributesFromMatch = ([, , , attrsString]) => {
const attrRegex = /(\w+)="(.+?)"/g;
const attrs = {};
let key;
let value;
do {
[, key, value] = attrRegex.exec(attrsString) || [];
if (key) attrs[key] = value;
} while (key);
return attrs;
};

View File

@ -1,5 +1,5 @@
import { IconFile, IconSong, IconVideo, IconImage } from '@douyinfe/semi-icons'; import { IconFile, IconSong, IconVideo, IconImage } from '@douyinfe/semi-icons';
import { normalizeFileType } from '../../utils/file'; import { normalizeFileType } from 'tiptap/prose-utils';
export const getFileTypeIcon = (fileType: string) => { export const getFileTypeIcon = (fileType: string) => {
const type = normalizeFileType(fileType); const type = normalizeFileType(fileType);

View File

@ -5,9 +5,8 @@ import { Button, Typography, Spin, Collapsible, Space } from '@douyinfe/semi-ui'
import { IconDownload, IconPlayCircle, IconClose } from '@douyinfe/semi-icons'; import { IconDownload, IconPlayCircle, IconClose } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { download } from '../../utils/download'; import { download, normalizeFileSize, extractFileExtension, extractFilename } from 'tiptap/prose-utils';
import { uploadFile } from 'services/file'; import { uploadFile } from 'services/file';
import { normalizeFileSize, extractFileExtension, extractFilename } from '../../utils/file';
import { Player } from './player'; import { Player } from './player';
import { getFileTypeIcon } from './file-icon'; import { getFileTypeIcon } from './file-icon';
import styles from './index.module.scss'; import styles from './index.module.scss';

View File

@ -7,7 +7,7 @@ import {
extractFilename, extractFilename,
normalizeFileType, normalizeFileType,
FileType, FileType,
} from '../../../utils/file'; } from 'tiptap/prose-utils';
import { PDFPlayer } from './pdf-player'; import { PDFPlayer } from './pdf-player';
import styles from './index.module.scss'; import styles from './index.module.scss';

View File

@ -1,10 +1,10 @@
import { useMemo } from 'react';
import { NodeViewWrapper } from '@tiptap/react'; import { NodeViewWrapper } from '@tiptap/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Link from 'next/link'; import Link from 'next/link';
import cls from 'classnames'; import cls from 'classnames';
import { IconDocument } from 'components/icons'; import { IconDocument } from 'components/icons';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { useMemo } from 'react';
export const DocumentReferenceWrapper = ({ editor, node, updateAttributes }) => { export const DocumentReferenceWrapper = ({ editor, node, updateAttributes }) => {
const { pathname } = useRouter(); const { pathname } = useRouter();

View File

@ -3,7 +3,7 @@ import cls from 'classnames';
import { NodeViewWrapper } from '@tiptap/react'; import { NodeViewWrapper } from '@tiptap/react';
import { Typography } from '@douyinfe/semi-ui'; import { Typography } from '@douyinfe/semi-ui';
import { Resizeable } from 'components/resizeable'; import { Resizeable } from 'components/resizeable';
import { getEditorContainerDOMSize } from '../../utils/editor'; import { getEditorContainerDOMSize } from 'tiptap/prose-utils';
import styles from './index.module.scss'; import styles from './index.module.scss';
const { Text } = Typography; const { Text } = Typography;

Some files were not shown because too many files have changed in this diff Show More