@tiptap/core 2.4.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.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;
@@ -1894,7 +1936,7 @@
1894
1936
  return commands$1.joinForward(state, dispatch);
1895
1937
  };
1896
1938
 
1897
- const joinItemBackward = () => ({ tr, state, dispatch, }) => {
1939
+ const joinItemBackward = () => ({ state, dispatch, tr, }) => {
1898
1940
  try {
1899
1941
  const point = transform.joinPoint(state.doc, state.selection.$from.pos, -1);
1900
1942
  if (point === null || point === undefined) {
@@ -1906,7 +1948,7 @@
1906
1948
  }
1907
1949
  return true;
1908
1950
  }
1909
- catch {
1951
+ catch (e) {
1910
1952
  return false;
1911
1953
  }
1912
1954
  };
@@ -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
  }
@@ -3877,6 +3932,7 @@ img.ProseMirror-separator {
3877
3932
  enableInputRules: true,
3878
3933
  enablePasteRules: true,
3879
3934
  enableCoreExtensions: true,
3935
+ enableContentCheck: false,
3880
3936
  onBeforeCreate: () => null,
3881
3937
  onCreate: () => null,
3882
3938
  onUpdate: () => null,
@@ -3885,6 +3941,7 @@ img.ProseMirror-separator {
3885
3941
  onFocus: () => null,
3886
3942
  onBlur: () => null,
3887
3943
  onDestroy: () => null,
3944
+ onContentError: ({ error }) => { throw error; },
3888
3945
  };
3889
3946
  this.isCapturingTransaction = false;
3890
3947
  this.capturedTransaction = null;
@@ -3894,6 +3951,7 @@ img.ProseMirror-separator {
3894
3951
  this.createSchema();
3895
3952
  this.on('beforeCreate', this.options.onBeforeCreate);
3896
3953
  this.emit('beforeCreate', { editor: this });
3954
+ this.on('contentError', this.options.onContentError);
3897
3955
  this.createView();
3898
3956
  this.injectCSS();
3899
3957
  this.on('create', this.options.onCreate);
@@ -4053,7 +4111,28 @@ img.ProseMirror-separator {
4053
4111
  * Creates a ProseMirror view.
4054
4112
  */
4055
4113
  createView() {
4056
- const doc = createDocument(this.options.content, this.schema, this.options.parseOptions);
4114
+ let doc;
4115
+ try {
4116
+ doc = createDocument(this.options.content, this.schema, this.options.parseOptions, { errorOnInvalidContent: this.options.enableContentCheck });
4117
+ }
4118
+ catch (e) {
4119
+ if (!(e instanceof Error) || !['[tiptap error]: Invalid JSON content', '[tiptap error]: Invalid HTML content'].includes(e.message)) {
4120
+ // Not the content error we were expecting
4121
+ throw e;
4122
+ }
4123
+ this.emit('contentError', {
4124
+ editor: this,
4125
+ error: e,
4126
+ disableCollaboration: () => {
4127
+ // To avoid syncing back invalid content, reinitialize the extensions without the collaboration extension
4128
+ this.options.extensions = this.options.extensions.filter(extension => extension.name !== 'collaboration');
4129
+ // Restart the initialization process by recreating the extension manager with the new set of extensions
4130
+ this.createExtensionManager();
4131
+ },
4132
+ });
4133
+ // Content is invalid, but attempt to create it anyway, stripping out the invalid parts
4134
+ doc = createDocument(this.options.content, this.schema, this.options.parseOptions, { errorOnInvalidContent: false });
4135
+ }
4057
4136
  const selection = resolveFocusPosition(doc, this.options.autofocus);
4058
4137
  this.view = new view.EditorView(this.options.element, {
4059
4138
  ...this.options.editorProps,
@@ -4314,7 +4393,8 @@ img.ProseMirror-separator {
4314
4393
  tr.replaceWith(matchStart, end, newNode);
4315
4394
  }
4316
4395
  else if (match[0]) {
4317
- tr.insert(start - 1, config.type.create(attributes)).delete(tr.mapping.map(start), tr.mapping.map(end));
4396
+ const insertionStart = config.type.isInline ? start : start - 1;
4397
+ tr.insert(insertionStart, config.type.create(attributes)).delete(tr.mapping.map(start), tr.mapping.map(end));
4318
4398
  }
4319
4399
  tr.scrollIntoView();
4320
4400
  },