mirror of https://github.com/fantasticit/think.git
tiptap: ensure only one title node, do not create paragraph if next sibling is exists
This commit is contained in:
parent
1b99a0876b
commit
a7e787d758
|
@ -2,7 +2,7 @@ import { mergeAttributes, Node } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state';
|
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state';
|
||||||
import { Decoration, DecorationSet } from 'prosemirror-view';
|
import { Decoration, DecorationSet } from 'prosemirror-view';
|
||||||
import { getDatasetAttribute, isInTitle, nodeAttrsToDataset } from 'tiptap/prose-utils';
|
import { getDatasetAttribute, getNodeAtPos, isInTitle, nodeAttrsToDataset } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
import { TitleWrapper } from '../wrappers/title';
|
import { TitleWrapper } from '../wrappers/title';
|
||||||
|
|
||||||
|
@ -78,15 +78,29 @@ export const Title = Node.create<TitleOptions>({
|
||||||
addProseMirrorPlugins() {
|
addProseMirrorPlugins() {
|
||||||
const { editor } = this;
|
const { editor } = this;
|
||||||
let shouldSelectTitleNode = true;
|
let shouldSelectTitleNode = true;
|
||||||
|
let editorView;
|
||||||
|
|
||||||
|
const closeSelectTitleNode = () => {
|
||||||
|
shouldSelectTitleNode = false;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
new Plugin({
|
new Plugin({
|
||||||
key: TitlePluginKey,
|
key: TitlePluginKey,
|
||||||
|
view: (view) => {
|
||||||
|
editorView = view;
|
||||||
|
|
||||||
|
return {
|
||||||
|
update(view) {
|
||||||
|
editorView = view;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
decorations: (state) => {
|
decorations: (state) => {
|
||||||
const { doc } = state;
|
const { doc } = state;
|
||||||
const decorations = [];
|
const decorations = [];
|
||||||
|
|
||||||
doc.descendants((node, pos) => {
|
doc.descendants((node, pos) => {
|
||||||
if (node.type.name !== this.name) return;
|
if (node.type.name !== this.name) return;
|
||||||
|
|
||||||
|
@ -96,34 +110,22 @@ export const Title = Node.create<TitleOptions>({
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return DecorationSet.create(doc, decorations);
|
return DecorationSet.create(doc, decorations);
|
||||||
},
|
},
|
||||||
handleClick() {
|
handleClick() {
|
||||||
shouldSelectTitleNode = false;
|
closeSelectTitleNode();
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
handleDOMEvents: {
|
handleDOMEvents: {
|
||||||
click() {
|
click: closeSelectTitleNode,
|
||||||
shouldSelectTitleNode = false;
|
mousedown: closeSelectTitleNode,
|
||||||
return;
|
pointerdown: closeSelectTitleNode,
|
||||||
},
|
touchstart: closeSelectTitleNode,
|
||||||
mousedown() {
|
|
||||||
shouldSelectTitleNode = false;
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
pointerdown() {
|
|
||||||
shouldSelectTitleNode = false;
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
touchstart() {
|
|
||||||
shouldSelectTitleNode = false;
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
handleKeyDown(view, evt) {
|
handleKeyDown(view, evt) {
|
||||||
const { state, dispatch } = view;
|
const { state, dispatch } = view;
|
||||||
shouldSelectTitleNode = false;
|
|
||||||
|
closeSelectTitleNode();
|
||||||
|
|
||||||
if (isInTitle(view.state) && evt.code === 'Enter') {
|
if (isInTitle(view.state) && evt.code === 'Enter') {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
@ -136,10 +138,13 @@ export const Title = Node.create<TitleOptions>({
|
||||||
|
|
||||||
const $head = state.selection.$head;
|
const $head = state.selection.$head;
|
||||||
const titleNode = $head.node($head.depth);
|
const titleNode = $head.node($head.depth);
|
||||||
|
|
||||||
const endPos = ((titleNode.firstChild && titleNode.firstChild.nodeSize) || 0) + 1;
|
const endPos = ((titleNode.firstChild && titleNode.firstChild.nodeSize) || 0) + 1;
|
||||||
|
|
||||||
dispatch(state.tr.insert(endPos, paragraph.create()));
|
const nextNode = getNodeAtPos(state, endPos + 2);
|
||||||
|
|
||||||
|
if (!nextNode) {
|
||||||
|
dispatch(state.tr.insert(endPos, paragraph.create()));
|
||||||
|
}
|
||||||
|
|
||||||
const newState = view.state;
|
const newState = view.state;
|
||||||
const next = new TextSelection(newState.doc.resolve(endPos + 2));
|
const next = new TextSelection(newState.doc.resolve(endPos + 2));
|
||||||
|
@ -152,20 +157,45 @@ export const Title = Node.create<TitleOptions>({
|
||||||
appendTransaction: (transactions, oldState, newState) => {
|
appendTransaction: (transactions, oldState, newState) => {
|
||||||
if (!editor.isEditable) return;
|
if (!editor.isEditable) return;
|
||||||
|
|
||||||
if (!shouldSelectTitleNode) return;
|
|
||||||
|
|
||||||
const tr = newState.tr;
|
const tr = newState.tr;
|
||||||
|
let shouldReturnTr = false;
|
||||||
|
|
||||||
const firstNode = newState?.doc?.content?.content?.[0];
|
if (shouldSelectTitleNode) {
|
||||||
|
const firstNode = newState?.doc?.content?.content?.[0];
|
||||||
if (firstNode && firstNode.type.name === this.name && firstNode.nodeSize === 2) {
|
if (firstNode && firstNode.type.name === this.name && firstNode.nodeSize === 2) {
|
||||||
const selection = new TextSelection(newState.tr.doc.resolve(firstNode?.attrs?.cover ? 1 : 0));
|
const selection = new TextSelection(newState.tr.doc.resolve(firstNode?.attrs?.cover ? 1 : 0));
|
||||||
tr.setSelection(selection).scrollIntoView();
|
tr.setSelection(selection).scrollIntoView();
|
||||||
tr.setMeta('addToHistory', false);
|
tr.setMeta('addToHistory', false);
|
||||||
return tr;
|
shouldReturnTr = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
const newTitleNodes = (newState.tr.doc.content.content || []).filter((item) => item.type.name === this.name);
|
||||||
|
|
||||||
|
if (newTitleNodes.length > 1) {
|
||||||
|
const oldTitleNode = (oldState.tr.doc.content.content || []).filter((item) => item.type.name === this.name);
|
||||||
|
|
||||||
|
const otherNewNodes = (newState.tr.doc.content.content || []).filter(
|
||||||
|
(item) => item.type.name !== this.name
|
||||||
|
);
|
||||||
|
|
||||||
|
const fixedDoc = {
|
||||||
|
...newState.tr.doc.toJSON(),
|
||||||
|
content: [].concat(
|
||||||
|
((oldTitleNode && oldTitleNode[0]) || newTitleNodes[0]).toJSON(),
|
||||||
|
otherNewNodes.map((node) => node.toJSON())
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
tr.replaceWith(0, newState.doc.content.size, newState.schema.nodeFromJSON(fixedDoc));
|
||||||
|
|
||||||
|
if (tr.docChanged) {
|
||||||
|
shouldReturnTr = true;
|
||||||
|
tr.setMeta('addToHistory', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldReturnTr ? tr : undefined;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in New Issue