fix: do not modify createNodeFromContent

This commit is contained in:
Arnau Gómez Farell 2025-06-04 12:57:26 +02:00
parent 3f8efb9e64
commit 42f719468b
No known key found for this signature in database
4 changed files with 25 additions and 147 deletions

View File

@ -84,14 +84,28 @@ export const insertContentAt: RawCommands['insertContentAt'] = (position, value,
})
}
try {
content = createNodeFromContent(value, editor.schema, {
parseOptions: {
const parseOptions: ParseOptions = {
preserveWhitespace: 'full',
...options.parseOptions,
},
}
// If `emitContentError` is enabled, we want to check the content for errors
// but ignore them (do not remove the invalid content from the document)
if (!options.errorOnInvalidContent && editor.options.enableContentCheck && editor.options.emitContentError) {
try {
createNodeFromContent(value, editor.schema, {
parseOptions,
errorOnInvalidContent: true,
})
} catch (e) {
emitContentError(e as Error)
}
}
try {
content = createNodeFromContent(value, editor.schema, {
parseOptions,
errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
onIgnoredError: editor.options.emitContentError ? emitContentError : undefined,
})
} catch (e) {
emitContentError(e as Error)

View File

@ -13,13 +13,6 @@ export type CreateNodeFromContentOptions = {
slice?: boolean
parseOptions?: ParseOptions
errorOnInvalidContent?: boolean
/**
* Runs if a content is invalid and an error would have been thrown, but
* `errorOnInvalidContent` is `false` so the invalid content is ignored.
*
* @param error The error that was not thrown
*/
onIgnoredError?: (error: Error) => void
}
/**
@ -63,16 +56,11 @@ export function createNodeFromContent(
return node
} catch (error) {
const thrownError = new Error('[tiptap error]: Invalid JSON content', { cause: error as Error })
if (options.errorOnInvalidContent) {
throw thrownError
throw new Error('[tiptap error]: Invalid JSON content', { cause: error as Error })
}
if (options.onIgnoredError) {
options.onIgnoredError(thrownError)
} else {
console.warn('[tiptap warn]: Invalid content.', 'Passed value:', content, 'Error:', error)
}
return createNodeFromContent('', schema, options)
}
@ -117,15 +105,8 @@ export function createNodeFromContent(
DOMParser.fromSchema(contentCheckSchema).parse(elementFromString(content), options.parseOptions)
}
if (hasInvalidContent) {
const thrownError = new Error('[tiptap error]: Invalid HTML content', { cause: new Error(`Invalid element found: ${invalidContent}`) })
if (options.errorOnInvalidContent) {
throw thrownError
} else if (options.onIgnoredError) {
options.onIgnoredError(thrownError)
}
if (options.errorOnInvalidContent && hasInvalidContent) {
throw new Error('[tiptap error]: Invalid HTML content', { cause: new Error(`Invalid element found: ${invalidContent}`) })
}
}

View File

@ -128,7 +128,7 @@ export interface EditorOptions {
*/
enableContentCheck: boolean;
/**
* If `true`, the editor will emit the `contentError` event invalid content is
* If `true`, the editor will emit the `contentError` event if invalid content is
* encountered but `enableContentCheck` is `false`. This lets you preserve the
* invalid editor content while still showing a warning or error message to
* the user.

View File

@ -263,121 +263,4 @@ describe('createNodeFromContent', () => {
]), { errorOnInvalidContent: true })
}).to.throw('[tiptap error]: Invalid JSON content')
})
it('calls onIgnoredError when a schema does not have matching node types for JSON content', () => {
const content = {
type: 'non-existing-node-type',
content: [{
type: 'text',
text: 'Example Text',
}],
}
let errorCalled = false
let errorMessage = ''
const fragment = createNodeFromContent(content, getSchemaByResolvedExtensions([
Document,
Paragraph,
Text,
]), {
errorOnInvalidContent: false,
onIgnoredError: error => {
errorCalled = true
errorMessage = error.message
},
})
expect(errorCalled).to.eq(true)
expect(errorMessage).to.eq('[tiptap error]: Invalid JSON content')
expect(fragment.toJSON()).to.deep.eq(null)
})
it('calls onIgnoredError when a schema does not have matching node types for HTML content', () => {
const content = '<non-existing-node-type>Example Text</non-existing-node-type>'
let errorCalled = false
let errorMessage = ''
const fragment = createNodeFromContent(content, getSchemaByResolvedExtensions([
Document,
Paragraph,
Text,
]), {
errorOnInvalidContent: false,
onIgnoredError: error => {
errorCalled = true
errorMessage = error.message
},
})
expect(errorCalled).to.eq(true)
expect(errorMessage).to.eq('[tiptap error]: Invalid HTML content')
expect(fragment.toJSON()).to.deep.eq([{ type: 'text', text: 'Example Text' }])
})
it('calls onIgnoredError when a schema does not have matching mark types for JSON content', () => {
const content = {
type: 'paragraph',
content: [{
type: 'text',
text: 'Example Text',
marks: [{
type: 'non-existing-mark-type',
}],
}],
}
let errorCalled = false
let errorMessage = ''
const fragment = createNodeFromContent(content, getSchemaByResolvedExtensions([
Document,
Paragraph,
Text,
]), {
errorOnInvalidContent: false,
onIgnoredError: error => {
errorCalled = true
errorMessage = error.message
},
})
expect(errorCalled).to.eq(true)
expect(errorMessage).to.eq('[tiptap error]: Invalid JSON content')
expect(fragment.toJSON()).to.deep.eq(null)
})
it('calls onIgnoredError when the JSON content does not follow the nesting rules of the schema', () => {
const content = {
type: 'paragraph',
content: [{
type: 'paragraph',
content: [{
type: 'text',
text: 'Example Text',
}],
}],
}
let errorCalled = false
let errorMessage = ''
const fragment = createNodeFromContent(content, getSchemaByResolvedExtensions([
Document,
Paragraph,
Text,
]), {
errorOnInvalidContent: false,
onIgnoredError: error => {
errorCalled = true
errorMessage = error.message
},
})
expect(errorCalled).to.eq(true)
expect(errorMessage).to.eq('[tiptap error]: Invalid JSON content')
expect(fragment.toJSON()).to.deep.eq(null)
})
})