mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-11-23 19:19:03 +08:00
feat(core): add ignoreWhitespace option to isNodeEmpty (#5446)
This commit is contained in:
parent
efb27faf54
commit
ae0254db97
5
.changeset/hungry-poems-bake.md
Normal file
5
.changeset/hungry-poems-bake.md
Normal 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
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user