@tiptap/core
Advanced tools
Comparing version 2.5.0-beta.0 to 2.5.0-beta.1
@@ -31,4 +31,14 @@ import { ParseOptions } from '@tiptap/pm/model'; | ||
updateSelection?: boolean; | ||
/** | ||
* Whether to apply input rules after inserting the content. | ||
*/ | ||
applyInputRules?: boolean; | ||
/** | ||
* Whether to apply paste rules after inserting the content. | ||
*/ | ||
applyPasteRules?: boolean; | ||
/** | ||
* Whether to throw an error if the content is invalid. | ||
*/ | ||
errorOnInvalidContent?: boolean; | ||
}) => ReturnType; | ||
@@ -35,0 +45,0 @@ }; |
@@ -27,3 +27,12 @@ import { ParseOptions } from '@tiptap/pm/model'; | ||
*/ | ||
parseOptions?: ParseOptions) => ReturnType; | ||
parseOptions?: ParseOptions, | ||
/** | ||
* Options for `setContent`. | ||
*/ | ||
options?: { | ||
/** | ||
* Whether to throw an error if the content is invalid. | ||
*/ | ||
errorOnInvalidContent?: boolean; | ||
}) => ReturnType; | ||
}; | ||
@@ -30,0 +39,0 @@ } |
@@ -9,4 +9,6 @@ import { MarkType, NodeType, Schema } from '@tiptap/pm/model'; | ||
export * as extensions from './extensions/index.js'; | ||
export interface HTMLElement { | ||
editor?: Editor; | ||
declare global { | ||
interface HTMLElement { | ||
editor?: Editor; | ||
} | ||
} | ||
@@ -13,0 +15,0 @@ export declare class Editor extends EventEmitter<EditorEvents> { |
@@ -10,2 +10,4 @@ import { Node as ProseMirrorNode, ParseOptions, Schema } from '@tiptap/pm/model'; | ||
*/ | ||
export declare function createDocument(content: Content, schema: Schema, parseOptions?: ParseOptions): ProseMirrorNode; | ||
export declare function createDocument(content: Content, schema: Schema, parseOptions?: ParseOptions, options?: { | ||
errorOnInvalidContent?: boolean; | ||
}): ProseMirrorNode; |
@@ -6,2 +6,3 @@ import { Fragment, Node as ProseMirrorNode, ParseOptions, Schema } from '@tiptap/pm/model'; | ||
parseOptions?: ParseOptions; | ||
errorOnInvalidContent?: boolean; | ||
}; | ||
@@ -8,0 +9,0 @@ /** |
@@ -26,2 +26,11 @@ import { Mark as ProseMirrorMark, Node as ProseMirrorNode, NodeType, ParseOptions } from '@tiptap/pm/model'; | ||
}; | ||
contentError: { | ||
editor: Editor; | ||
error: Error; | ||
/** | ||
* If called, will re-initialize the editor with the collaboration extension removed. | ||
* This will prevent syncing back deletions of content not present in the current schema. | ||
*/ | ||
disableCollaboration: () => void; | ||
}; | ||
update: { | ||
@@ -70,4 +79,16 @@ editor: Editor; | ||
enableCoreExtensions: boolean; | ||
/** | ||
* If `true`, the editor will check the content for errors on initialization. | ||
* Emitting the `contentError` event if the content is invalid. | ||
* Which can be used to show a warning or error message to the user. | ||
* @default false | ||
*/ | ||
enableContentCheck: boolean; | ||
onBeforeCreate: (props: EditorEvents['beforeCreate']) => void; | ||
onCreate: (props: EditorEvents['create']) => void; | ||
/** | ||
* Called when the editor encounters an error while parsing the content. | ||
* Only enabled if `enableContentCheck` is `true`. | ||
*/ | ||
onContentError: (props: EditorEvents['contentError']) => void; | ||
onUpdate: (props: EditorEvents['update']) => void; | ||
@@ -74,0 +95,0 @@ onSelectionUpdate: (props: EditorEvents['selectionUpdate']) => void; |
{ | ||
"name": "@tiptap/core", | ||
"description": "headless rich text editor", | ||
"version": "2.5.0-beta.0", | ||
"version": "2.5.0-beta.1", | ||
"homepage": "https://tiptap.dev", | ||
@@ -35,3 +35,3 @@ "keywords": [ | ||
"devDependencies": { | ||
"@tiptap/pm": "^2.5.0-beta.0" | ||
"@tiptap/pm": "^2.5.0-beta.1" | ||
}, | ||
@@ -38,0 +38,0 @@ "peerDependencies": { |
@@ -38,4 +38,17 @@ import { Fragment, Node as ProseMirrorNode, ParseOptions } from '@tiptap/pm/model' | ||
updateSelection?: boolean | ||
/** | ||
* Whether to apply input rules after inserting the content. | ||
*/ | ||
applyInputRules?: boolean | ||
/** | ||
* Whether to apply paste rules after inserting the content. | ||
*/ | ||
applyPasteRules?: boolean | ||
/** | ||
* Whether to throw an error if the content is invalid. | ||
*/ | ||
errorOnInvalidContent?: boolean | ||
}, | ||
@@ -61,9 +74,16 @@ ) => ReturnType | ||
const content = createNodeFromContent(value, editor.schema, { | ||
parseOptions: { | ||
preserveWhitespace: 'full', | ||
...options.parseOptions, | ||
}, | ||
}) | ||
let content: Fragment | ProseMirrorNode | ||
try { | ||
content = createNodeFromContent(value, editor.schema, { | ||
parseOptions: { | ||
preserveWhitespace: 'full', | ||
...options.parseOptions, | ||
}, | ||
errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck, | ||
}) | ||
} catch (e) { | ||
return false | ||
} | ||
// don’t dispatch an empty fragment because this can lead to strange errors | ||
@@ -70,0 +90,0 @@ if (content.toString() === '<>') { |
@@ -1,2 +0,2 @@ | ||
import { ParseOptions } from '@tiptap/pm/model' | ||
import { Fragment, Node as ProseMirrorNode, ParseOptions } from '@tiptap/pm/model' | ||
@@ -33,2 +33,11 @@ import { createDocument } from '../helpers/createDocument.js' | ||
parseOptions?: ParseOptions, | ||
/** | ||
* Options for `setContent`. | ||
*/ | ||
options?: { | ||
/** | ||
* Whether to throw an error if the content is invalid. | ||
*/ | ||
errorOnInvalidContent?: boolean | ||
}, | ||
) => ReturnType | ||
@@ -39,6 +48,15 @@ } | ||
export const setContent: RawCommands['setContent'] = (content, emitUpdate = false, parseOptions = {}) => ({ tr, editor, dispatch }) => { | ||
export const setContent: RawCommands['setContent'] = (content, emitUpdate = false, parseOptions = {}, options = {}) => ({ tr, editor, dispatch }) => { | ||
const { doc } = tr | ||
const document = createDocument(content, editor.schema, parseOptions) | ||
let document: Fragment | ProseMirrorNode | ||
try { | ||
document = createDocument(content, editor.schema, parseOptions, { | ||
errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck, | ||
}) | ||
} catch (e) { | ||
return false | ||
} | ||
if (dispatch) { | ||
@@ -45,0 +63,0 @@ tr.replaceWith(0, doc.content.size, document).setMeta('preventUpdate', !emitUpdate) |
import { | ||
MarkType, NodeType, Schema, | ||
MarkType, | ||
Node as ProseMirrorNode, | ||
NodeType, | ||
Schema, | ||
} from '@tiptap/pm/model' | ||
@@ -39,4 +42,6 @@ import { | ||
export interface HTMLElement { | ||
editor?: Editor | ||
declare global { | ||
interface HTMLElement { | ||
editor?: Editor; | ||
} | ||
} | ||
@@ -73,2 +78,3 @@ | ||
enableCoreExtensions: true, | ||
enableContentCheck: false, | ||
onBeforeCreate: () => null, | ||
@@ -82,2 +88,3 @@ onCreate: () => null, | ||
onDestroy: () => null, | ||
onContentError: ({ error }) => { throw error }, | ||
} | ||
@@ -93,2 +100,3 @@ | ||
this.emit('beforeCreate', { editor: this }) | ||
this.on('contentError', this.options.onContentError) | ||
this.createView() | ||
@@ -283,3 +291,36 @@ this.injectCSS() | ||
private createView(): void { | ||
const doc = createDocument(this.options.content, this.schema, this.options.parseOptions) | ||
let doc: ProseMirrorNode | ||
try { | ||
doc = createDocument( | ||
this.options.content, | ||
this.schema, | ||
this.options.parseOptions, | ||
{ errorOnInvalidContent: this.options.enableContentCheck }, | ||
) | ||
} catch (e) { | ||
if (!(e instanceof Error) || !['[tiptap error]: Invalid JSON content', '[tiptap error]: Invalid HTML content'].includes(e.message)) { | ||
// Not the content error we were expecting | ||
throw e | ||
} | ||
this.emit('contentError', { | ||
editor: this, | ||
error: e as Error, | ||
disableCollaboration: () => { | ||
// To avoid syncing back invalid content, reinitialize the extensions without the collaboration extension | ||
this.options.extensions = this.options.extensions.filter(extension => extension.name !== 'collaboration') | ||
// Restart the initialization process by recreating the extension manager with the new set of extensions | ||
this.createExtensionManager() | ||
}, | ||
}) | ||
// Content is invalid, but attempt to create it anyway, stripping out the invalid parts | ||
doc = createDocument( | ||
this.options.content, | ||
this.schema, | ||
this.options.parseOptions, | ||
{ errorOnInvalidContent: false }, | ||
) | ||
} | ||
const selection = resolveFocusPosition(doc, this.options.autofocus) | ||
@@ -286,0 +327,0 @@ |
@@ -17,4 +17,9 @@ import { Node as ProseMirrorNode, ParseOptions, Schema } from '@tiptap/pm/model' | ||
parseOptions: ParseOptions = {}, | ||
options: { errorOnInvalidContent?: boolean } = {}, | ||
): ProseMirrorNode { | ||
return createNodeFromContent(content, schema, { slice: false, parseOptions }) as ProseMirrorNode | ||
return createNodeFromContent(content, schema, { | ||
slice: false, | ||
parseOptions, | ||
errorOnInvalidContent: options.errorOnInvalidContent, | ||
}) as ProseMirrorNode | ||
} |
@@ -15,2 +15,3 @@ import { | ||
parseOptions?: ParseOptions | ||
errorOnInvalidContent?: boolean | ||
} | ||
@@ -50,2 +51,6 @@ | ||
} catch (error) { | ||
if (options.errorOnInvalidContent) { | ||
throw new Error('[tiptap error]: Invalid JSON content', { cause: error as Error }) | ||
} | ||
console.warn('[tiptap warn]: Invalid content.', 'Passed value:', content, 'Error:', error) | ||
@@ -58,7 +63,42 @@ | ||
if (isTextContent) { | ||
const parser = DOMParser.fromSchema(schema) | ||
let schemaToUse = schema | ||
let hasInvalidContent = false | ||
return options.slice | ||
// Only ever check for invalid content if we're supposed to throw an error | ||
if (options.errorOnInvalidContent) { | ||
schemaToUse = new Schema({ | ||
topNode: schema.spec.topNode, | ||
marks: schema.spec.marks, | ||
// Prosemirror's schemas are executed such that: the last to execute, matches last | ||
// This means that we can add a catch-all node at the end of the schema to catch any content that we don't know how to handle | ||
nodes: schema.spec.nodes.append({ | ||
__tiptap__private__unknown__catch__all__node: { | ||
content: 'inline*', | ||
group: 'block', | ||
parseDOM: [ | ||
{ | ||
tag: '*', | ||
getAttrs: () => { | ||
// If this is ever called, we know that the content has something that we don't know how to handle in the schema | ||
hasInvalidContent = true | ||
return null | ||
}, | ||
}, | ||
], | ||
}, | ||
}), | ||
}) | ||
} | ||
const parser = DOMParser.fromSchema(schemaToUse) | ||
const response = options.slice | ||
? parser.parseSlice(elementFromString(content), options.parseOptions).content | ||
: parser.parse(elementFromString(content), options.parseOptions) | ||
if (options.errorOnInvalidContent && hasInvalidContent) { | ||
throw new Error('[tiptap error]: Invalid HTML content') | ||
} | ||
return response | ||
} | ||
@@ -65,0 +105,0 @@ |
@@ -42,2 +42,11 @@ import { | ||
create: { editor: Editor } | ||
contentError: { | ||
editor: Editor, | ||
error: Error, | ||
/** | ||
* If called, will re-initialize the editor with the collaboration extension removed. | ||
* This will prevent syncing back deletions of content not present in the current schema. | ||
*/ | ||
disableCollaboration: () => void | ||
} | ||
update: { editor: Editor; transaction: Transaction } | ||
@@ -71,4 +80,16 @@ selectionUpdate: { editor: Editor; transaction: Transaction } | ||
enableCoreExtensions: boolean | ||
/** | ||
* If `true`, the editor will check the content for errors on initialization. | ||
* Emitting the `contentError` event if the content is invalid. | ||
* Which can be used to show a warning or error message to the user. | ||
* @default false | ||
*/ | ||
enableContentCheck: boolean | ||
onBeforeCreate: (props: EditorEvents['beforeCreate']) => void | ||
onCreate: (props: EditorEvents['create']) => void | ||
/** | ||
* Called when the editor encounters an error while parsing the content. | ||
* Only enabled if `enableContentCheck` is `true`. | ||
*/ | ||
onContentError: (props: EditorEvents['contentError']) => void | ||
onUpdate: (props: EditorEvents['update']) => void | ||
@@ -75,0 +96,0 @@ onSelectionUpdate: (props: EditorEvents['selectionUpdate']) => void |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2307322
26946