@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.cjs CHANGED
@@ -1774,15 +1774,49 @@ function createNodeFromContent(content, schema, options) {
1774
1774
  return schema.nodeFromJSON(content);
1775
1775
  }
1776
1776
  catch (error) {
1777
+ if (options.errorOnInvalidContent) {
1778
+ throw new Error('[tiptap error]: Invalid JSON content', { cause: error });
1779
+ }
1777
1780
  console.warn('[tiptap warn]: Invalid content.', 'Passed value:', content, 'Error:', error);
1778
1781
  return createNodeFromContent('', schema, options);
1779
1782
  }
1780
1783
  }
1781
1784
  if (isTextContent) {
1782
- const parser = model.DOMParser.fromSchema(schema);
1783
- return options.slice
1785
+ let schemaToUse = schema;
1786
+ let hasInvalidContent = false;
1787
+ // Only ever check for invalid content if we're supposed to throw an error
1788
+ if (options.errorOnInvalidContent) {
1789
+ schemaToUse = new model.Schema({
1790
+ topNode: schema.spec.topNode,
1791
+ marks: schema.spec.marks,
1792
+ // Prosemirror's schemas are executed such that: the last to execute, matches last
1793
+ // 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
1794
+ nodes: schema.spec.nodes.append({
1795
+ __tiptap__private__unknown__catch__all__node: {
1796
+ content: 'inline*',
1797
+ group: 'block',
1798
+ parseDOM: [
1799
+ {
1800
+ tag: '*',
1801
+ getAttrs: () => {
1802
+ // If this is ever called, we know that the content has something that we don't know how to handle in the schema
1803
+ hasInvalidContent = true;
1804
+ return null;
1805
+ },
1806
+ },
1807
+ ],
1808
+ },
1809
+ }),
1810
+ });
1811
+ }
1812
+ const parser = model.DOMParser.fromSchema(schemaToUse);
1813
+ const response = options.slice
1784
1814
  ? parser.parseSlice(elementFromString(content), options.parseOptions).content
1785
1815
  : parser.parse(elementFromString(content), options.parseOptions);
1816
+ if (options.errorOnInvalidContent && hasInvalidContent) {
1817
+ throw new Error('[tiptap error]: Invalid HTML content');
1818
+ }
1819
+ return response;
1786
1820
  }
1787
1821
  return createNodeFromContent('', schema, options);
1788
1822
  }
@@ -1811,6 +1845,7 @@ const isFragment = (nodeOrFragment) => {
1811
1845
  return nodeOrFragment.toString().startsWith('<');
1812
1846
  };
1813
1847
  const insertContentAt = (position, value, options) => ({ tr, dispatch, editor }) => {
1848
+ var _a;
1814
1849
  if (dispatch) {
1815
1850
  options = {
1816
1851
  parseOptions: {},
@@ -1819,12 +1854,19 @@ const insertContentAt = (position, value, options) => ({ tr, dispatch, editor })
1819
1854
  applyPasteRules: false,
1820
1855
  ...options,
1821
1856
  };
1822
- const content = createNodeFromContent(value, editor.schema, {
1823
- parseOptions: {
1824
- preserveWhitespace: 'full',
1825
- ...options.parseOptions,
1826
- },
1827
- });
1857
+ let content;
1858
+ try {
1859
+ content = createNodeFromContent(value, editor.schema, {
1860
+ parseOptions: {
1861
+ preserveWhitespace: 'full',
1862
+ ...options.parseOptions,
1863
+ },
1864
+ errorOnInvalidContent: (_a = options.errorOnInvalidContent) !== null && _a !== void 0 ? _a : editor.options.enableContentCheck,
1865
+ });
1866
+ }
1867
+ catch (e) {
1868
+ return false;
1869
+ }
1828
1870
  // don’t dispatch an empty fragment because this can lead to strange errors
1829
1871
  if (content.toString() === '<>') {
1830
1872
  return true;
@@ -2188,13 +2230,26 @@ const selectTextblockStart = () => ({ state, dispatch }) => {
2188
2230
  * @param parseOptions Options for the parser
2189
2231
  * @returns The created Prosemirror document node
2190
2232
  */
2191
- function createDocument(content, schema, parseOptions = {}) {
2192
- return createNodeFromContent(content, schema, { slice: false, parseOptions });
2233
+ function createDocument(content, schema, parseOptions = {}, options = {}) {
2234
+ return createNodeFromContent(content, schema, {
2235
+ slice: false,
2236
+ parseOptions,
2237
+ errorOnInvalidContent: options.errorOnInvalidContent,
2238
+ });
2193
2239
  }
2194
2240
 
2195
- const setContent = (content, emitUpdate = false, parseOptions = {}) => ({ tr, editor, dispatch }) => {
2241
+ const setContent = (content, emitUpdate = false, parseOptions = {}, options = {}) => ({ tr, editor, dispatch }) => {
2242
+ var _a;
2196
2243
  const { doc } = tr;
2197
- const document = createDocument(content, editor.schema, parseOptions);
2244
+ let document;
2245
+ try {
2246
+ document = createDocument(content, editor.schema, parseOptions, {
2247
+ errorOnInvalidContent: (_a = options.errorOnInvalidContent) !== null && _a !== void 0 ? _a : editor.options.enableContentCheck,
2248
+ });
2249
+ }
2250
+ catch (e) {
2251
+ return false;
2252
+ }
2198
2253
  if (dispatch) {
2199
2254
  tr.replaceWith(0, doc.content.size, document).setMeta('preventUpdate', !emitUpdate);
2200
2255
  }
@@ -3883,6 +3938,7 @@ class Editor extends EventEmitter {
3883
3938
  enableInputRules: true,
3884
3939
  enablePasteRules: true,
3885
3940
  enableCoreExtensions: true,
3941
+ enableContentCheck: false,
3886
3942
  onBeforeCreate: () => null,
3887
3943
  onCreate: () => null,
3888
3944
  onUpdate: () => null,
@@ -3891,6 +3947,7 @@ class Editor extends EventEmitter {
3891
3947
  onFocus: () => null,
3892
3948
  onBlur: () => null,
3893
3949
  onDestroy: () => null,
3950
+ onContentError: ({ error }) => { throw error; },
3894
3951
  };
3895
3952
  this.isCapturingTransaction = false;
3896
3953
  this.capturedTransaction = null;
@@ -3900,6 +3957,7 @@ class Editor extends EventEmitter {
3900
3957
  this.createSchema();
3901
3958
  this.on('beforeCreate', this.options.onBeforeCreate);
3902
3959
  this.emit('beforeCreate', { editor: this });
3960
+ this.on('contentError', this.options.onContentError);
3903
3961
  this.createView();
3904
3962
  this.injectCSS();
3905
3963
  this.on('create', this.options.onCreate);
@@ -4059,7 +4117,28 @@ class Editor extends EventEmitter {
4059
4117
  * Creates a ProseMirror view.
4060
4118
  */
4061
4119
  createView() {
4062
- const doc = createDocument(this.options.content, this.schema, this.options.parseOptions);
4120
+ let doc;
4121
+ try {
4122
+ doc = createDocument(this.options.content, this.schema, this.options.parseOptions, { errorOnInvalidContent: this.options.enableContentCheck });
4123
+ }
4124
+ catch (e) {
4125
+ if (!(e instanceof Error) || !['[tiptap error]: Invalid JSON content', '[tiptap error]: Invalid HTML content'].includes(e.message)) {
4126
+ // Not the content error we were expecting
4127
+ throw e;
4128
+ }
4129
+ this.emit('contentError', {
4130
+ editor: this,
4131
+ error: e,
4132
+ disableCollaboration: () => {
4133
+ // To avoid syncing back invalid content, reinitialize the extensions without the collaboration extension
4134
+ this.options.extensions = this.options.extensions.filter(extension => extension.name !== 'collaboration');
4135
+ // Restart the initialization process by recreating the extension manager with the new set of extensions
4136
+ this.createExtensionManager();
4137
+ },
4138
+ });
4139
+ // Content is invalid, but attempt to create it anyway, stripping out the invalid parts
4140
+ doc = createDocument(this.options.content, this.schema, this.options.parseOptions, { errorOnInvalidContent: false });
4141
+ }
4063
4142
  const selection = resolveFocusPosition(doc, this.options.autofocus);
4064
4143
  this.view = new view.EditorView(this.options.element, {
4065
4144
  ...this.options.editorProps,