@tiptap/core 2.0.0-beta.217 → 2.0.0-beta.219

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.
@@ -1,15 +1,15 @@
1
1
  import { Node as ProseMirrorNode } from '@tiptap/pm/model';
2
- import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view';
2
+ import { NodeView as ProseMirrorNodeView } from '@tiptap/pm/view';
3
3
  import { Editor as CoreEditor } from './Editor';
4
4
  import { Node } from './Node';
5
- import { NodeViewRendererOptions, NodeViewRendererProps } from './types';
5
+ import { DecorationWithType, NodeViewRendererOptions, NodeViewRendererProps } from './types';
6
6
  export declare class NodeView<Component, NodeEditor extends CoreEditor = CoreEditor, Options extends NodeViewRendererOptions = NodeViewRendererOptions> implements ProseMirrorNodeView {
7
7
  component: Component;
8
8
  editor: NodeEditor;
9
9
  options: Options;
10
10
  extension: Node;
11
11
  node: ProseMirrorNode;
12
- decorations: Decoration[];
12
+ decorations: DecorationWithType[];
13
13
  getPos: any;
14
14
  isDragging: boolean;
15
15
  constructor(component: Component, props: NodeViewRendererProps, options?: Partial<Options>);
@@ -6,7 +6,7 @@ declare module '@tiptap/core' {
6
6
  /**
7
7
  * Toggle between different list types.
8
8
  */
9
- toggleList: (listTypeOrName: string | NodeType, itemTypeOrName: string | NodeType) => ReturnType;
9
+ toggleList: (listTypeOrName: string | NodeType, itemTypeOrName: string | NodeType, keepMarks?: boolean) => ReturnType;
10
10
  };
11
11
  }
12
12
  }
@@ -1,4 +1,7 @@
1
1
  export * from './combineTransactionSteps';
2
+ export * from './createChainableState';
3
+ export * from './createDocument';
4
+ export * from './createNodeFromContent';
2
5
  export * from './defaultBlockAt';
3
6
  export * from './findChildren';
4
7
  export * from './findChildrenInRange';
@@ -8,6 +11,7 @@ export * from './generateHTML';
8
11
  export * from './generateJSON';
9
12
  export * from './generateText';
10
13
  export * from './getAttributes';
14
+ export * from './getAttributesFromExtensions';
11
15
  export * from './getChangedRanges';
12
16
  export * from './getDebugJSON';
13
17
  export * from './getExtensionField';
@@ -18,12 +22,19 @@ export * from './getMarksBetween';
18
22
  export * from './getMarkType';
19
23
  export * from './getNodeAttributes';
20
24
  export * from './getNodeType';
25
+ export * from './getRenderedAttributes';
21
26
  export * from './getSchema';
27
+ export * from './getSchemaByResolvedExtensions';
28
+ export * from './getSchemaTypeByName';
29
+ export * from './getSchemaTypeNameByName';
30
+ export * from './getSplittedAttributes';
22
31
  export * from './getText';
23
32
  export * from './getTextBetween';
24
33
  export * from './getTextContentFromNodes';
25
34
  export * from './getTextSerializersFromSchema';
35
+ export * from './injectExtensionAttributesToParseRule';
26
36
  export * from './isActive';
37
+ export * from './isExtensionRulesEnabled';
27
38
  export * from './isList';
28
39
  export * from './isMarkActive';
29
40
  export * from './isNodeActive';
@@ -31,3 +42,6 @@ export * from './isNodeEmpty';
31
42
  export * from './isNodeSelection';
32
43
  export * from './isTextSelection';
33
44
  export * from './posToDOMRect';
45
+ export * from './resolveFocusPosition';
46
+ export * from './selectionToInsertionEnd';
47
+ export * from './splitExtensions';
@@ -1,4 +1,5 @@
1
1
  import { Node as ProseMirrorNode, NodeType } from '@tiptap/pm/model';
2
+ import { Editor } from '../Editor';
2
3
  import { InputRule, InputRuleFinder } from '../InputRule';
3
4
  import { ExtendedRegExpMatchArray } from '../types';
4
5
  /**
@@ -18,6 +19,9 @@ import { ExtendedRegExpMatchArray } from '../types';
18
19
  export declare function wrappingInputRule(config: {
19
20
  find: InputRuleFinder;
20
21
  type: NodeType;
22
+ keepMarks?: boolean;
23
+ keepAttributes?: boolean;
24
+ editor?: Editor;
21
25
  getAttributes?: Record<string, any> | ((match: ExtendedRegExpMatchArray) => Record<string, any>) | false | null;
22
26
  joinPredicate?: (match: ExtendedRegExpMatchArray, node: ProseMirrorNode) => boolean;
23
27
  }): InputRule;
@@ -1,12 +1,12 @@
1
1
  import { NodeType } from '@tiptap/pm/model';
2
- import { PasteRule } from '../PasteRule';
2
+ import { PasteRule, PasteRuleFinder } from '../PasteRule';
3
3
  import { ExtendedRegExpMatchArray } from '../types';
4
4
  /**
5
5
  * Build an paste rule that adds a node when the
6
6
  * matched text is pasted into it.
7
7
  */
8
8
  export declare function nodePasteRule(config: {
9
- find: RegExp;
9
+ find: PasteRuleFinder;
10
10
  type: NodeType;
11
11
  getAttributes?: Record<string, any> | ((match: ExtendedRegExpMatchArray) => Record<string, any>) | false | null;
12
12
  }): PasteRule;
@@ -1,4 +1,4 @@
1
- import { Mark as ProseMirrorMark, Node as ProseMirrorNode, ParseOptions } from '@tiptap/pm/model';
1
+ import { Mark as ProseMirrorMark, Node as ProseMirrorNode, NodeType, ParseOptions } from '@tiptap/pm/model';
2
2
  import { EditorState, Transaction } from '@tiptap/pm/state';
3
3
  import { Decoration, EditorProps, EditorView, NodeView } from '@tiptap/pm/view';
4
4
  import { Commands, ExtensionConfig, MarkConfig, NodeConfig } from '.';
@@ -135,10 +135,13 @@ export declare type ValuesOf<T> = T[keyof T];
135
135
  export declare type KeysWithTypeOf<T, Type> = {
136
136
  [P in keyof T]: T[P] extends Type ? P : never;
137
137
  }[keyof T];
138
+ export declare type DecorationWithType = Decoration & {
139
+ type: NodeType;
140
+ };
138
141
  export declare type NodeViewProps = {
139
142
  editor: Editor;
140
143
  node: ProseMirrorNode;
141
- decorations: Decoration[];
144
+ decorations: DecorationWithType[];
142
145
  selected: boolean;
143
146
  extension: Node;
144
147
  getPos: () => number;
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.0.0-beta.217",
4
+ "version": "2.0.0-beta.219",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -35,7 +35,7 @@
35
35
  "@tiptap/pm": "^2.0.0-beta.209"
36
36
  },
37
37
  "devDependencies": {
38
- "@tiptap/pm": "^2.0.0-beta.217"
38
+ "@tiptap/pm": "^2.0.0-beta.219"
39
39
  },
40
40
  "repository": {
41
41
  "type": "git",
package/src/Editor.ts CHANGED
@@ -426,8 +426,8 @@ export class Editor extends EventEmitter<EditorEvents> {
426
426
  return getText(this.state.doc, {
427
427
  blockSeparator,
428
428
  textSerializers: {
429
- ...textSerializers,
430
429
  ...getTextSerializersFromSchema(this.schema),
430
+ ...textSerializers,
431
431
  },
432
432
  })
433
433
  }
package/src/NodeView.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Node as ProseMirrorNode } from '@tiptap/pm/model'
2
2
  import { NodeSelection } from '@tiptap/pm/state'
3
- import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
3
+ import { NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
4
4
 
5
5
  import { Editor as CoreEditor } from './Editor'
6
6
  import { Node } from './Node'
7
- import { NodeViewRendererOptions, NodeViewRendererProps } from './types'
7
+ import { DecorationWithType, NodeViewRendererOptions, NodeViewRendererProps } from './types'
8
8
  import { isiOS } from './utilities/isiOS'
9
9
 
10
10
  export class NodeView<
@@ -22,7 +22,7 @@ export class NodeView<
22
22
 
23
23
  node: ProseMirrorNode
24
24
 
25
- decorations: Decoration[]
25
+ decorations: DecorationWithType[]
26
26
 
27
27
  getPos: any
28
28
 
@@ -38,7 +38,7 @@ export class NodeView<
38
38
  } as Options
39
39
  this.extension = props.extension
40
40
  this.node = props.node
41
- this.decorations = props.decorations
41
+ this.decorations = props.decorations as DecorationWithType[]
42
42
  this.getPos = props.getPos
43
43
  this.mount()
44
44
  }
@@ -113,11 +113,12 @@ export class NodeView<
113
113
  return false
114
114
  }
115
115
 
116
+ const isDragEvent = event.type.startsWith('drag')
116
117
  const isDropEvent = event.type === 'drop'
117
118
  const isInput = ['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'].includes(target.tagName) || target.isContentEditable
118
119
 
119
120
  // any input event within node views should be ignored by ProseMirror
120
- if (isInput && !isDropEvent) {
121
+ if (isInput && !isDropEvent && !isDragEvent) {
121
122
  return true
122
123
  }
123
124
 
@@ -129,7 +130,6 @@ export class NodeView<
129
130
  const isPasteEvent = event.type === 'paste'
130
131
  const isCutEvent = event.type === 'cut'
131
132
  const isClickEvent = event.type === 'mousedown'
132
- const isDragEvent = event.type.startsWith('drag')
133
133
 
134
134
  // ProseMirror tries to drag selectable nodes
135
135
  // even if `draggable` is set to `false`
@@ -159,6 +159,14 @@ export class NodeView<
159
159
  { once: true },
160
160
  )
161
161
 
162
+ document.addEventListener(
163
+ 'drop',
164
+ () => {
165
+ this.isDragging = false
166
+ },
167
+ { once: true },
168
+ )
169
+
162
170
  document.addEventListener(
163
171
  'mouseup',
164
172
  () => {
@@ -79,7 +79,15 @@ export const insertContentAt: RawCommands['insertContentAt'] = (position, value,
79
79
  // if there is only plain text we have to use `insertText`
80
80
  // because this will keep the current marks
81
81
  if (isOnlyTextContent) {
82
- tr.insertText(value as string, from, to)
82
+ // if value is string, we can use it directly
83
+ // otherwise if it is an array, we have to join it
84
+ if (Array.isArray(value)) {
85
+ tr.insertText(value.map(v => v.text || '').join(''), from, to)
86
+ } else if (typeof value === 'object' && !!value && !!value.text) {
87
+ tr.insertText(value.text, from, to)
88
+ } else {
89
+ tr.insertText(value as string, from, to)
90
+ }
83
91
  } else {
84
92
  tr.replaceWith(from, to, content)
85
93
  }
@@ -130,7 +130,19 @@ export const splitListItem: RawCommands['splitListItem'] = typeOrName => ({
130
130
  }
131
131
 
132
132
  if (dispatch) {
133
+ const { selection, storedMarks } = state
134
+ const { splittableMarks } = editor.extensionManager
135
+ const marks = storedMarks || (selection.$to.parentOffset && selection.$from.marks())
136
+
133
137
  tr.split($from.pos, 2, types).scrollIntoView()
138
+
139
+ if (!marks || !dispatch) {
140
+ return true
141
+ }
142
+
143
+ const filteredMarks = marks.filter(mark => splittableMarks.includes(mark.type.name))
144
+
145
+ tr.ensureMarks(filteredMarks)
134
146
  }
135
147
 
136
148
  return true
@@ -63,24 +63,23 @@ declare module '@tiptap/core' {
63
63
  /**
64
64
  * Toggle between different list types.
65
65
  */
66
- toggleList: (
67
- listTypeOrName: string | NodeType,
68
- itemTypeOrName: string | NodeType,
69
- ) => ReturnType
66
+ toggleList: (listTypeOrName: string | NodeType, itemTypeOrName: string | NodeType, keepMarks?: boolean) => ReturnType;
70
67
  }
71
68
  }
72
69
  }
73
70
 
74
- export const toggleList: RawCommands['toggleList'] = (listTypeOrName, itemTypeOrName) => ({
71
+ export const toggleList: RawCommands['toggleList'] = (listTypeOrName, itemTypeOrName, keepMarks) => ({
75
72
  editor, tr, state, dispatch, chain, commands, can,
76
73
  }) => {
77
- const { extensions } = editor.extensionManager
74
+ const { extensions, splittableMarks } = editor.extensionManager
78
75
  const listType = getNodeType(listTypeOrName, state.schema)
79
76
  const itemType = getNodeType(itemTypeOrName, state.schema)
80
- const { selection } = state
77
+ const { selection, storedMarks } = state
81
78
  const { $from, $to } = selection
82
79
  const range = $from.blockRange($to)
83
80
 
81
+ const marks = storedMarks || (selection.$to.parentOffset && selection.$from.marks())
82
+
84
83
  if (!range) {
85
84
  return false
86
85
  }
@@ -110,6 +109,24 @@ export const toggleList: RawCommands['toggleList'] = (listTypeOrName, itemTypeOr
110
109
  .run()
111
110
  }
112
111
  }
112
+ if (!keepMarks || !marks || !dispatch) {
113
+
114
+ return chain()
115
+ // try to convert node to default node if needed
116
+ .command(() => {
117
+ const canWrapInList = can().wrapInList(listType)
118
+
119
+ if (canWrapInList) {
120
+ return true
121
+ }
122
+
123
+ return commands.clearNodes()
124
+ })
125
+ .wrapInList(listType)
126
+ .command(() => joinListBackwards(tr, listType))
127
+ .command(() => joinListForwards(tr, listType))
128
+ .run()
129
+ }
113
130
 
114
131
  return (
115
132
  chain()
@@ -117,6 +134,10 @@ export const toggleList: RawCommands['toggleList'] = (listTypeOrName, itemTypeOr
117
134
  .command(() => {
118
135
  const canWrapInList = can().wrapInList(listType)
119
136
 
137
+ const filteredMarks = marks.filter(mark => splittableMarks.includes(mark.type.name))
138
+
139
+ tr.ensureMarks(filteredMarks)
140
+
120
141
  if (canWrapInList) {
121
142
  return true
122
143
  }
@@ -27,7 +27,7 @@ export function createNodeFromContent(
27
27
 
28
28
  if (typeof content === 'object' && content !== null) {
29
29
  try {
30
- if (Array.isArray(content)) {
30
+ if (Array.isArray(content) && content.length > 0) {
31
31
  return Fragment.fromArray(content.map(item => schema.nodeFromJSON(item)))
32
32
  }
33
33
 
@@ -1,4 +1,7 @@
1
1
  export * from './combineTransactionSteps'
2
+ export * from './createChainableState'
3
+ export * from './createDocument'
4
+ export * from './createNodeFromContent'
2
5
  export * from './defaultBlockAt'
3
6
  export * from './findChildren'
4
7
  export * from './findChildrenInRange'
@@ -8,6 +11,7 @@ export * from './generateHTML'
8
11
  export * from './generateJSON'
9
12
  export * from './generateText'
10
13
  export * from './getAttributes'
14
+ export * from './getAttributesFromExtensions'
11
15
  export * from './getChangedRanges'
12
16
  export * from './getDebugJSON'
13
17
  export * from './getExtensionField'
@@ -18,12 +22,19 @@ export * from './getMarksBetween'
18
22
  export * from './getMarkType'
19
23
  export * from './getNodeAttributes'
20
24
  export * from './getNodeType'
25
+ export * from './getRenderedAttributes'
21
26
  export * from './getSchema'
27
+ export * from './getSchemaByResolvedExtensions'
28
+ export * from './getSchemaTypeByName'
29
+ export * from './getSchemaTypeNameByName'
30
+ export * from './getSplittedAttributes'
22
31
  export * from './getText'
23
32
  export * from './getTextBetween'
24
33
  export * from './getTextContentFromNodes'
25
34
  export * from './getTextSerializersFromSchema'
35
+ export * from './injectExtensionAttributesToParseRule'
26
36
  export * from './isActive'
37
+ export * from './isExtensionRulesEnabled'
27
38
  export * from './isList'
28
39
  export * from './isMarkActive'
29
40
  export * from './isNodeActive'
@@ -31,3 +42,6 @@ export * from './isNodeEmpty'
31
42
  export * from './isNodeSelection'
32
43
  export * from './isTextSelection'
33
44
  export * from './posToDOMRect'
45
+ export * from './resolveFocusPosition'
46
+ export * from './selectionToInsertionEnd'
47
+ export * from './splitExtensions'
@@ -1,6 +1,7 @@
1
1
  import { Node as ProseMirrorNode, NodeType } from '@tiptap/pm/model'
2
2
  import { canJoin, findWrapping } from '@tiptap/pm/transform'
3
3
 
4
+ import { Editor } from '../Editor'
4
5
  import { InputRule, InputRuleFinder } from '../InputRule'
5
6
  import { ExtendedRegExpMatchArray } from '../types'
6
7
  import { callOrReturn } from '../utilities/callOrReturn'
@@ -20,18 +21,24 @@ import { callOrReturn } from '../utilities/callOrReturn'
20
21
  * return a boolean to indicate whether a join should happen.
21
22
  */
22
23
  export function wrappingInputRule(config: {
23
- find: InputRuleFinder
24
- type: NodeType
24
+ find: InputRuleFinder,
25
+ type: NodeType,
26
+ keepMarks?: boolean,
27
+ keepAttributes?: boolean,
28
+ editor?: Editor
25
29
  getAttributes?:
26
- | Record<string, any>
27
- | ((match: ExtendedRegExpMatchArray) => Record<string, any>)
28
- | false
29
- | null
30
- joinPredicate?: (match: ExtendedRegExpMatchArray, node: ProseMirrorNode) => boolean
30
+ | Record<string, any>
31
+ | ((match: ExtendedRegExpMatchArray) => Record<string, any>)
32
+ | false
33
+ | null
34
+ ,
35
+ joinPredicate?: (match: ExtendedRegExpMatchArray, node: ProseMirrorNode) => boolean,
31
36
  }) {
32
37
  return new InputRule({
33
38
  find: config.find,
34
- handler: ({ state, range, match }) => {
39
+ handler: ({
40
+ state, range, match, chain,
41
+ }) => {
35
42
  const attributes = callOrReturn(config.getAttributes, undefined, match) || {}
36
43
  const tr = state.tr.delete(range.from, range.to)
37
44
  const $start = tr.doc.resolve(range.from)
@@ -44,6 +51,24 @@ export function wrappingInputRule(config: {
44
51
 
45
52
  tr.wrap(blockRange, wrapping)
46
53
 
54
+ if (config.keepMarks && config.editor) {
55
+ const { selection, storedMarks } = state
56
+ const { splittableMarks } = config.editor.extensionManager
57
+ const marks = storedMarks || (selection.$to.parentOffset && selection.$from.marks())
58
+
59
+ if (marks) {
60
+ const filteredMarks = marks.filter(mark => splittableMarks.includes(mark.type.name))
61
+
62
+ tr.ensureMarks(filteredMarks)
63
+ }
64
+ }
65
+ if (config.keepAttributes) {
66
+ /** If the nodeType is `bulletList` or `orderedList` set the `nodeType` as `listItem` */
67
+ const nodeType = config.type.name === 'bulletList' || config.type.name === 'orderedList' ? 'listItem' : 'taskList'
68
+
69
+ chain().updateAttributes(nodeType, attributes).run()
70
+ }
71
+
47
72
  const before = tr.doc.resolve(range.from - 1).nodeBefore
48
73
 
49
74
  if (
@@ -1,6 +1,6 @@
1
1
  import { NodeType } from '@tiptap/pm/model'
2
2
 
3
- import { PasteRule } from '../PasteRule'
3
+ import { PasteRule, PasteRuleFinder } from '../PasteRule'
4
4
  import { ExtendedRegExpMatchArray } from '../types'
5
5
  import { callOrReturn } from '../utilities'
6
6
 
@@ -9,7 +9,7 @@ import { callOrReturn } from '../utilities'
9
9
  * matched text is pasted into it.
10
10
  */
11
11
  export function nodePasteRule(config: {
12
- find: RegExp
12
+ find: PasteRuleFinder
13
13
  type: NodeType
14
14
  getAttributes?:
15
15
  | Record<string, any>
package/src/types.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { Mark as ProseMirrorMark, Node as ProseMirrorNode, ParseOptions } from '@tiptap/pm/model'
1
+ import {
2
+ Mark as ProseMirrorMark, Node as ProseMirrorNode, NodeType, ParseOptions,
3
+ } from '@tiptap/pm/model'
2
4
  import { EditorState, Transaction } from '@tiptap/pm/state'
3
5
  import {
4
6
  Decoration, EditorProps, EditorView, NodeView,
@@ -148,10 +150,14 @@ export type ValuesOf<T> = T[keyof T]
148
150
 
149
151
  export type KeysWithTypeOf<T, Type> = { [P in keyof T]: T[P] extends Type ? P : never }[keyof T]
150
152
 
153
+ export type DecorationWithType = Decoration & {
154
+ type: NodeType
155
+ }
156
+
151
157
  export type NodeViewProps = {
152
158
  editor: Editor
153
159
  node: ProseMirrorNode
154
- decorations: Decoration[]
160
+ decorations: DecorationWithType[]
155
161
  selected: boolean
156
162
  extension: Node
157
163
  getPos: () => number