Skip to content

Commit

Permalink
feat(core): add ignoreWhitespace option to isNodeEmpty
Browse files Browse the repository at this point in the history
  • Loading branch information
nperez0111 committed Aug 5, 2024
1 parent 7c8889a commit 4bf1e34
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/hungry-poems-bake.md
Original file line number Diff line number Diff line change
@@ -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
37 changes: 29 additions & 8 deletions packages/core/src/helpers/isNodeEmpty.ts
Original file line number Diff line number Diff line change
@@ -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
}
Expand All @@ -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
Expand Down
69 changes: 52 additions & 17 deletions tests/cypress/integration/core/isNodeEmpty.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!' })
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -120,9 +161,7 @@ describe('isNodeEmpty', () => {
content: [
{
type: 'heading',
content: [
{ type: 'text', text: 'Hello world!' },
],
content: [{ type: 'text', text: 'Hello world!' }],
},
],
})
Expand All @@ -137,9 +176,7 @@ describe('isNodeEmpty', () => {
{ type: 'heading' },
{
type: 'paragraph',
content: [
{ type: 'text', text: 'Hello world!' },
],
content: [{ type: 'text', text: 'Hello world!' }],
},
],
})
Expand All @@ -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)
Expand Down

0 comments on commit 4bf1e34

Please sign in to comment.