feat(core): add ignoreWhitespace option to isNodeEmpty (#5446)

This commit is contained in:
Nick Perez 2024-08-06 10:05:50 +02:00 committed by GitHub
parent efb27faf54
commit ae0254db97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 86 additions and 25 deletions

View File

@ -0,0 +1,5 @@
---
"@tiptap/core": minor
---
Add `ignoreWhitespace` option to `isNodeEmpty` to ignore any whitespace and hardbreaks in a node to check for emptiness

View File

@ -1,13 +1,34 @@
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
/**
* Returns true if the given node is empty.
* When `checkChildren` is true (default), it will also check if all children are empty.
* Returns true if the given prosemirror node is empty.
*/
export function isNodeEmpty(
node: ProseMirrorNode,
{ checkChildren }: { checkChildren: boolean } = { checkChildren: true },
{
checkChildren = true,
ignoreWhitespace = false,
}: {
/**
* When true (default), it will also check if all children are empty.
*/
checkChildren?: boolean;
/**
* When true, it will ignore whitespace when checking for emptiness.
*/
ignoreWhitespace?: boolean;
} = {},
): boolean {
if (ignoreWhitespace) {
if (node.type.name === 'hardBreak') {
// Hard breaks are considered empty
return true
}
if (node.isText) {
return /^\s*$/m.test(node.text ?? '')
}
}
if (node.isText) {
return !node.text
}
@ -21,20 +42,20 @@ export function isNodeEmpty(
}
if (checkChildren) {
let hasSameContent = true
let isContentEmpty = true
node.content.forEach(childNode => {
if (hasSameContent === false) {
if (isContentEmpty === false) {
// Exit early for perf
return
}
if (!isNodeEmpty(childNode)) {
hasSameContent = false
if (!isNodeEmpty(childNode, { ignoreWhitespace, checkChildren })) {
isContentEmpty = false
}
})
return hasSameContent
return isContentEmpty
}
return false

View File

@ -7,10 +7,49 @@ import Mention from '@tiptap/extension-mention'
import StarterKit from '@tiptap/starter-kit'
const schema = getSchema([StarterKit, Mention])
const modifiedSchema = getSchema([StarterKit.configure({ document: false }), Document.extend({ content: 'heading block*' })])
const imageSchema = getSchema([StarterKit.configure({ document: false }), Document.extend({ content: 'image block*' }), Image])
const modifiedSchema = getSchema([
StarterKit.configure({ document: false }),
Document.extend({ content: 'heading block*' }),
])
const imageSchema = getSchema([
StarterKit.configure({ document: false }),
Document.extend({ content: 'image block*' }),
Image,
])
describe('isNodeEmpty', () => {
describe('ignoreWhitespace=true', () => {
it('should return true when text has only whitespace', () => {
const node = schema.nodeFromJSON({ type: 'text', text: ' \n\t\r\n' })
expect(isNodeEmpty(node, { ignoreWhitespace: true })).to.eq(true)
})
it('should return true when a paragraph has only whitespace', () => {
const node = schema.nodeFromJSON({
type: 'paragraph',
content: [{ type: 'text', text: ' \n\t\r\n' }],
})
expect(isNodeEmpty(node, { ignoreWhitespace: true })).to.eq(true)
})
it('should return true for a hardbreak', () => {
const node = schema.nodeFromJSON({ type: 'hardBreak' })
expect(isNodeEmpty(node, { ignoreWhitespace: true })).to.eq(true)
})
it('should return true when a paragraph has only a hardbreak', () => {
const node = schema.nodeFromJSON({
type: 'paragraph',
content: [{ type: 'hardBreak' }],
})
expect(isNodeEmpty(node, { ignoreWhitespace: true })).to.eq(true)
})
})
describe('with default schema', () => {
it('should return false when text has content', () => {
const node = schema.nodeFromJSON({ type: 'text', text: 'Hello world!' })
@ -39,13 +78,15 @@ describe('isNodeEmpty', () => {
it('should return false when a paragraph has a mention', () => {
const node = schema.nodeFromJSON({
type: 'paragraph',
content: [{
type: 'mention',
attrs: {
id: 'Winona Ryder',
label: null,
content: [
{
type: 'mention',
attrs: {
id: 'Winona Ryder',
label: null,
},
},
}],
],
})
expect(isNodeEmpty(node)).to.eq(false)
@ -120,9 +161,7 @@ describe('isNodeEmpty', () => {
content: [
{
type: 'heading',
content: [
{ type: 'text', text: 'Hello world!' },
],
content: [{ type: 'text', text: 'Hello world!' }],
},
],
})
@ -137,9 +176,7 @@ describe('isNodeEmpty', () => {
{ type: 'heading' },
{
type: 'paragraph',
content: [
{ type: 'text', text: 'Hello world!' },
],
content: [{ type: 'text', text: 'Hello world!' }],
},
],
})
@ -162,9 +199,7 @@ describe('isNodeEmpty', () => {
it('should return true when a document has an empty heading with attrs', () => {
const node = modifiedSchema.nodeFromJSON({
type: 'doc',
content: [
{ type: 'heading', content: [], attrs: { level: 2 } },
],
content: [{ type: 'heading', content: [], attrs: { level: 2 } }],
})
expect(isNodeEmpty(node)).to.eq(true)