mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-06-08 01:53:04 +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'
|
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given node is empty.
|
* Returns true if the given prosemirror node is empty.
|
||||||
* When `checkChildren` is true (default), it will also check if all children are empty.
|
|
||||||
*/
|
*/
|
||||||
export function isNodeEmpty(
|
export function isNodeEmpty(
|
||||||
node: ProseMirrorNode,
|
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 {
|
): 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) {
|
if (node.isText) {
|
||||||
return !node.text
|
return !node.text
|
||||||
}
|
}
|
||||||
@ -21,20 +42,20 @@ export function isNodeEmpty(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (checkChildren) {
|
if (checkChildren) {
|
||||||
let hasSameContent = true
|
let isContentEmpty = true
|
||||||
|
|
||||||
node.content.forEach(childNode => {
|
node.content.forEach(childNode => {
|
||||||
if (hasSameContent === false) {
|
if (isContentEmpty === false) {
|
||||||
// Exit early for perf
|
// Exit early for perf
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNodeEmpty(childNode)) {
|
if (!isNodeEmpty(childNode, { ignoreWhitespace, checkChildren })) {
|
||||||
hasSameContent = false
|
isContentEmpty = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return hasSameContent
|
return isContentEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -7,10 +7,49 @@ import Mention from '@tiptap/extension-mention'
|
|||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
|
||||||
const schema = getSchema([StarterKit, Mention])
|
const schema = getSchema([StarterKit, Mention])
|
||||||
const modifiedSchema = getSchema([StarterKit.configure({ document: false }), Document.extend({ content: 'heading block*' })])
|
const modifiedSchema = getSchema([
|
||||||
const imageSchema = getSchema([StarterKit.configure({ document: false }), Document.extend({ content: 'image block*' }), Image])
|
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('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', () => {
|
describe('with default schema', () => {
|
||||||
it('should return false when text has content', () => {
|
it('should return false when text has content', () => {
|
||||||
const node = schema.nodeFromJSON({ type: 'text', text: 'Hello world!' })
|
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', () => {
|
it('should return false when a paragraph has a mention', () => {
|
||||||
const node = schema.nodeFromJSON({
|
const node = schema.nodeFromJSON({
|
||||||
type: 'paragraph',
|
type: 'paragraph',
|
||||||
content: [{
|
content: [
|
||||||
type: 'mention',
|
{
|
||||||
attrs: {
|
type: 'mention',
|
||||||
id: 'Winona Ryder',
|
attrs: {
|
||||||
label: null,
|
id: 'Winona Ryder',
|
||||||
|
label: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(isNodeEmpty(node)).to.eq(false)
|
expect(isNodeEmpty(node)).to.eq(false)
|
||||||
@ -120,9 +161,7 @@ describe('isNodeEmpty', () => {
|
|||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 'heading',
|
type: 'heading',
|
||||||
content: [
|
content: [{ type: 'text', text: 'Hello world!' }],
|
||||||
{ type: 'text', text: 'Hello world!' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@ -137,9 +176,7 @@ describe('isNodeEmpty', () => {
|
|||||||
{ type: 'heading' },
|
{ type: 'heading' },
|
||||||
{
|
{
|
||||||
type: 'paragraph',
|
type: 'paragraph',
|
||||||
content: [
|
content: [{ type: 'text', text: 'Hello world!' }],
|
||||||
{ type: 'text', text: 'Hello world!' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@ -162,9 +199,7 @@ describe('isNodeEmpty', () => {
|
|||||||
it('should return true when a document has an empty heading with attrs', () => {
|
it('should return true when a document has an empty heading with attrs', () => {
|
||||||
const node = modifiedSchema.nodeFromJSON({
|
const node = modifiedSchema.nodeFromJSON({
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
content: [
|
content: [{ type: 'heading', content: [], attrs: { level: 2 } }],
|
||||||
{ type: 'heading', content: [], attrs: { level: 2 } },
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(isNodeEmpty(node)).to.eq(true)
|
expect(isNodeEmpty(node)).to.eq(true)
|
||||||
|
Loading…
Reference in New Issue
Block a user