@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.cjs +119 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +119 -30
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +119 -30
- package/dist/index.umd.js.map +1 -1
- package/dist/packages/core/src/Editor.d.ts +4 -2
- package/dist/packages/core/src/commands/insertContentAt.d.ts +10 -0
- package/dist/packages/core/src/commands/setContent.d.ts +10 -1
- package/dist/packages/core/src/helpers/createDocument.d.ts +3 -1
- package/dist/packages/core/src/helpers/createNodeFromContent.d.ts +1 -0
- package/dist/packages/core/src/types.d.ts +23 -2
- package/package.json +2 -2
- package/src/Editor.ts +45 -4
- package/src/commands/insertContentAt.ts +26 -6
- package/src/commands/setContent.ts +21 -3
- package/src/commands/updateAttributes.ts +40 -25
- package/src/helpers/createDocument.ts +6 -1
- package/src/helpers/createNodeFromContent.ts +42 -2
- package/src/types.ts +23 -2
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
|
-
|
|
1783
|
-
|
|
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
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
}
|
|
@@ -3301,30 +3356,40 @@ const updateAttributes = (typeOrName, attributes = {}) => ({ tr, state, dispatch
|
|
|
3301
3356
|
markType = getMarkType(typeOrName, state.schema);
|
|
3302
3357
|
}
|
|
3303
3358
|
if (dispatch) {
|
|
3304
|
-
|
|
3359
|
+
let lastPos;
|
|
3360
|
+
let lastNode;
|
|
3361
|
+
let trimmedFrom;
|
|
3362
|
+
let trimmedTo;
|
|
3363
|
+
tr.selection.ranges.forEach((range) => {
|
|
3305
3364
|
const from = range.$from.pos;
|
|
3306
3365
|
const to = range.$to.pos;
|
|
3307
3366
|
state.doc.nodesBetween(from, to, (node, pos) => {
|
|
3308
3367
|
if (nodeType && nodeType === node.type) {
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
}
|
|
3314
|
-
if (markType && node.marks.length) {
|
|
3315
|
-
node.marks.forEach(mark => {
|
|
3316
|
-
if (markType === mark.type) {
|
|
3317
|
-
const trimmedFrom = Math.max(pos, from);
|
|
3318
|
-
const trimmedTo = Math.min(pos + node.nodeSize, to);
|
|
3319
|
-
tr.addMark(trimmedFrom, trimmedTo, markType.create({
|
|
3320
|
-
...mark.attrs,
|
|
3321
|
-
...attributes,
|
|
3322
|
-
}));
|
|
3323
|
-
}
|
|
3324
|
-
});
|
|
3368
|
+
trimmedFrom = Math.max(pos, from);
|
|
3369
|
+
trimmedTo = Math.min(pos + node.nodeSize, to);
|
|
3370
|
+
lastPos = pos;
|
|
3371
|
+
lastNode = node;
|
|
3325
3372
|
}
|
|
3326
3373
|
});
|
|
3327
3374
|
});
|
|
3375
|
+
if (lastNode) {
|
|
3376
|
+
if (lastPos !== undefined) {
|
|
3377
|
+
tr.setNodeMarkup(lastPos, undefined, {
|
|
3378
|
+
...lastNode.attrs,
|
|
3379
|
+
...attributes,
|
|
3380
|
+
});
|
|
3381
|
+
}
|
|
3382
|
+
if (markType && lastNode.marks.length) {
|
|
3383
|
+
lastNode.marks.forEach((mark) => {
|
|
3384
|
+
if (markType === mark.type) {
|
|
3385
|
+
tr.addMark(trimmedFrom, trimmedTo, markType.create({
|
|
3386
|
+
...mark.attrs,
|
|
3387
|
+
...attributes,
|
|
3388
|
+
}));
|
|
3389
|
+
}
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3328
3393
|
}
|
|
3329
3394
|
return true;
|
|
3330
3395
|
};
|
|
@@ -3883,6 +3948,7 @@ class Editor extends EventEmitter {
|
|
|
3883
3948
|
enableInputRules: true,
|
|
3884
3949
|
enablePasteRules: true,
|
|
3885
3950
|
enableCoreExtensions: true,
|
|
3951
|
+
enableContentCheck: false,
|
|
3886
3952
|
onBeforeCreate: () => null,
|
|
3887
3953
|
onCreate: () => null,
|
|
3888
3954
|
onUpdate: () => null,
|
|
@@ -3891,6 +3957,7 @@ class Editor extends EventEmitter {
|
|
|
3891
3957
|
onFocus: () => null,
|
|
3892
3958
|
onBlur: () => null,
|
|
3893
3959
|
onDestroy: () => null,
|
|
3960
|
+
onContentError: ({ error }) => { throw error; },
|
|
3894
3961
|
};
|
|
3895
3962
|
this.isCapturingTransaction = false;
|
|
3896
3963
|
this.capturedTransaction = null;
|
|
@@ -3900,6 +3967,7 @@ class Editor extends EventEmitter {
|
|
|
3900
3967
|
this.createSchema();
|
|
3901
3968
|
this.on('beforeCreate', this.options.onBeforeCreate);
|
|
3902
3969
|
this.emit('beforeCreate', { editor: this });
|
|
3970
|
+
this.on('contentError', this.options.onContentError);
|
|
3903
3971
|
this.createView();
|
|
3904
3972
|
this.injectCSS();
|
|
3905
3973
|
this.on('create', this.options.onCreate);
|
|
@@ -4059,7 +4127,28 @@ class Editor extends EventEmitter {
|
|
|
4059
4127
|
* Creates a ProseMirror view.
|
|
4060
4128
|
*/
|
|
4061
4129
|
createView() {
|
|
4062
|
-
|
|
4130
|
+
let doc;
|
|
4131
|
+
try {
|
|
4132
|
+
doc = createDocument(this.options.content, this.schema, this.options.parseOptions, { errorOnInvalidContent: this.options.enableContentCheck });
|
|
4133
|
+
}
|
|
4134
|
+
catch (e) {
|
|
4135
|
+
if (!(e instanceof Error) || !['[tiptap error]: Invalid JSON content', '[tiptap error]: Invalid HTML content'].includes(e.message)) {
|
|
4136
|
+
// Not the content error we were expecting
|
|
4137
|
+
throw e;
|
|
4138
|
+
}
|
|
4139
|
+
this.emit('contentError', {
|
|
4140
|
+
editor: this,
|
|
4141
|
+
error: e,
|
|
4142
|
+
disableCollaboration: () => {
|
|
4143
|
+
// To avoid syncing back invalid content, reinitialize the extensions without the collaboration extension
|
|
4144
|
+
this.options.extensions = this.options.extensions.filter(extension => extension.name !== 'collaboration');
|
|
4145
|
+
// Restart the initialization process by recreating the extension manager with the new set of extensions
|
|
4146
|
+
this.createExtensionManager();
|
|
4147
|
+
},
|
|
4148
|
+
});
|
|
4149
|
+
// Content is invalid, but attempt to create it anyway, stripping out the invalid parts
|
|
4150
|
+
doc = createDocument(this.options.content, this.schema, this.options.parseOptions, { errorOnInvalidContent: false });
|
|
4151
|
+
}
|
|
4063
4152
|
const selection = resolveFocusPosition(doc, this.options.autofocus);
|
|
4064
4153
|
this.view = new view.EditorView(this.options.element, {
|
|
4065
4154
|
...this.options.editorProps,
|