tiptap: dynamic placeholder

fix/tiptap
fantasticit 2022-08-19 11:03:09 +08:00
parent ef2ed7eea9
commit 5146b0f5a2
2 changed files with 103 additions and 4 deletions

View File

@ -1,3 +1,95 @@
import BuiltInPlaceholder from '@tiptap/extension-placeholder'; import { Editor, Extension } from '@tiptap/core';
import { Node as ProsemirrorNode } from 'prosemirror-model';
import { Plugin } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
export const Placeholder = BuiltInPlaceholder; export interface PlaceholderOptions {
emptyEditorClass: string;
emptyNodeClass: string;
placeholder: ((PlaceholderProps: { editor: Editor; node: ProsemirrorNode; pos: number }) => string) | string;
showOnlyWhenEditable: boolean;
showOnlyCurrent: boolean;
includeChildren: boolean;
}
export const Placeholder = Extension.create<PlaceholderOptions>({
name: 'placeholder',
addOptions() {
return {
emptyEditorClass: 'is-editor-empty',
emptyNodeClass: 'is-empty',
placeholder: 'Write something …',
showOnlyWhenEditable: true,
showOnlyCurrent: true,
includeChildren: false,
};
},
addStorage() {
return new Map();
},
addProseMirrorPlugins() {
return [
new Plugin({
props: {
decorations: ({ doc, selection }) => {
const active = this.editor.isEditable || !this.options.showOnlyWhenEditable;
const { anchor } = selection;
const decorations: Decoration[] = [];
if (!active) {
return;
}
doc.descendants((node, pos) => {
const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
const isEmpty = !node.isLeaf && !node.childCount;
if ((hasAnchor || !this.options.showOnlyCurrent) && isEmpty) {
const classes = [this.options.emptyNodeClass];
if (this.editor.isEmpty) {
classes.push(this.options.emptyEditorClass);
}
const start = pos;
const end = pos + node.nodeSize;
const key = `${start}-${end}`;
if (!this.editor.storage[this.name].has(key)) {
this.editor.storage[this.name].set(
key,
typeof this.options.placeholder === 'function'
? this.options.placeholder({
editor: this.editor,
node,
pos,
})
: this.options.placeholder
);
}
const decoration = Decoration.node(start, end, {
'class': classes.join(' '),
'data-placeholder': this.editor.storage[this.name].get(key),
});
setTimeout(() => {
this.editor.storage[this.name].delete(key);
}, 500);
decorations.push(decoration);
}
return this.options.includeChildren;
});
return DecorationSet.create(doc, decorations);
},
},
}),
];
},
});

View File

@ -1,5 +1,4 @@
import { Toast } from '@douyinfe/semi-ui'; import { Toast } from '@douyinfe/semi-ui';
import scrollIntoView from 'scroll-into-view-if-needed';
// 自定义节点扩展 // 自定义节点扩展
import { Attachment } from 'tiptap/core/extensions/attachment'; import { Attachment } from 'tiptap/core/extensions/attachment';
import { BackgroundColor } from 'tiptap/core/extensions/background-color'; import { BackgroundColor } from 'tiptap/core/extensions/background-color';
@ -78,6 +77,14 @@ const DocumentWithTitle = Document.extend({
export { Document }; export { Document };
const placeholders = [
'输入 / 唤起更多',
'使用 markdown 语法进行输入',
'输入 @ 来提及他人',
'输入 : 来插入表情',
'你知道吗?输入 $katex 然后按一下空格就可以快速插入数学公式,其他节点操作类似哦',
];
export const CollaborationKit = [ export const CollaborationKit = [
Paragraph, Paragraph,
Placeholder.configure({ Placeholder.configure({
@ -88,7 +95,7 @@ export const CollaborationKit = [
if (!editor.isEditable) return; if (!editor.isEditable) return;
return '输入 / 唤起更多'; return placeholders[~~(Math.random() * placeholders.length)];
}, },
showOnlyCurrent: false, showOnlyCurrent: false,
showOnlyWhenEditable: false, showOnlyWhenEditable: false,