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

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.umd.js CHANGED
@@ -1768,15 +1768,49 @@
1768
1768
  return schema.nodeFromJSON(content);
1769
1769
  }
1770
1770
  catch (error) {
1771
+ if (options.errorOnInvalidContent) {
1772
+ throw new Error('[tiptap error]: Invalid JSON content', { cause: error });
1773
+ }
1771
1774
  console.warn('[tiptap warn]: Invalid content.', 'Passed value:', content, 'Error:', error);
1772
1775
  return createNodeFromContent('', schema, options);
1773
1776
  }
1774
1777
  }
1775
1778
  if (isTextContent) {
1776
- const parser = model.DOMParser.fromSchema(schema);
1777
- return options.slice
1779
+ let schemaToUse = schema;
1780
+ let hasInvalidContent = false;
1781
+ // Only ever check for invalid content if we're supposed to throw an error
1782
+ if (options.errorOnInvalidContent) {
1783
+ schemaToUse = new model.Schema({
1784
+ topNode: schema.spec.topNode,
1785
+ marks: schema.spec.marks,
1786
+ // Prosemirror's schemas are executed such that: the last to execute, matches last
1787
+ // 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
1788
+ nodes: schema.spec.nodes.append({
1789
+ __tiptap__private__unknown__catch__all__node: {
1790
+ content: 'inline*',
1791
+ group: 'block',
1792
+ parseDOM: [
1793
+ {
1794
+ tag: '*',
1795
+ getAttrs: () => {
1796
+ // If this is ever called, we know that the content has something that we don't know how to handle in the schema
1797
+ hasInvalidContent = true;
1798
+ return null;
1799
+ },
1800
+ },
1801
+ ],
1802
+ },
1803
+ }),
1804
+ });
1805
+ }
1806
+ const parser = model.DOMParser.fromSchema(schemaToUse);
1807
+ const response = options.slice
1778
1808
  ? parser.parseSlice(elementFromString(content), options.parseOptions).content
1779
1809
  : parser.parse(elementFromString(content), options.parseOptions);
1810
+ if (options.errorOnInvalidContent && hasInvalidContent) {
1811
+ throw new Error('[tiptap error]: Invalid HTML content');
1812
+ }
1813
+ return response;
1780
1814
  }
1781
1815
  return createNodeFromContent('', schema, options);
1782
1816
  }
@@ -1805,6 +1839,7 @@
1805
1839
  return nodeOrFragment.toString().startsWith('<');
1806
1840
  };
1807
1841
  const insertContentAt = (position, value, options) => ({ tr, dispatch, editor }) => {
1842
+ var _a;
1808
1843
  if (dispatch) {
1809
1844
  options = {
1810
1845
  parseOptions: {},
@@ -1813,12 +1848,19 @@
1813
1848
  applyPasteRules: false,
1814
1849
  ...options,
1815
1850
  };
1816
- const content = createNodeFromContent(value, editor.schema, {
1817
- parseOptions: {
1818
- preserveWhitespace: 'full',
1819
- ...options.parseOptions,
1820
- },
1821
- });
1851
+ let content;
1852
+ try {
1853
+ content = createNodeFromContent(value, editor.schema, {
1854
+ parseOptions: {
1855
+ preserveWhitespace: 'full',
1856
+ ...options.parseOptions,
1857
+ },
1858
+ errorOnInvalidContent: (_a = options.errorOnInvalidContent) !== null && _a !== void 0 ? _a : editor.options.enableContentCheck,
1859
+ });
1860
+ }
1861
+ catch (e) {
1862
+ return false;
1863
+ }
1822
1864
  // don’t dispatch an empty fragment because this can lead to strange errors
1823
1865
  if (content.toString() === '<>') {
1824
1866
  return true;
@@ -2182,13 +2224,26 @@
2182
2224
  * @param parseOptions Options for the parser
2183
2225
  * @returns The created Prosemirror document node
2184
2226
  */
2185
- function createDocument(content, schema, parseOptions = {}) {
2186
- return createNodeFromContent(content, schema, { slice: false, parseOptions });
2227
+ function createDocument(content, schema, parseOptions = {}, options = {}) {
2228
+ return createNodeFromContent(content, schema, {
2229
+ slice: false,
2230
+ parseOptions,
2231
+ errorOnInvalidContent: options.errorOnInvalidContent,
2232
+ });
2187
2233
  }
2188
2234
 
2189
- const setContent = (content, emitUpdate = false, parseOptions = {}) => ({ tr, editor, dispatch }) => {
2235
+ const setContent = (content, emitUpdate = false, parseOptions = {}, options = {}) => ({ tr, editor, dispatch }) => {
2236
+ var _a;
2190
2237
  const { doc } = tr;
2191
- const document = createDocument(content, editor.schema, parseOptions);
2238
+ let document;
2239
+ try {
2240
+ document = createDocument(content, editor.schema, parseOptions, {
2241
+ errorOnInvalidContent: (_a = options.errorOnInvalidContent) !== null && _a !== void 0 ? _a : editor.options.enableContentCheck,
2242
+ });
2243
+ }
2244
+ catch (e) {
2245
+ return false;
2246
+ }
2192
2247
  if (dispatch) {
2193
2248
  tr.replaceWith(0, doc.content.size, document).setMeta('preventUpdate', !emitUpdate);
2194
2249
  }
@@ -3295,30 +3350,40 @@
3295
3350
  markType = getMarkType(typeOrName, state.schema);
3296
3351
  }
3297
3352
  if (dispatch) {
3298
- tr.selection.ranges.forEach(range => {
3353
+ let lastPos;
3354
+ let lastNode;
3355
+ let trimmedFrom;
3356
+ let trimmedTo;
3357
+ tr.selection.ranges.forEach((range) => {
3299
3358
  const from = range.$from.pos;
3300
3359
  const to = range.$to.pos;
3301
3360
  state.doc.nodesBetween(from, to, (node, pos) => {
3302
3361
  if (nodeType && nodeType === node.type) {
3303
- tr.setNodeMarkup(pos, undefined, {
3304
- ...node.attrs,
3305
- ...attributes,
3306
- });
3307
- }
3308
- if (markType && node.marks.length) {
3309
- node.marks.forEach(mark => {
3310
- if (markType === mark.type) {
3311
- const trimmedFrom = Math.max(pos, from);
3312
- const trimmedTo = Math.min(pos + node.nodeSize, to);
3313
- tr.addMark(trimmedFrom, trimmedTo, markType.create({
3314
- ...mark.attrs,
3315
- ...attributes,
3316
- }));
3317
- }
3318
- });
3362
+ trimmedFrom = Math.max(pos, from);
3363
+ trimmedTo = Math.min(pos + node.nodeSize, to);
3364
+ lastPos = pos;
3365
+ lastNode = node;
3319
3366
  }
3320
3367
  });
3321
3368
  });
3369
+ if (lastNode) {
3370
+ if (lastPos !== undefined) {
3371
+ tr.setNodeMarkup(lastPos, undefined, {
3372
+ ...lastNode.attrs,
3373
+ ...attributes,
3374
+ });
3375
+ }
3376
+ if (markType && lastNode.marks.length) {
3377
+ lastNode.marks.forEach((mark) => {
3378
+ if (markType === mark.type) {
3379
+ tr.addMark(trimmedFrom, trimmedTo, markType.create({
3380
+ ...mark.attrs,
3381
+ ...attributes,
3382
+ }));
3383
+ }
3384
+ });
3385
+ }
3386
+ }
3322
3387
  }
3323
3388
  return true;
3324
3389
  };
@@ -3877,6 +3942,7 @@ img.ProseMirror-separator {
3877
3942
  enableInputRules: true,
3878
3943
  enablePasteRules: true,
3879
3944
  enableCoreExtensions: true,
3945
+ enableContentCheck: false,
3880
3946
  onBeforeCreate: () => null,
3881
3947
  onCreate: () => null,
3882
3948
  onUpdate: () => null,
@@ -3885,6 +3951,7 @@ img.ProseMirror-separator {
3885
3951
  onFocus: () => null,
3886
3952
  onBlur: () => null,
3887
3953
  onDestroy: () => null,
3954
+ onContentError: ({ error }) => { throw error; },
3888
3955
  };
3889
3956
  this.isCapturingTransaction = false;
3890
3957
  this.capturedTransaction = null;
@@ -3894,6 +3961,7 @@ img.ProseMirror-separator {
3894
3961
  this.createSchema();
3895
3962
  this.on('beforeCreate', this.options.onBeforeCreate);
3896
3963
  this.emit('beforeCreate', { editor: this });
3964
+ this.on('contentError', this.options.onContentError);
3897
3965
  this.createView();
3898
3966
  this.injectCSS();
3899
3967
  this.on('create', this.options.onCreate);
@@ -4053,7 +4121,28 @@ img.ProseMirror-separator {
4053
4121
  * Creates a ProseMirror view.
4054
4122
  */
4055
4123
  createView() {
4056
- const doc = createDocument(this.options.content, this.schema, this.options.parseOptions);
4124
+ let doc;
4125
+ try {
4126
+ doc = createDocument(this.options.content, this.schema, this.options.parseOptions, { errorOnInvalidContent: this.options.enableContentCheck });
4127
+ }
4128
+ catch (e) {
4129
+ if (!(e instanceof Error) || !['[tiptap error]: Invalid JSON content', '[tiptap error]: Invalid HTML content'].includes(e.message)) {
4130
+ // Not the content error we were expecting
4131
+ throw e;
4132
+ }
4133
+ this.emit('contentError', {
4134
+ editor: this,
4135
+ error: e,
4136
+ disableCollaboration: () => {
4137
+ // To avoid syncing back invalid content, reinitialize the extensions without the collaboration extension
4138
+ this.options.extensions = this.options.extensions.filter(extension => extension.name !== 'collaboration');
4139
+ // Restart the initialization process by recreating the extension manager with the new set of extensions
4140
+ this.createExtensionManager();
4141
+ },
4142
+ });
4143
+ // Content is invalid, but attempt to create it anyway, stripping out the invalid parts
4144
+ doc = createDocument(this.options.content, this.schema, this.options.parseOptions, { errorOnInvalidContent: false });
4145
+ }
4057
4146
  const selection = resolveFocusPosition(doc, this.options.autofocus);
4058
4147
  this.view = new view.EditorView(this.options.element, {
4059
4148
  ...this.options.editorProps,