@portabletext/editor 1.5.4 → 1.5.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -48,7 +48,7 @@
48
48
  "is-hotkey-esm": "^1.0.0",
49
49
  "lodash": "^4.17.21",
50
50
  "lodash.startcase": "^4.4.0",
51
- "react-compiler-runtime": "19.0.0-beta-9ee70a1-20241017",
51
+ "react-compiler-runtime": "19.0.0-beta-63b359f-20241101",
52
52
  "slate": "0.110.2",
53
53
  "slate-dom": "^0.111.0",
54
54
  "slate-react": "0.111.0",
@@ -56,55 +56,33 @@
56
56
  "xstate": "^5.18.2"
57
57
  },
58
58
  "devDependencies": {
59
- "@babel/preset-env": "^7.26.0",
60
- "@babel/preset-react": "^7.25.9",
61
- "@jest/globals": "^29.7.0",
62
- "@jest/types": "^29.6.3",
63
- "@playwright/test": "1.48.2",
64
59
  "@portabletext/toolkit": "^2.0.16",
65
- "@sanity/block-tools": "^3.62.3",
60
+ "@sanity/block-tools": "^3.63.0",
66
61
  "@sanity/diff-match-patch": "^3.1.1",
67
- "@sanity/pkg-utils": "^6.11.8",
68
- "@sanity/schema": "^3.62.3",
69
- "@sanity/types": "^3.62.3",
70
- "@sanity/ui": "^2.8.18",
71
- "@sanity/util": "^3.62.3",
72
- "@testing-library/dom": "^10.4.0",
62
+ "@sanity/pkg-utils": "^6.11.9",
63
+ "@sanity/schema": "^3.63.0",
64
+ "@sanity/types": "^3.63.0",
65
+ "@sanity/util": "^3.63.0",
73
66
  "@testing-library/jest-dom": "^6.6.3",
74
67
  "@testing-library/react": "^16.0.1",
75
- "@testing-library/user-event": "^14.5.2",
76
68
  "@types/debug": "^4.1.5",
77
- "@types/express": "^4.17.21",
78
- "@types/express-ws": "^3.0.5",
79
69
  "@types/lodash": "^4.17.13",
80
70
  "@types/lodash.startcase": "^4.4.9",
81
- "@types/node": "^18.19.8",
82
- "@types/node-ipc": "^9.2.3",
83
71
  "@types/react": "^18.3.12",
84
72
  "@types/react-dom": "^18.3.1",
85
- "@types/ws": "~8.5.12",
86
- "@typescript-eslint/eslint-plugin": "^8.12.2",
87
- "@typescript-eslint/parser": "^8.12.2",
73
+ "@typescript-eslint/eslint-plugin": "^8.13.0",
74
+ "@typescript-eslint/parser": "^8.13.0",
88
75
  "@vitejs/plugin-react": "^4.3.3",
89
76
  "@vitest/browser": "^2.1.4",
90
- "babel-plugin-react-compiler": "beta",
91
- "dotenv": "^16.4.5",
77
+ "babel-plugin-react-compiler": "19.0.0-beta-63b359f-20241101",
92
78
  "eslint": "8.57.1",
93
- "eslint-plugin-react-compiler": "beta",
79
+ "eslint-plugin-react-compiler": "19.0.0-beta-63b359f-20241101",
94
80
  "eslint-plugin-react-hooks": "^5.0.0",
95
- "express": "^4.21.1",
96
- "express-ws": "^5.0.2",
97
- "jest": "^29.7.0",
98
- "jest-dev-server": "^10.1.4",
99
- "jest-environment-node": "^29.7.0",
100
81
  "jsdom": "^25.0.1",
101
- "node-ipc": "npm:@node-ipc/compat@9.2.5",
102
- "playwright": "^1.48.2",
103
82
  "react": "^18.3.1",
104
83
  "react-dom": "^18.3.1",
105
84
  "rxjs": "^7.8.1",
106
85
  "styled-components": "^6.1.13",
107
- "ts-node": "^10.9.2",
108
86
  "typescript": "5.6.3",
109
87
  "vite": "^5.4.10",
110
88
  "vitest": "^2.1.4",
@@ -112,10 +90,10 @@
112
90
  "@sanity/gherkin-driver": "^0.0.1"
113
91
  },
114
92
  "peerDependencies": {
115
- "@sanity/block-tools": "^3.62.3",
116
- "@sanity/schema": "^3.62.3",
117
- "@sanity/types": "^3.62.3",
118
- "@sanity/util": "^3.62.3",
93
+ "@sanity/block-tools": "^3.63.0",
94
+ "@sanity/schema": "^3.63.0",
95
+ "@sanity/types": "^3.63.0",
96
+ "@sanity/util": "^3.63.0",
119
97
  "react": "^16.9 || ^17 || ^18",
120
98
  "rxjs": "^7.8.1",
121
99
  "styled-components": "^6.1.13"
@@ -135,8 +113,6 @@
135
113
  "dev": "pkg-utils watch",
136
114
  "lint:fix": "biome lint --write .",
137
115
  "test": "vitest --run",
138
- "test:watch": "vitest",
139
- "test:e2e-legacy": "jest --config=e2e-tests/e2e.config.ts",
140
- "test:e2e-legacy:watch": "jest --config=e2e-tests/e2e.config.ts --watch"
116
+ "test:watch": "vitest"
141
117
  }
142
118
  }
@@ -30,7 +30,7 @@ export type MarkdownBehaviorsConfig = {
30
30
  * @alpha
31
31
  */
32
32
  export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
33
- const automaticStyleOnSpace = defineBehavior({
33
+ const automaticBlockquoteOnSpace = defineBehavior({
34
34
  on: 'insert text',
35
35
  guard: ({context, event}) => {
36
36
  const isSpace = event.text === ' '
@@ -47,20 +47,15 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
47
47
  return false
48
48
  }
49
49
 
50
- const looksLikeMarkdownHeading = /^#+/.test(focusSpan.node.text)
51
- const headingStyle = config.mapHeadingStyle(
52
- context.schema,
53
- focusSpan.node.text.length,
54
- )
55
-
50
+ const caretAtTheEndOfQuote = context.selection.focus.offset === 1
56
51
  const looksLikeMarkdownQuote = /^>/.test(focusSpan.node.text)
57
52
  const blockquoteStyle = config.mapBlockquoteStyle(context.schema)
58
53
 
59
- if (looksLikeMarkdownHeading && headingStyle !== undefined) {
60
- return {focusTextBlock, focusSpan, style: headingStyle}
61
- }
62
-
63
- if (looksLikeMarkdownQuote && blockquoteStyle !== undefined) {
54
+ if (
55
+ caretAtTheEndOfQuote &&
56
+ looksLikeMarkdownQuote &&
57
+ blockquoteStyle !== undefined
58
+ ) {
64
59
  return {focusTextBlock, focusSpan, style: blockquoteStyle}
65
60
  }
66
61
 
@@ -87,10 +82,91 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
87
82
  {
88
83
  type: 'delete',
89
84
  selection: {
90
- anchor: {path: focusSpan.path, offset: 0},
85
+ anchor: {
86
+ path: focusSpan.path,
87
+ offset: 0,
88
+ },
89
+ focus: {
90
+ path: focusSpan.path,
91
+ offset: 2,
92
+ },
93
+ },
94
+ },
95
+ ],
96
+ ],
97
+ })
98
+ const automaticHeadingOnSpace = defineBehavior({
99
+ on: 'insert text',
100
+ guard: ({context, event}) => {
101
+ const isSpace = event.text === ' '
102
+
103
+ if (!isSpace) {
104
+ return false
105
+ }
106
+
107
+ const selectionCollapsed = selectionIsCollapsed(context)
108
+ const focusTextBlock = getFocusTextBlock(context)
109
+ const focusSpan = getFocusSpan(context)
110
+
111
+ if (!selectionCollapsed || !focusTextBlock || !focusSpan) {
112
+ return false
113
+ }
114
+
115
+ const markdownHeadingSearch = /^#+/.exec(focusSpan.node.text)
116
+ const headingLevel = markdownHeadingSearch
117
+ ? markdownHeadingSearch[0].length
118
+ : undefined
119
+ const caretAtTheEndOfHeading =
120
+ context.selection.focus.offset === headingLevel
121
+
122
+ if (!caretAtTheEndOfHeading) {
123
+ return false
124
+ }
125
+
126
+ const headingStyle =
127
+ headingLevel !== undefined
128
+ ? config.mapHeadingStyle(context.schema, headingLevel)
129
+ : undefined
130
+
131
+ if (headingLevel !== undefined && headingStyle !== undefined) {
132
+ return {
133
+ focusTextBlock,
134
+ focusSpan,
135
+ style: headingStyle,
136
+ level: headingLevel,
137
+ }
138
+ }
139
+
140
+ return false
141
+ },
142
+ actions: [
143
+ () => [
144
+ {
145
+ type: 'insert text',
146
+ text: ' ',
147
+ },
148
+ ],
149
+ (_, {focusTextBlock, focusSpan, style, level}) => [
150
+ {
151
+ type: 'unset block',
152
+ props: ['listItem', 'level'],
153
+ paths: [focusTextBlock.path],
154
+ },
155
+ {
156
+ type: 'set block',
157
+ style,
158
+ paths: [focusTextBlock.path],
159
+ },
160
+ {
161
+ type: 'delete',
162
+ selection: {
163
+ anchor: {
164
+ path: focusSpan.path,
165
+ offset: 0,
166
+ },
91
167
  focus: {
92
168
  path: focusSpan.path,
93
- offset: focusSpan.node.text.length + 1,
169
+ offset: level + 1,
94
170
  },
95
171
  },
96
172
  },
@@ -108,13 +184,16 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
108
184
  return false
109
185
  }
110
186
 
187
+ const atTheBeginningOfBLock =
188
+ focusTextBlock.node.children[0]._key === focusSpan.node._key &&
189
+ context.selection.focus.offset === 0
190
+
111
191
  const defaultStyle = config.mapDefaultStyle(context.schema)
112
192
 
113
193
  if (
194
+ atTheBeginningOfBLock &&
114
195
  defaultStyle &&
115
- focusTextBlock.node.children.length === 1 &&
116
- focusTextBlock.node.style !== config.mapDefaultStyle(context.schema) &&
117
- focusSpan.node.text === ''
196
+ focusTextBlock.node.style !== defaultStyle
118
197
  ) {
119
198
  return {defaultStyle, focusTextBlock}
120
199
  }
@@ -131,7 +210,6 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
131
210
  ],
132
211
  ],
133
212
  })
134
-
135
213
  const automaticListOnSpace = defineBehavior({
136
214
  on: 'insert text',
137
215
  guard: ({context, event}) => {
@@ -149,18 +227,38 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
149
227
  return false
150
228
  }
151
229
 
152
- const looksLikeUnorderedList = /^-/.test(focusSpan.node.text)
230
+ const looksLikeUnorderedList = /^(-|\*)/.test(focusSpan.node.text)
153
231
  const unorderedListStyle = config.mapUnorderedListStyle(context.schema)
232
+ const caretAtTheEndOfUnorderedList = context.selection.focus.offset === 1
154
233
 
155
- if (looksLikeUnorderedList && unorderedListStyle !== undefined) {
156
- return {focusTextBlock, focusSpan, listItem: unorderedListStyle}
234
+ if (
235
+ caretAtTheEndOfUnorderedList &&
236
+ looksLikeUnorderedList &&
237
+ unorderedListStyle !== undefined
238
+ ) {
239
+ return {
240
+ focusTextBlock,
241
+ focusSpan,
242
+ listItem: unorderedListStyle,
243
+ listItemLength: 1,
244
+ }
157
245
  }
158
246
 
159
247
  const looksLikeOrderedList = /^1./.test(focusSpan.node.text)
160
248
  const orderedListStyle = config.mapOrderedListStyle(context.schema)
249
+ const caretAtTheEndOfOrderedList = context.selection.focus.offset === 2
161
250
 
162
- if (looksLikeOrderedList && orderedListStyle !== undefined) {
163
- return {focusTextBlock, focusSpan, listItem: orderedListStyle}
251
+ if (
252
+ caretAtTheEndOfOrderedList &&
253
+ looksLikeOrderedList &&
254
+ orderedListStyle !== undefined
255
+ ) {
256
+ return {
257
+ focusTextBlock,
258
+ focusSpan,
259
+ listItem: orderedListStyle,
260
+ listItemLength: 2,
261
+ }
164
262
  }
165
263
 
166
264
  return false
@@ -172,7 +270,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
172
270
  text: ' ',
173
271
  },
174
272
  ],
175
- (_, {focusTextBlock, focusSpan, listItem}) => [
273
+ (_, {focusTextBlock, focusSpan, listItem, listItemLength}) => [
176
274
  {
177
275
  type: 'unset block',
178
276
  props: ['style'],
@@ -187,10 +285,13 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
187
285
  {
188
286
  type: 'delete',
189
287
  selection: {
190
- anchor: {path: focusSpan.path, offset: 0},
288
+ anchor: {
289
+ path: focusSpan.path,
290
+ offset: 0,
291
+ },
191
292
  focus: {
192
293
  path: focusSpan.path,
193
- offset: focusSpan.node.text.length + 1,
294
+ offset: listItemLength + 1,
194
295
  },
195
296
  },
196
297
  },
@@ -199,7 +300,8 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
199
300
  })
200
301
 
201
302
  const markdownBehaviors = [
202
- automaticStyleOnSpace,
303
+ automaticBlockquoteOnSpace,
304
+ automaticHeadingOnSpace,
203
305
  clearStyleOnBackspace,
204
306
  automaticListOnSpace,
205
307
  ]
@@ -23,7 +23,10 @@ import {
23
23
  import type {PortableTextSlateEditor} from '../../types/editor'
24
24
  import {debugWithName} from '../../utils/debug'
25
25
  import {fromSlateValue} from '../../utils/values'
26
+ import {isChangingRemotely} from '../../utils/withChanges'
26
27
  import {
28
+ isRedoing,
29
+ isUndoing,
27
30
  setIsRedoing,
28
31
  setIsUndoing,
29
32
  withRedoing,
@@ -115,6 +118,25 @@ export function createWithUndoRedo(
115
118
  apply(op)
116
119
  return
117
120
  }
121
+
122
+ /**
123
+ * We don't want to run any side effects when the editor is processing
124
+ * remote changes.
125
+ */
126
+ if (isChangingRemotely(editor)) {
127
+ apply(op)
128
+ return
129
+ }
130
+
131
+ /**
132
+ * We don't want to run any side effects when the editor is undoing or
133
+ * redoing operations.
134
+ */
135
+ if (isUndoing(editor) || isRedoing(editor)) {
136
+ apply(op)
137
+ return
138
+ }
139
+
118
140
  const {operations, history} = editor
119
141
  const {undos} = history
120
142
  const step = undos[undos.length - 1]
@@ -1,67 +0,0 @@
1
- import type {Patch} from '@sanity/diff-match-patch'
2
-
3
- /**
4
- * Takes a `patches` array as produced by diff-match-patch and adjusts the
5
- * `start1` and `start2` properties so that they refer to UCS-2 index instead
6
- * of a UTF-8 index.
7
- *
8
- * @param patches - The patches to adjust
9
- * @param base - The base string to use for counting bytes
10
- * @returns A new array of patches with adjusted indicies
11
- * @beta
12
- */
13
- export function adjustIndiciesToUcs2(patches: Patch[], base: string): Patch[] {
14
- let byteOffset = 0
15
- let idx = 0 // index into the input.
16
-
17
- function advanceTo(target: number) {
18
- while (byteOffset < target) {
19
- const codePoint = base.codePointAt(idx)
20
- if (typeof codePoint === 'undefined') {
21
- // Reached the end of the base string - the indicies won't be correct,
22
- // but we also cannot advance any further to find a closer index.
23
- return idx
24
- }
25
-
26
- byteOffset += utf8len(codePoint)
27
-
28
- // This is encoded as a surrogate pair.
29
- if (codePoint > 0xffff) {
30
- idx += 2
31
- } else {
32
- idx += 1
33
- }
34
- }
35
-
36
- // Theoretically, we should have reached target - however, due to differences in
37
- // `base` from the string that the patch was originally based upon, occurences
38
- // _can_ happen where we go beyond the target due to surrogate pairs or similar.
39
- // In the PTE, this is okayish - best effort matching is good enough.
40
- return idx
41
- }
42
-
43
- const adjusted: Patch[] = []
44
- for (const patch of patches) {
45
- adjusted.push({
46
- diffs: patch.diffs.map((diff) => [...diff]),
47
- start1: advanceTo(patch.start1),
48
- start2: advanceTo(patch.start2),
49
- utf8Start1: patch.utf8Start1,
50
- utf8Start2: patch.utf8Start2,
51
- length1: patch.length1,
52
- length2: patch.length2,
53
- utf8Length1: patch.utf8Length1,
54
- utf8Length2: patch.utf8Length2,
55
- })
56
- }
57
-
58
- return adjusted
59
- }
60
-
61
- function utf8len(codePoint: number): 1 | 2 | 3 | 4 {
62
- // See table at https://en.wikipedia.org/wiki/UTF-8
63
- if (codePoint <= 0x007f) return 1
64
- if (codePoint <= 0x07ff) return 2
65
- if (codePoint <= 0xffff) return 3
66
- return 4
67
- }