@tiptap/core 2.5.0-beta.3 → 2.5.0-beta.5

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.
@@ -338,6 +338,6 @@ export declare class Extension<Options = any, Storage = any> {
338
338
  config: ExtensionConfig;
339
339
  constructor(config?: Partial<ExtensionConfig<Options, Storage>>);
340
340
  static create<O = any, S = any>(config?: Partial<ExtensionConfig<O, S>>): Extension<O, S>;
341
- configure(options?: Partial<Options>): Extension<Options, Storage>;
341
+ configure(options?: Partial<Options>): Extension<any, any>;
342
342
  extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig?: Partial<ExtensionConfig<ExtendedOptions, ExtendedStorage>>): Extension<ExtendedOptions, ExtendedStorage>;
343
343
  }
@@ -442,7 +442,7 @@ export declare class Mark<Options = any, Storage = any> {
442
442
  config: MarkConfig;
443
443
  constructor(config?: Partial<MarkConfig<Options, Storage>>);
444
444
  static create<O = any, S = any>(config?: Partial<MarkConfig<O, S>>): Mark<O, S>;
445
- configure(options?: Partial<Options>): Mark<Options, Storage>;
445
+ configure(options?: Partial<Options>): Mark<any, any>;
446
446
  extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig?: Partial<MarkConfig<ExtendedOptions, ExtendedStorage>>): Mark<ExtendedOptions, ExtendedStorage>;
447
447
  static handleExit({ editor, mark }: {
448
448
  editor: Editor;
@@ -606,6 +606,6 @@ export declare class Node<Options = any, Storage = any> {
606
606
  config: NodeConfig;
607
607
  constructor(config?: Partial<NodeConfig<Options, Storage>>);
608
608
  static create<O = any, S = any>(config?: Partial<NodeConfig<O, S>>): Node<O, S>;
609
- configure(options?: Partial<Options>): Node<Options, Storage>;
609
+ configure(options?: Partial<Options>): Node<any, any>;
610
610
  extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig?: Partial<NodeConfig<ExtendedOptions, ExtendedStorage>>): Node<ExtendedOptions, ExtendedStorage>;
611
611
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/core",
3
3
  "description": "headless rich text editor",
4
- "version": "2.5.0-beta.3",
4
+ "version": "2.5.0-beta.5",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -32,7 +32,7 @@
32
32
  "dist"
33
33
  ],
34
34
  "devDependencies": {
35
- "@tiptap/pm": "^2.5.0-beta.3"
35
+ "@tiptap/pm": "^2.5.0-beta.5"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@tiptap/pm": "^2.0.0"
package/src/Extension.ts CHANGED
@@ -457,17 +457,17 @@ export class Extension<Options = any, Storage = any> {
457
457
  configure(options: Partial<Options> = {}) {
458
458
  // return a new instance so we can use the same extension
459
459
  // with different calls of `configure`
460
- const extension = this.extend()
461
-
460
+ const extension = this.extend({
461
+ ...this.config,
462
+ addOptions() {
463
+ return mergeDeep(this.parent?.() || {}, options) as Options
464
+ },
465
+ })
466
+
467
+ // Always preserve the current name
468
+ extension.name = this.name
469
+ // Set the parent to be our parent
462
470
  extension.parent = this.parent
463
- extension.options = mergeDeep(this.options as Record<string, any>, options) as Options
464
-
465
- extension.storage = callOrReturn(
466
- getExtensionField<AnyConfig['addStorage']>(extension, 'addStorage', {
467
- name: extension.name,
468
- options: extension.options,
469
- }),
470
- )
471
471
 
472
472
  return extension
473
473
  }
@@ -483,7 +483,7 @@ export class Extension<Options = any, Storage = any> {
483
483
 
484
484
  extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name
485
485
 
486
- if (extendedConfig.defaultOptions) {
486
+ if (extendedConfig.defaultOptions && Object.keys(extendedConfig.defaultOptions).length > 0) {
487
487
  console.warn(
488
488
  `[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`,
489
489
  )
@@ -181,7 +181,7 @@ export class ExtensionManager {
181
181
  let defaultBindings: Record<string, () => boolean> = {}
182
182
 
183
183
  // bind exit handling
184
- if (extension.type === 'mark' && extension.config.exitable) {
184
+ if (extension.type === 'mark' && getExtensionField<AnyConfig['exitable']>(extension, 'exitable', context)) {
185
185
  defaultBindings.ArrowRight = () => Mark.handleExit({ editor, mark: extension as Mark })
186
186
  }
187
187
 
package/src/Mark.ts CHANGED
@@ -589,16 +589,17 @@ export class Mark<Options = any, Storage = any> {
589
589
  configure(options: Partial<Options> = {}) {
590
590
  // return a new instance so we can use the same extension
591
591
  // with different calls of `configure`
592
- const extension = this.extend()
593
-
594
- extension.options = mergeDeep(this.options as Record<string, any>, options) as Options
592
+ const extension = this.extend({
593
+ ...this.config,
594
+ addOptions() {
595
+ return mergeDeep(this.parent?.() || {}, options) as Options
596
+ },
597
+ })
595
598
 
596
- extension.storage = callOrReturn(
597
- getExtensionField<AnyConfig['addStorage']>(extension, 'addStorage', {
598
- name: extension.name,
599
- options: extension.options,
600
- }),
601
- )
599
+ // Always preserve the current name
600
+ extension.name = this.name
601
+ // Set the parent to be our parent
602
+ extension.parent = this.parent
602
603
 
603
604
  return extension
604
605
  }
@@ -606,7 +607,7 @@ export class Mark<Options = any, Storage = any> {
606
607
  extend<ExtendedOptions = Options, ExtendedStorage = Storage>(
607
608
  extendedConfig: Partial<MarkConfig<ExtendedOptions, ExtendedStorage>> = {},
608
609
  ) {
609
- const extension = new Mark<ExtendedOptions, ExtendedStorage>({ ...this.config, ...extendedConfig })
610
+ const extension = new Mark<ExtendedOptions, ExtendedStorage>(extendedConfig)
610
611
 
611
612
  extension.parent = this
612
613
 
@@ -614,7 +615,7 @@ export class Mark<Options = any, Storage = any> {
614
615
 
615
616
  extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name
616
617
 
617
- if (extendedConfig.defaultOptions) {
618
+ if (extendedConfig.defaultOptions && Object.keys(extendedConfig.defaultOptions).length > 0) {
618
619
  console.warn(
619
620
  `[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`,
620
621
  )
package/src/Node.ts CHANGED
@@ -780,16 +780,17 @@ export class Node<Options = any, Storage = any> {
780
780
  configure(options: Partial<Options> = {}) {
781
781
  // return a new instance so we can use the same extension
782
782
  // with different calls of `configure`
783
- const extension = this.extend()
784
-
785
- extension.options = mergeDeep(this.options as Record<string, any>, options) as Options
783
+ const extension = this.extend({
784
+ ...this.config,
785
+ addOptions() {
786
+ return mergeDeep(this.parent?.() || {}, options) as Options
787
+ },
788
+ })
786
789
 
787
- extension.storage = callOrReturn(
788
- getExtensionField<AnyConfig['addStorage']>(extension, 'addStorage', {
789
- name: extension.name,
790
- options: extension.options,
791
- }),
792
- )
790
+ // Always preserve the current name
791
+ extension.name = this.name
792
+ // Set the parent to be our parent
793
+ extension.parent = this.parent
793
794
 
794
795
  return extension
795
796
  }
@@ -797,7 +798,7 @@ export class Node<Options = any, Storage = any> {
797
798
  extend<ExtendedOptions = Options, ExtendedStorage = Storage>(
798
799
  extendedConfig: Partial<NodeConfig<ExtendedOptions, ExtendedStorage>> = {},
799
800
  ) {
800
- const extension = new Node<ExtendedOptions, ExtendedStorage>({ ...this.config, ...extendedConfig })
801
+ const extension = new Node<ExtendedOptions, ExtendedStorage>(extendedConfig)
801
802
 
802
803
  extension.parent = this
803
804
 
@@ -805,7 +806,7 @@ export class Node<Options = any, Storage = any> {
805
806
 
806
807
  extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name
807
808
 
808
- if (extendedConfig.defaultOptions) {
809
+ if (extendedConfig.defaultOptions && Object.keys(extendedConfig.defaultOptions).length > 0) {
809
810
  console.warn(
810
811
  `[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`,
811
812
  )
@@ -84,11 +84,6 @@ export const insertContentAt: RawCommands['insertContentAt'] = (position, value,
84
84
  return false
85
85
  }
86
86
 
87
- // don’t dispatch an empty fragment because this can lead to strange errors
88
- if (content.toString() === '<>') {
89
- return true
90
- }
91
-
92
87
  let { from, to } = typeof position === 'number' ? { from: position, to: position } : { from: position.from, to: position.to }
93
88
 
94
89
  let isOnlyTextContent = true
@@ -1,4 +1,4 @@
1
- import { Fragment, Node as ProseMirrorNode, ParseOptions } from '@tiptap/pm/model'
1
+ import { ParseOptions } from '@tiptap/pm/model'
2
2
 
3
3
  import { createDocument } from '../helpers/createDocument.js'
4
4
  import { Content, RawCommands } from '../types.js'
@@ -44,22 +44,34 @@ declare module '@tiptap/core' {
44
44
  }
45
45
  }
46
46
 
47
- export const setContent: RawCommands['setContent'] = (content, emitUpdate = false, parseOptions = {}, options = {}) => ({ tr, editor, dispatch }) => {
47
+ export const setContent: RawCommands['setContent'] = (content, emitUpdate = false, parseOptions = {}, options = {}) => ({
48
+ editor, tr, dispatch, commands,
49
+ }) => {
48
50
  const { doc } = tr
49
51
 
50
- let document: Fragment | ProseMirrorNode
51
-
52
- try {
53
- document = createDocument(content, editor.schema, parseOptions, {
52
+ // This is to keep backward compatibility with the previous behavior
53
+ // TODO remove this in the next major version
54
+ if (parseOptions.preserveWhitespace !== 'full') {
55
+ const document = createDocument(content, editor.schema, parseOptions, {
54
56
  errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
55
57
  })
56
- } catch (e) {
57
- return false
58
+
59
+ if (dispatch) {
60
+ tr.replaceWith(0, doc.content.size, document).setMeta('preventUpdate', !emitUpdate)
61
+ }
62
+ return true
58
63
  }
59
64
 
60
65
  if (dispatch) {
61
- tr.replaceWith(0, doc.content.size, document).setMeta('preventUpdate', !emitUpdate)
66
+ tr.setMeta('preventUpdate', !emitUpdate)
62
67
  }
63
68
 
64
- return true
69
+ return commands.insertContentAt(
70
+ { from: 0, to: doc.content.size },
71
+ content,
72
+ {
73
+ parseOptions,
74
+ errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
75
+ },
76
+ )
65
77
  }
@@ -1,7 +1,4 @@
1
- import {
2
- Mark, MarkType, Node, NodeType,
3
- } from '@tiptap/pm/model'
4
- import { SelectionRange } from '@tiptap/pm/state'
1
+ import { MarkType, NodeType } from '@tiptap/pm/model'
5
2
 
6
3
  import { getMarkType } from '../helpers/getMarkType.js'
7
4
  import { getNodeType } from '../helpers/getNodeType.js'
@@ -54,49 +51,37 @@ export const updateAttributes: RawCommands['updateAttributes'] = (typeOrName, at
54
51
  }
55
52
 
56
53
  if (dispatch) {
57
- let lastPos: number | undefined
58
- let lastNode: Node | undefined
59
- let trimmedFrom: number
60
- let trimmedTo: number
61
-
62
- tr.selection.ranges.forEach((range: SelectionRange) => {
54
+ tr.selection.ranges.forEach(range => {
63
55
  const from = range.$from.pos
64
56
  const to = range.$to.pos
65
57
 
66
- state.doc.nodesBetween(from, to, (node: Node, pos: number) => {
58
+ state.doc.nodesBetween(from, to, (node, pos) => {
67
59
  if (nodeType && nodeType === node.type) {
68
- trimmedFrom = Math.max(pos, from)
69
- trimmedTo = Math.min(pos + node.nodeSize, to)
70
- lastPos = pos
71
- lastNode = node
60
+ tr.setNodeMarkup(pos, undefined, {
61
+ ...node.attrs,
62
+ ...attributes,
63
+ })
72
64
  }
73
- })
74
- })
75
-
76
- if (lastNode) {
77
65
 
78
- if (lastPos !== undefined) {
79
- tr.setNodeMarkup(lastPos, undefined, {
80
- ...lastNode.attrs,
81
- ...attributes,
82
- })
83
- }
66
+ if (markType && node.marks.length) {
67
+ node.marks.forEach(mark => {
68
+ if (markType === mark.type) {
69
+ const trimmedFrom = Math.max(pos, from)
70
+ const trimmedTo = Math.min(pos + node.nodeSize, to)
84
71
 
85
- if (markType && lastNode.marks.length) {
86
- lastNode.marks.forEach((mark: Mark) => {
87
- if (markType === mark.type) {
88
- tr.addMark(
89
- trimmedFrom,
90
- trimmedTo,
91
- markType.create({
92
- ...mark.attrs,
93
- ...attributes,
94
- }),
95
- )
96
- }
97
- })
98
- }
99
- }
72
+ tr.addMark(
73
+ trimmedFrom,
74
+ trimmedTo,
75
+ markType.create({
76
+ ...mark.attrs,
77
+ ...attributes,
78
+ }),
79
+ )
80
+ }
81
+ })
82
+ }
83
+ })
84
+ })
100
85
  }
101
86
 
102
87
  return true
@@ -10,7 +10,7 @@ export const Tabindex = Extension.create({
10
10
  new Plugin({
11
11
  key: new PluginKey('tabindex'),
12
12
  props: {
13
- attributes: this.editor.isEditable ? { tabindex: '0' } : {},
13
+ attributes: (): { [name: string]: string; } => (this.editor.isEditable ? { tabindex: '0' } : {}),
14
14
  },
15
15
  }),
16
16
  ]
@@ -60,6 +60,7 @@ export function createNodeFromContent(
60
60
  if (isTextContent) {
61
61
  let schemaToUse = schema
62
62
  let hasInvalidContent = false
63
+ let invalidContent = ''
63
64
 
64
65
  // Only ever check for invalid content if we're supposed to throw an error
65
66
  if (options.errorOnInvalidContent) {
@@ -75,9 +76,11 @@ export function createNodeFromContent(
75
76
  parseDOM: [
76
77
  {
77
78
  tag: '*',
78
- getAttrs: () => {
79
+ getAttrs: e => {
79
80
  // If this is ever called, we know that the content has something that we don't know how to handle in the schema
80
81
  hasInvalidContent = true
82
+ // Try to stringify the element for a more helpful error message
83
+ invalidContent = typeof e === 'string' ? e : e.outerHTML
81
84
  return null
82
85
  },
83
86
  },
@@ -94,7 +97,7 @@ export function createNodeFromContent(
94
97
  : parser.parse(elementFromString(content), options.parseOptions)
95
98
 
96
99
  if (options.errorOnInvalidContent && hasInvalidContent) {
97
- throw new Error('[tiptap error]: Invalid HTML content')
100
+ throw new Error('[tiptap error]: Invalid HTML content', { cause: new Error(`Invalid element found: ${invalidContent}`) })
98
101
  }
99
102
 
100
103
  return response
@@ -77,6 +77,7 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
77
77
  getExtensionField<NodeConfig['draggable']>(extension, 'draggable', context),
78
78
  ),
79
79
  code: callOrReturn(getExtensionField<NodeConfig['code']>(extension, 'code', context)),
80
+ whitespace: callOrReturn(getExtensionField<NodeConfig['whitespace']>(extension, 'whitespace', context)),
80
81
  defining: callOrReturn(
81
82
  getExtensionField<NodeConfig['defining']>(extension, 'defining', context),
82
83
  ),