From 9f45028be9dd5f94d83acc2fc311237ec8337b71 Mon Sep 17 00:00:00 2001 From: Dominik Biedebach Date: Wed, 16 Aug 2023 09:48:16 -0700 Subject: [PATCH] feat(placeholder): allow editor-is-empty class on any node --- .../Extensions/Placeholder/React/styles.scss | 2 +- docs/api/extensions/placeholder.md | 11 ++++ .../extension-placeholder/src/placeholder.ts | 65 +++++++++++++++++-- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/demos/src/Extensions/Placeholder/React/styles.scss b/demos/src/Extensions/Placeholder/React/styles.scss index 8fd5d0ca790..5a8b4b37dfe 100644 --- a/demos/src/Extensions/Placeholder/React/styles.scss +++ b/demos/src/Extensions/Placeholder/React/styles.scss @@ -6,7 +6,7 @@ } /* Placeholder (at the top) */ -.tiptap p.is-editor-empty:first-child::before { +.tiptap .is-editor-empty:first-child::before { color: #adb5bd; content: attr(data-placeholder); float: left; diff --git a/docs/api/extensions/placeholder.md b/docs/api/extensions/placeholder.md index bc7c023ead9..b00bb124d8e 100644 --- a/docs/api/extensions/placeholder.md +++ b/docs/api/extensions/placeholder.md @@ -88,6 +88,17 @@ Placeholder.configure({ }) ``` +### considerAnyAsEmpty +Consider any node that is not a leaf or atom as empty for the editor empty check. + +Default: `false` + +```js +Placeholder.configure({ + considerAnyAsEmpty: true, +}) +``` + ### showOnlyWhenEditable Show decorations only when editor is editable. diff --git a/packages/extension-placeholder/src/placeholder.ts b/packages/extension-placeholder/src/placeholder.ts index 1b2598bdf8c..cdbc20c66a3 100644 --- a/packages/extension-placeholder/src/placeholder.ts +++ b/packages/extension-placeholder/src/placeholder.ts @@ -4,8 +4,24 @@ import { Plugin, PluginKey } from '@tiptap/pm/state' import { Decoration, DecorationSet } from '@tiptap/pm/view' export interface PlaceholderOptions { + /** + * **The class name for the empty editor** + * @default 'is-editor-empty' + */ emptyEditorClass: string + + /** + * **The class name for empty nodes** + * @default 'is-empty' + */ emptyNodeClass: string + + /** + * **The placeholder content** + * + * You can use a function to return a dynamic placeholder or a string. + * @default 'Write something …' + */ placeholder: | ((PlaceholderProps: { editor: Editor @@ -14,8 +30,41 @@ export interface PlaceholderOptions { hasAnchor: boolean }) => string) | string + + /** + * **Used for empty check on the document.** + * + * If true, any node that is not a leaf or atom will be considered for empty check. + * If false, only default nodes (paragraphs) will be considered for empty check. + * @default false + */ + considerAnyAsEmpty: boolean + + /** + * **Checks if the placeholder should be only shown when the editor is editable.** + * + * If true, the placeholder will only be shown when the editor is editable. + * If false, the placeholder will always be shown. + * @default true + */ showOnlyWhenEditable: boolean + + /** + * **Checks if the placeholder should be only shown when the current node is empty.** + * + * If true, the placeholder will only be shown when the current node is empty. + * If false, the placeholder will be shown when any node is empty. + * @default true + */ showOnlyCurrent: boolean + + /** + * **Controls if the placeholder should be shown for all descendents.** + * + * If true, the placeholder will be shown for all descendents. + * If false, the placeholder will only be shown for the current node. + * @default false + */ includeChildren: boolean } @@ -28,6 +77,7 @@ export const Placeholder = Extension.create({ emptyNodeClass: 'is-empty', placeholder: 'Write something …', showOnlyWhenEditable: true, + considerAnyAsEmpty: false, showOnlyCurrent: true, includeChildren: false, } @@ -48,9 +98,16 @@ export const Placeholder = Extension.create({ } // only calculate isEmpty once due to its performance impacts (see issue #3360) - const emptyDocInstance = doc.type.createAndFill() - const isEditorEmpty = emptyDocInstance?.sameMarkup(doc) - && emptyDocInstance.content.findDiffStart(doc.content) === null + const { firstChild } = doc.content + const isLeaf = firstChild && firstChild.type.isLeaf + const isAtom = firstChild && firstChild.isAtom + const isValidNode = this.options.considerAnyAsEmpty + ? true + : firstChild && firstChild.type.name === doc.type.contentMatch.defaultType?.name + const isEmptyDoc = doc.content.childCount <= 1 + && firstChild + && isValidNode + && (firstChild.nodeSize <= 2 && (!isLeaf || !isAtom)) doc.descendants((node, pos) => { const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize @@ -59,7 +116,7 @@ export const Placeholder = Extension.create({ if ((hasAnchor || !this.options.showOnlyCurrent) && isEmpty) { const classes = [this.options.emptyNodeClass] - if (isEditorEmpty) { + if (isEmptyDoc) { classes.push(this.options.emptyEditorClass) }