diff --git a/demos/src/Examples/Issue4327/React/foo.ts b/demos/src/Examples/Issue4327/React/foo.ts new file mode 100644 index 000000000..47708c279 --- /dev/null +++ b/demos/src/Examples/Issue4327/React/foo.ts @@ -0,0 +1,26 @@ +import { mergeAttributes, Node } from '@tiptap/core' + +export default Node.create({ + name: 'foo', + + group: 'inline', + + inline: true, + + parseHTML() { + return [ + { + tag: 'span', + getAttrs: node => (node as HTMLElement).hasAttribute('data-foo') && null, + }, + ] + }, + + renderHTML({ HTMLAttributes }) { + return ['span', mergeAttributes({ 'data-foo': '', HTMLAttributes }), 'foo'] + }, + + renderText() { + return 'foo' + }, +}) diff --git a/demos/src/Examples/Issue4327/React/index.html b/demos/src/Examples/Issue4327/React/index.html new file mode 100644 index 000000000..e69de29bb diff --git a/demos/src/Examples/Issue4327/React/index.tsx b/demos/src/Examples/Issue4327/React/index.tsx new file mode 100644 index 000000000..beff64ecc --- /dev/null +++ b/demos/src/Examples/Issue4327/React/index.tsx @@ -0,0 +1,27 @@ +import './styles.scss' + +import { EditorContent, FloatingMenu, useEditor } from '@tiptap/react' +import StarterKit from '@tiptap/starter-kit' +import React from 'react' + +import Foo from './foo.js' + +export default () => { + const editor = useEditor({ + extensions: [ + StarterKit, Foo, + ], + content: ` +

foo

+ `, + }) + + return ( + <> + {editor && +
Hello
+
} + + + ) +} diff --git a/demos/src/Examples/Issue4327/React/styles.scss b/demos/src/Examples/Issue4327/React/styles.scss new file mode 100644 index 000000000..1c9f0f6cd --- /dev/null +++ b/demos/src/Examples/Issue4327/React/styles.scss @@ -0,0 +1,10 @@ +.tiptap { + > * + * { + margin-top: 0.75em; + } + + ul, + ol { + padding: 0 1rem; + } +} diff --git a/demos/src/Examples/Issue4327/foo.ts b/demos/src/Examples/Issue4327/foo.ts new file mode 100644 index 000000000..47708c279 --- /dev/null +++ b/demos/src/Examples/Issue4327/foo.ts @@ -0,0 +1,26 @@ +import { mergeAttributes, Node } from '@tiptap/core' + +export default Node.create({ + name: 'foo', + + group: 'inline', + + inline: true, + + parseHTML() { + return [ + { + tag: 'span', + getAttrs: node => (node as HTMLElement).hasAttribute('data-foo') && null, + }, + ] + }, + + renderHTML({ HTMLAttributes }) { + return ['span', mergeAttributes({ 'data-foo': '', HTMLAttributes }), 'foo'] + }, + + renderText() { + return 'foo' + }, +}) diff --git a/packages/extension-floating-menu/src/floating-menu-plugin.ts b/packages/extension-floating-menu/src/floating-menu-plugin.ts index ed1079878..94025e1fe 100644 --- a/packages/extension-floating-menu/src/floating-menu-plugin.ts +++ b/packages/extension-floating-menu/src/floating-menu-plugin.ts @@ -1,4 +1,7 @@ -import { Editor, posToDOMRect } from '@tiptap/core' +import { + Editor, getText, getTextSerializersFromSchema, posToDOMRect, +} from '@tiptap/core' +import { Node as ProseMirrorNode } from '@tiptap/pm/model' import { EditorState, Plugin, PluginKey } from '@tiptap/pm/state' import { EditorView } from '@tiptap/pm/view' import tippy, { Instance, Props } from 'tippy.js' @@ -35,11 +38,15 @@ export class FloatingMenuView { public tippyOptions?: Partial + private getTextContent(node:ProseMirrorNode) { + return getText(node, { textSerializers: getTextSerializersFromSchema(this.editor.schema) }) + } + public shouldShow: Exclude = ({ view, state }) => { const { selection } = state const { $anchor, empty } = selection const isRootDepth = $anchor.depth === 1 - const isEmptyTextBlock = $anchor.parent.isTextblock && !$anchor.parent.type.spec.code && !$anchor.parent.textContent + const isEmptyTextBlock = $anchor.parent.isTextblock && !$anchor.parent.type.spec.code && !this.getTextContent($anchor.parent) if ( !view.hasFocus() diff --git a/tests/cypress/integration/Issue4327/index.spec.ts b/tests/cypress/integration/Issue4327/index.spec.ts new file mode 100644 index 000000000..66494c0fc --- /dev/null +++ b/tests/cypress/integration/Issue4327/index.spec.ts @@ -0,0 +1,18 @@ +/// + +import { Editor } from '@tiptap/core' + +interface EditorElement extends HTMLElement { + editor: Editor +} +context('/cypress/integration/Issue4327/React/', () => { + before(() => { + cy.visit('/src/Examples/Issue4327/React/') + }) + it('should not show menu when node has renderText returning text with length > 0', () => { + cy.get('.tiptap').then(([editorElement]) => { + (editorElement as EditorElement).editor.commands.focus() + }).get('.ProseMirror-focused').get('#app') + .should('not.have.descendants', '[data-tippy-root]') + }) +})