@tiptap/core 2.5.0-beta.0 → 2.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1770,15 +1770,49 @@ function createNodeFromContent(content, schema, options) {
1770
1770
  return schema.nodeFromJSON(content);
1771
1771
  }
1772
1772
  catch (error) {
1773
+ if (options.errorOnInvalidContent) {
1774
+ throw new Error('[tiptap error]: Invalid JSON content', { cause: error });
1775
+ }
1773
1776
  console.warn('[tiptap warn]: Invalid content.', 'Passed value:', content, 'Error:', error);
1774
1777
  return createNodeFromContent('', schema, options);
1775
1778
  }
1776
1779
  }
1777
1780
  if (isTextContent) {
1778
- const parser = DOMParser.fromSchema(schema);
1779
- return options.slice
1781
+ let schemaToUse = schema;
1782
+ let hasInvalidContent = false;
1783
+ // Only ever check for invalid content if we're supposed to throw an error
1784
+ if (options.errorOnInvalidContent) {
1785
+ schemaToUse = new Schema({
1786
+ topNode: schema.spec.topNode,
1787
+ marks: schema.spec.marks,
1788
+ // Prosemirror's schemas are executed such that: the last to execute, matches last
1789
+ // 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
1790
+ nodes: schema.spec.nodes.append({
1791
+ __tiptap__private__unknown__catch__all__node: {
1792
+ content: 'inline*',
1793
+ group: 'block',
1794
+ parseDOM: [
1795
+ {
1796
+ tag: '*',
1797
+ getAttrs: () => {
1798
+ // If this is ever called, we know that the content has something that we don't know how to handle in the schema
1799
+ hasInvalidContent = true;
1800
+ return null;
1801
+ },
1802
+ },
1803
+ ],
1804
+ },
1805
+ }),
1806
+ });
1807
+ }
1808
+ const parser = DOMParser.fromSchema(schemaToUse);
1809
+ const response = options.slice
1780
1810
  ? parser.parseSlice(elementFromString(content), options.parseOptions).content
1781
1811
  : parser.parse(elementFromString(content), options.parseOptions);
1812
+ if (options.errorOnInvalidContent && hasInvalidContent) {
1813
+ throw new Error('[tiptap error]: Invalid HTML content');
1814
+ }
1815
+ return response;
1782
1816
  }
1783
1817
  return createNodeFromContent('', schema, options);
1784
1818
  }
@@ -1807,6 +1841,7 @@ const isFragment = (nodeOrFragment) => {
1807
1841
  return nodeOrFragment.toString().startsWith('<');
1808
1842
  };
1809
1843
  const insertContentAt = (position, value, options) => ({ tr, dispatch, editor }) => {
1844
+ var _a;
1810
1845
  if (dispatch) {
1811
1846
  options = {
1812
1847
  parseOptions: {},
@@ -1815,12 +1850,19 @@ const insertContentAt = (position, value, options) => ({ tr, dispatch, editor })
1815
1850
  applyPasteRules: false,
1816
1851
  ...options,
1817
1852
  };
1818
- const content = createNodeFromContent(value, editor.schema, {
1819
- parseOptions: {
1820
- preserveWhitespace: 'full',
1821
- ...options.parseOptions,
1822
- },
1823
- });
1853
+ let content;
1854
+ try {
1855
+ content = createNodeFromContent(value, editor.schema, {
1856
+ parseOptions: {
1857
+ preserveWhitespace: 'full',
1858
+ ...options.parseOptions,
1859
+ },
1860
+ errorOnInvalidContent: (_a = options.errorOnInvalidContent) !== null && _a !== void 0 ? _a : editor.options.enableContentCheck,
1861
+ });
1862
+ }
1863
+ catch (e) {
1864
+ return false;
1865
+ }
1824
1866
  // don’t dispatch an empty fragment because this can lead to strange errors
1825
1867
  if (content.toString() === '<>') {
1826
1868
  return true;
@@ -2184,13 +2226,26 @@ const selectTextblockStart = () => ({ state, dispatch }) => {
2184
2226
  * @param parseOptions Options for the parser
2185
2227
  * @returns The created Prosemirror document node
2186
2228
  */
2187
- function createDocument(content, schema, parseOptions = {}) {
2188
- return createNodeFromContent(content, schema, { slice: false, parseOptions });
2229
+ function createDocument(content, schema, parseOptions = {}, options = {}) {
2230
+ return createNodeFromContent(content, schema, {
2231
+ slice: false,
2232
+ parseOptions,
2233
+ errorOnInvalidContent: options.errorOnInvalidContent,
2234
+ });
2189
2235
  }
2190
2236
 
2191
- const setContent = (content, emitUpdate = false, parseOptions = {}) => ({ tr, editor, dispatch }) => {
2237
+ const setContent = (content, emitUpdate = false, parseOptions = {}, options = {}) => ({ tr, editor, dispatch }) => {
2238
+ var _a;
2192
2239
  const { doc } = tr;
2193
- const document = createDocument(content, editor.schema, parseOptions);
2240
+ let document;
2241
+ try {
2242
+ document = createDocument(content, editor.schema, parseOptions, {
2243
+ errorOnInvalidContent: (_a = options.errorOnInvalidContent) !== null && _a !== void 0 ? _a : editor.options.enableContentCheck,
2244
+ });
2245
+ }
2246
+ catch (e) {
2247
+ return false;
2248
+ }
2194
2249
  if (dispatch) {
2195
2250
  tr.replaceWith(0, doc.content.size, document).setMeta('preventUpdate', !emitUpdate);
2196
2251
  }
@@ -3879,6 +3934,7 @@ class Editor extends EventEmitter {
3879
3934
  enableInputRules: true,
3880
3935
  enablePasteRules: true,
3881
3936
  enableCoreExtensions: true,
3937
+ enableContentCheck: false,
3882
3938
  onBeforeCreate: () => null,
3883
3939
  onCreate: () => null,
3884
3940
  onUpdate: () => null,
@@ -3887,6 +3943,7 @@ class Editor extends EventEmitter {
3887
3943
  onFocus: () => null,
3888
3944
  onBlur: () => null,
3889
3945
  onDestroy: () => null,
3946
+ onContentError: ({ error }) => { throw error; },
3890
3947
  };
3891
3948
  this.isCapturingTransaction = false;
3892
3949
  this.capturedTransaction = null;
@@ -3896,6 +3953,7 @@ class Editor extends EventEmitter {
3896
3953
  this.createSchema();
3897
3954
  this.on('beforeCreate', this.options.onBeforeCreate);
3898
3955
  this.emit('beforeCreate', { editor: this });
3956
+ this.on('contentError', this.options.onContentError);
3899
3957
  this.createView();
3900
3958
  this.injectCSS();
3901
3959
  this.on('create', this.options.onCreate);
@@ -4055,7 +4113,28 @@ class Editor extends EventEmitter {
4055
4113
  * Creates a ProseMirror view.
4056
4114
  */
4057
4115
  createView() {
4058
- const doc = createDocument(this.options.content, this.schema, this.options.parseOptions);
4116
+ let doc;
4117
+ try {
4118
+ doc = createDocument(this.options.content, this.schema, this.options.parseOptions, { errorOnInvalidContent: this.options.enableContentCheck });
4119
+ }
4120
+ catch (e) {
4121
+ if (!(e instanceof Error) || !['[tiptap error]: Invalid JSON content', '[tiptap error]: Invalid HTML content'].includes(e.message)) {
4122
+ // Not the content error we were expecting
4123
+ throw e;
4124
+ }
4125
+ this.emit('contentError', {
4126
+ editor: this,
4127
+ error: e,
4128
+ disableCollaboration: () => {
4129
+ // To avoid syncing back invalid content, reinitialize the extensions without the collaboration extension
4130
+ this.options.extensions = this.options.extensions.filter(extension => extension.name !== 'collaboration');
4131
+ // Restart the initialization process by recreating the extension manager with the new set of extensions
4132
+ this.createExtensionManager();
4133
+ },
4134
+ });
4135
+ // Content is invalid, but attempt to create it anyway, stripping out the invalid parts
4136
+ doc = createDocument(this.options.content, this.schema, this.options.parseOptions, { errorOnInvalidContent: false });
4137
+ }
4059
4138
  const selection = resolveFocusPosition(doc, this.options.autofocus);
4060
4139
  this.view = new EditorView(this.options.element, {
4061
4140
  ...this.options.editorProps,