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