@portabletext/plugin-sdk-value 8.0.6 → 8.1.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/README.md CHANGED
@@ -3,8 +3,9 @@
3
3
  > Connect a Portable Text Editor with a Sanity document using the SDK
4
4
 
5
5
  Two-way synchronization between a Portable Text Editor and a field in a Sanity
6
- document, plus presence: other people's carets show up in the field, and the
7
- local user's caret shows up for them, including in the Studio.
6
+ document, plus presence and inline comments: other people's carets show up in
7
+ the field and the local user's caret shows up for them, and comment threads
8
+ anchored to text draw as highlights, all interoperating with the Studio.
8
9
 
9
10
  ## Installation
10
11
 
@@ -18,6 +19,7 @@ npm install @portabletext/plugin-sdk-value
18
19
  - **Real-time updates**: Automatically handles patches from external sources (other users, mutations, etc.)
19
20
  - **Optimistic updates**: Provides smooth user experience with immediate local updates
20
21
  - **Presence**: Reports where the local user is editing, and draws other people's carets
22
+ - **Inline comments**: Draws highlights for comment threads anchored to text, and starts new threads on the selection
21
23
 
22
24
  ## Usage
23
25
 
@@ -72,6 +74,55 @@ Pass `renderCursor={null}` to draw no carets at all while still reporting the
72
74
  local user's presence, which is what you want if you only care about keeping the
73
75
  document in sync.
74
76
 
77
+ ### Inline comments
78
+
79
+ Comment threads anchored to text in the field draw as highlights, and new
80
+ threads can be started on the current selection. The composer UI stays with the
81
+ app, the same split presence uses for carets. Comments are written in the shape
82
+ Sanity Studio stores, so threads round-trip between an SDK app and the Studio.
83
+
84
+ `useSDKCommentDecorations` returns `RangeDecoration[]` for the open threads on
85
+ the field. Pass it through `rangeDecorations`, which `SDKPortableTextEditable`
86
+ merges with the presence carets:
87
+
88
+ ```tsx
89
+ const decorations = useSDKCommentDecorations({
90
+ ...documentHandle,
91
+ path: 'content',
92
+ renderDecoration: (comment) => (props) => (
93
+ <span data-comment-id={comment.id} style={{background: '#fef3c7'}}>
94
+ {props.children}
95
+ </span>
96
+ ),
97
+ })
98
+
99
+ return (
100
+ <SDKPortableTextEditable
101
+ {...documentHandle}
102
+ path="content"
103
+ rangeDecorations={decorations}
104
+ />
105
+ )
106
+ ```
107
+
108
+ `useSDKCommentAuthoring` is the write side: `commentableSelection` is set when
109
+ the current selection can take a comment, and `createInlineComment` starts a
110
+ thread anchored to it:
111
+
112
+ ```tsx
113
+ const {commentableSelection, createInlineComment} = useSDKCommentAuthoring({
114
+ ...documentHandle,
115
+ path: 'content',
116
+ })
117
+
118
+ // Show the composer while `commentableSelection` is set, then:
119
+ await createInlineComment({message})
120
+ ```
121
+
122
+ Highlights follow the text while the user types, and a highlight whose text is
123
+ deleted or rewritten beyond recognition is dropped rather than drawn on the
124
+ wrong words. Resolved threads draw nothing, matching the Studio.
125
+
75
126
  ### Rendering the editable yourself
76
127
 
77
128
  `SDKValuePlugin` still works as a sibling component if you would rather keep
@@ -138,5 +189,5 @@ the Studio, whose field indicators compare the exact document id its form is on.
138
189
 
139
190
  This plugin requires:
140
191
 
141
- - `@sanity/sdk-react` 2.19 or newer, where the presence hooks were added
192
+ - `@sanity/sdk-react` 2.20.1 or newer, where the comment hooks were added
142
193
  - The document must exist in the Sanity dataset
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { DocumentHandle, DocumentResource, usePresenceForDocument } from "@sanity/sdk-react";
1
+ import { Comment, CommentMessage, DocumentHandle, DocumentResource, usePresenceForDocument } from "@sanity/sdk-react";
2
2
  import { EditorSelection, Patch, PortableTextBlock, PortableTextEditableProps, RangeDecoration } from "@portabletext/editor";
3
3
  import { PropsWithChildren, ReactElement } from "react";
4
4
  import "@portabletext/patches";
@@ -94,6 +94,91 @@ declare function SDKPresencePlugin(props: SDKPresencePluginProps): null;
94
94
  * @public
95
95
  */
96
96
  declare function useSDKPresenceCursors(options: UseSDKPresenceCursorsOptions): RangeDecoration[];
97
+ /**
98
+ * Draws one comment's highlight. The plugin has no opinion about how a
99
+ * highlight looks, so it is the caller's to provide, the same way presence
100
+ * takes `renderCursor`.
101
+ *
102
+ * @public
103
+ */
104
+ type RenderCommentDecorationFunction = (comment: Comment) => (props: PropsWithChildren) => ReactElement;
105
+ /**
106
+ * Options for {@link useSDKCommentDecorations}.
107
+ *
108
+ * @public
109
+ */
110
+ interface UseSDKCommentDecorationsOptions extends DocumentHandle {
111
+ /**
112
+ * The document path of the Portable Text field, for example `content`. The
113
+ * same form `SDKValuePlugin` takes.
114
+ */
115
+ path: string;
116
+ renderDecoration: RenderCommentDecorationFunction;
117
+ }
118
+ /**
119
+ * Inline comment highlights for a Portable Text field, as range decorations.
120
+ *
121
+ * Pass the result to `<PortableTextEditable rangeDecorations={...} />`. Each
122
+ * thread's first comment that carries a text anchor on this field gets one
123
+ * decoration. Highlights stay put while the local user types, and a highlight
124
+ * whose text has been deleted or rewritten beyond recognition is dropped
125
+ * rather than drawn on the wrong words.
126
+ *
127
+ * Suspends while the document's comments load, like every SDK read hook.
128
+ *
129
+ * Resolved threads draw nothing: this mirrors the Studio, where resolving a
130
+ * thread removes its highlight from the text.
131
+ *
132
+ * @public
133
+ */
134
+ declare function useSDKCommentDecorations(options: UseSDKCommentDecorationsOptions): RangeDecoration[];
135
+ /**
136
+ * Options for {@link useSDKCommentAuthoring}.
137
+ *
138
+ * @public
139
+ */
140
+ interface UseSDKCommentAuthoringOptions extends DocumentHandle {
141
+ /**
142
+ * The document path of the Portable Text field, for example `content`.
143
+ */
144
+ path: string;
145
+ }
146
+ /**
147
+ * What {@link useSDKCommentAuthoring} returns.
148
+ *
149
+ * @public
150
+ */
151
+ interface SDKCommentAuthoring {
152
+ /**
153
+ * The current selection when it can take a comment, `null` otherwise. Show
154
+ * the comment affordance when this is set, and position it off the
155
+ * selection. A selection can take a comment when it is expanded, contains
156
+ * text, and stays within one array of blocks.
157
+ */
158
+ commentableSelection: EditorSelection;
159
+ /**
160
+ * Starts a comment thread anchored to the text selected right now.
161
+ *
162
+ * The anchor is captured from the live selection at call time, so call this
163
+ * from the affordance while the selection still stands. Rejects when nothing
164
+ * commentable is selected.
165
+ */
166
+ createInlineComment: (options: {
167
+ message: CommentMessage;
168
+ /** Reuse the id of a failed comment to retry it. */
169
+ commentId?: string;
170
+ }) => Promise<Comment>;
171
+ }
172
+ /**
173
+ * Lets the app author inline comments on a Portable Text field.
174
+ *
175
+ * The plugin captures the selection and writes the comment; the composer UI is
176
+ * the app's, the same split the Studio and Canvas use. Comments are written in
177
+ * the shape the Studio stores, so a thread started here shows up there.
178
+ *
179
+ * @public
180
+ */
181
+ declare function useSDKCommentAuthoring(options: UseSDKCommentAuthoringOptions): SDKCommentAuthoring;
97
182
  interface SDKValuePluginProps extends DocumentHandle {
98
183
  source?: DocumentResource;
99
184
  path: string;
@@ -183,5 +268,5 @@ interface SDKPortableTextEditableProps extends DocumentHandle, Omit<PortableText
183
268
  * @public
184
269
  */
185
270
  declare function SDKPortableTextEditable(props: SDKPortableTextEditableProps): import("react").JSX.Element;
186
- export { type RenderCursorFunction, SDKPortableTextEditable, type SDKPortableTextEditableProps, SDKPresencePlugin, type SDKPresencePluginProps, type SDKRemoteCursor, SDKValuePlugin, type UseSDKPresenceCursorsOptions, ValueSyncPlugin, useSDKPresenceCursors };
271
+ export { type RenderCommentDecorationFunction, type RenderCursorFunction, type SDKCommentAuthoring, SDKPortableTextEditable, type SDKPortableTextEditableProps, SDKPresencePlugin, type SDKPresencePluginProps, type SDKRemoteCursor, SDKValuePlugin, type UseSDKCommentAuthoringOptions, type UseSDKCommentDecorationsOptions, type UseSDKPresenceCursorsOptions, ValueSyncPlugin, useSDKCommentAuthoring, useSDKCommentDecorations, useSDKPresenceCursors };
187
272
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/plugin.sdk-presence.tsx","../src/plugin.sdk-value.tsx","../src/sdk-editable.tsx"],"mappings":";;;;;;;;;KAiBK,mBAAmB,kBACf;;;;;;;KASG,wBACV,QAAQ,qBACJ,OAAO,sBAAsB;;;;;;;;;UAUlB;;;;;EAKf;;;;EAIA,WAAW;EACX,MAAM;;;;;;;UAQS,+BAA+B;;;;EAI9C,SAAS;;;;;EAKT;;;;;;;UAQe,qCAAqC;;;;EAIpD,SAAS;EACT;EACA,cAAc;;;;;;;;;;;;;;;;iBAiBA,kBAAkB,OAAO;;;;;;;;;;;;;;;iBAwBzB,sBACd,SAAS,+BACR;UCo8BO,4BAA4B;EACpC,SAAS;EACT;;;;;iBAqCc,eAAe,OAAO,sCAAmB,IAAA;;;;KAkFpD;EACH,sBAAsB;EACtB,YAAY,OAAO;EACnB,sBAAsB;;;;;;;;EAQtB,mBACE,WAAW,SAAS;;;;;;EAOtB,eAAe,SAAS;;;;;;;;;;;;iBAaV,gBAAgB,OAAO;;;;;;UCnsCtB,qCAEb,gBAGA,KAAK,iCAAiC;;;;EAIxC,SAAS;;;;EAIT;;;;;;EAMA,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6CD,wBAAwB,OAAO,+CAA4B,IAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/plugin.sdk-presence.tsx","../src/plugin.sdk-comments.tsx","../src/plugin.sdk-value.tsx","../src/sdk-editable.tsx"],"mappings":";;;;;;;;;KAiBK,mBAAmB,kBACf;;;;;;;KASG,wBACV,QAAQ,qBACJ,OAAO,sBAAsB;;;;;;;;;UAUlB;;;;;EAKf;;;;EAIA,WAAW;EACX,MAAM;;;;;;;UAQS,+BAA+B;;;;EAI9C,SAAS;;;;;EAKT;;;;;;;UAQe,qCAAqC;;;;EAIpD,SAAS;EACT;EACA,cAAc;;;;;;;;;;;;;;;;iBAiBA,kBAAkB,OAAO;;;;;;;;;;;;;;;iBAwBzB,sBACd,SAAS,+BACR;;;;;;;;KCnCS,mCACV,SAAS,aACL,OAAO,sBAAsB;;;;;;UAOlB,wCAAwC;;;;;EAKvD;EACA,kBAAkB;;;;;;;;;;;;;;;;;;iBAmBJ,yBACd,SAAS,kCACR;;;;;;UAgGc,sCAAsC;;;;EAIrD;;;;;;;UAQe;;;;;;;EAOf,sBAAsB;;;;;;;;EAQtB,sBAAsB;IACpB,SAAS;;IAET;QACI,QAAQ;;;;;;;;;;;iBAwBA,uBACd,SAAS,gCACR;UC0yBO,4BAA4B;EACpC,SAAS;EACT;;;;;iBAqCc,eAAe,OAAO,sCAAmB,IAAA;;;;KAkFpD;EACH,sBAAsB;EACtB,YAAY,OAAO;EACnB,sBAAsB;;;;;;;;EAQtB,mBACE,WAAW,SAAS;;;;;;EAOtB,eAAe,SAAS;;;;;;;;;;;;iBAaV,gBAAgB,OAAO;;;;;;UCnsCtB,qCAEb,gBAGA,KAAK,iCAAiC;;;;EAIxC,SAAS;;;;EAIT;;;;;;EAMA,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6CD,wBAAwB,OAAO,+CAA4B,IAAA"}
package/dist/index.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import { c } from "react/compiler-runtime";
2
- import { editDocument, getDocumentState, subscribeDocumentEvents, useApplyDocumentActions, useEditDocument, usePresenceForDocument, useReportPresence, useSanityInstance } from "@sanity/sdk-react";
2
+ import { editDocument, getDocumentState, subscribeDocumentEvents, useApplyDocumentActions, useCommentActions, useComments, useEditDocument, usePresenceForDocument, useReportPresence, useSanityInstance } from "@sanity/sdk-react";
3
3
  import { PortableTextEditable, useEditor, useEditorSelector } from "@portabletext/editor";
4
- import { getSelection } from "@portabletext/editor/selectors";
4
+ import { getSelectedTextBlocks, getSelection } from "@portabletext/editor/selectors";
5
5
  import { isEqualSelections } from "@portabletext/editor/utils";
6
6
  import { useState } from "react";
7
7
  import { applyAll } from "@portabletext/patches";
8
8
  import { diffValue } from "@sanity/diff-patch";
9
- import { parsePath } from "@sanity/json-match";
9
+ import { parsePath, stringifyPath } from "@sanity/json-match";
10
10
  import { useActorRef } from "@xstate/react";
11
11
  import { fromCallback, setup } from "xstate";
12
12
  import rawDebug from "debug";
13
13
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
+ import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, applyPatches, cleanupEfficiency, makeDiff, makePatches } from "@sanity/diff-match-patch";
14
15
  /**
15
16
  * Reduces a selection to a caret at its focus point.
16
17
  *
@@ -235,11 +236,11 @@ function convertPatchesToSanity(patches, options) {
235
236
  }
236
237
  });
237
238
  }
238
- function segmentsEqual(a, b) {
239
+ function segmentsEqual$1(a, b) {
239
240
  return typeof a == "string" || typeof a == "number" ? a === b : typeof b == "string" || typeof b == "number" || Array.isArray(a) || Array.isArray(b) ? !1 : a._key === b._key;
240
241
  }
241
- function pathsEqual(a, b) {
242
- return a.length === b.length && a.every((segment, index) => segmentsEqual(segment, b[index]));
242
+ function pathsEqual$1(a, b) {
243
+ return a.length === b.length && a.every((segment, index) => segmentsEqual$1(segment, b[index]));
243
244
  }
244
245
  /**
245
246
  * The editor engine can only resolve keyed/indexed segments against the
@@ -264,7 +265,7 @@ function findSidecarArrayPath(path) {
264
265
  }
265
266
  return null;
266
267
  }
267
- function getValueAtPath(value, path) {
268
+ function getValueAtPath$1(value, path) {
268
269
  let current = value;
269
270
  for (let segment of path) {
270
271
  if (current == null) return;
@@ -298,10 +299,10 @@ function toEngineSafePatches(patches, targetValue) {
298
299
  safe.push(patch);
299
300
  continue;
300
301
  }
301
- sidecarPaths.some((existing) => pathsEqual(existing, sidecarPath)) || sidecarPaths.push(sidecarPath);
302
+ sidecarPaths.some((existing) => pathsEqual$1(existing, sidecarPath)) || sidecarPaths.push(sidecarPath);
302
303
  }
303
304
  for (let sidecarPath of sidecarPaths) {
304
- let value = getValueAtPath(targetValue, sidecarPath);
305
+ let value = getValueAtPath$1(targetValue, sidecarPath);
305
306
  safe.push(value === void 0 ? {
306
307
  type: "unset",
307
308
  path: sidecarPath,
@@ -326,7 +327,7 @@ function toEngineSafePatches(patches, targetValue) {
326
327
  function filterInsertsMissingFromRemoteValue(patches, remoteValue) {
327
328
  return patches.flatMap((patch) => {
328
329
  if (patch.type !== "insert") return [patch];
329
- let parentValue = getValueAtPath(remoteValue, patch.path.slice(0, -1));
330
+ let parentValue = getValueAtPath$1(remoteValue, patch.path.slice(0, -1));
330
331
  if (!Array.isArray(parentValue)) return [patch];
331
332
  let items = patch.items.filter((item) => {
332
333
  let itemKey = getKey(item);
@@ -363,7 +364,7 @@ function toMergeableMarkDefsPatches(patches, getCurrentValue) {
363
364
  if (patch.type !== "set" || patch.path.at(-1) !== "markDefs" || !Array.isArray(patch.value)) return [patch];
364
365
  let currentValue = getCurrentValue();
365
366
  if (!currentValue) return [patch];
366
- let root = currentValue, storeMarkDefs = getValueAtPath(root, patch.path), storeBlock = getValueAtPath(root, patch.path.slice(0, -1));
367
+ let root = currentValue, storeMarkDefs = getValueAtPath$1(root, patch.path), storeBlock = getValueAtPath$1(root, patch.path.slice(0, -1));
367
368
  if (!Array.isArray(storeMarkDefs) || typeof storeBlock != "object" || !storeBlock) return [patch];
368
369
  let local = patch.value, store = storeMarkDefs;
369
370
  if (local.some((item) => item._key === void 0)) return [patch];
@@ -415,8 +416,8 @@ function canApplyToValue(patch, value) {
415
416
  switch (patch.type) {
416
417
  case "unset":
417
418
  case "insert":
418
- case "diffMatchPatch": return getValueAtPath(root, patch.path) !== void 0;
419
- case "set": return patch.path.length === 0 || getValueAtPath(root, patch.path.slice(0, -1)) !== void 0;
419
+ case "diffMatchPatch": return getValueAtPath$1(root, patch.path) !== void 0;
420
+ case "set": return patch.path.length === 0 || getValueAtPath$1(root, patch.path.slice(0, -1)) !== void 0;
420
421
  default: return !0;
421
422
  }
422
423
  }
@@ -448,7 +449,7 @@ function scopeRemotePatches(patches, fieldPath) {
448
449
  let prefix = arrayifyPath(fieldPath), converted = convertPatches(patches), scoped = [];
449
450
  for (let patch of converted) {
450
451
  let overlap = Math.min(patch.path.length, prefix.length), touchesField = !0;
451
- for (let index = 0; index < overlap; index++) if (!segmentsEqual(patch.path[index], prefix[index])) {
452
+ for (let index = 0; index < overlap; index++) if (!segmentsEqual$1(patch.path[index], prefix[index])) {
452
453
  touchesField = !1;
453
454
  break;
454
455
  }
@@ -818,14 +819,14 @@ function useSDKPresenceCursors(options) {
818
819
  excludeVersions: !0
819
820
  }, $[6] = fieldPath, $[7] = handle, $[8] = t1) : t1 = $[8];
820
821
  let { presence } = usePresenceForDocument(t1), t2;
821
- $[9] === presence ? t2 = $[10] : (t2 = presence.flatMap(_temp), $[9] = presence, $[10] = t2);
822
+ $[9] === presence ? t2 = $[10] : (t2 = presence.flatMap(_temp$1), $[9] = presence, $[10] = t2);
822
823
  let cursors = t2, t3;
823
824
  return $[11] !== cursors || $[12] !== renderCursor ? (t3 = {
824
825
  cursors,
825
826
  renderCursor
826
827
  }, $[11] = cursors, $[12] = renderCursor, $[13] = t3) : t3 = $[13], useRemoteCursors(t3);
827
828
  }
828
- function _temp(participant) {
829
+ function _temp$1(participant) {
829
830
  return participant.selection ? [{
830
831
  sessionId: participant.sessionId,
831
832
  selection: participant.selection,
@@ -842,6 +843,339 @@ function useFieldPath(path) {
842
843
  return $[0] === path ? t0 = $[1] : (t0 = arrayifyPath(path), $[0] = path, $[1] = t0), t0;
843
844
  }
844
845
  /**
846
+ * How the Studio stores an inline comment's anchor: per Portable Text block the
847
+ * selection touches, the block's entire plain text with these two private-use
848
+ * characters inserted where the selection starts and ends. Text rather than
849
+ * offsets, so the anchor can be re-found after the text around it changes.
850
+ */
851
+ const COMMENT_INDICATORS = ["", ""], COMMENT_INDICATORS_REGEX = RegExp(`[${COMMENT_INDICATORS.join("")}]`, "g");
852
+ function isTextBlock(node) {
853
+ return typeof node == "object" && !!node && Array.isArray(node.children) && typeof node._key == "string";
854
+ }
855
+ function isSpan$1(child) {
856
+ return child._type === "span" && typeof child.text == "string";
857
+ }
858
+ function getValueAtPath(value, path) {
859
+ let current = value;
860
+ for (let segment of path) {
861
+ if (typeof current != "object" || !current) return;
862
+ if (typeof segment == "string") current = current[segment];
863
+ else if (typeof segment == "number") current = Array.isArray(current) ? current[segment] : void 0;
864
+ else if (isKeyedSegment$1(segment)) current = Array.isArray(current) ? current.find((item) => typeof item == "object" && !!item && item._key === segment._key) : void 0;
865
+ else return;
866
+ }
867
+ return current;
868
+ }
869
+ function isKeyedSegment$1(segment) {
870
+ return typeof segment == "object" && !!segment && "_key" in segment;
871
+ }
872
+ function toPlainTextWithChildSeparators(block) {
873
+ return block.children.map((child) => isSpan$1(child) ? (child.text ?? "").replaceAll("", " ") : "").join("");
874
+ }
875
+ function diffText(current, next) {
876
+ let diff = makeDiff(current, next), diffs = cleanupEfficiency(diff);
877
+ return {
878
+ patches: makePatches(current, diffs, { margin: 15 }),
879
+ levenshtein: diffsLevenshtein(diffs)
880
+ };
881
+ }
882
+ function diffApply(current, patches) {
883
+ return applyPatches(patches, current, {
884
+ allowExceedingIndices: !0,
885
+ margin: 15
886
+ })[0];
887
+ }
888
+ /**
889
+ * Finds each stored anchor in the current editor value.
890
+ *
891
+ * A direct port of the Studio's `buildRangeDecorationSelectionsFromComments`,
892
+ * minus its Studio-only inputs. The stored text is diffed against the block's
893
+ * current text, so an anchor survives edits around it and inside it up to a
894
+ * similarity threshold. An anchor whose text is gone, or changed beyond
895
+ * recognition, is dropped rather than drawn somewhere wrong.
896
+ *
897
+ * Live tracking while the user types is not this function's job: the editor's
898
+ * `RangeDecoration.onMoved` does that. This runs when comments load or change.
899
+ */
900
+ function resolveCommentSelections(options) {
901
+ let { value, comments } = options, anchored = [];
902
+ for (let { commentId, relativePath, selection } of comments) for (let selectionMember of selection.value) {
903
+ let container = relativePath.length > 0 ? getValueAtPath(value, relativePath) : value, matchedBlock = Array.isArray(container) ? container.find((block) => isTextBlock(block) && block._key === selectionMember._key) : void 0;
904
+ if (!matchedBlock || !isTextBlock(matchedBlock)) continue;
905
+ let selectionText = selectionMember.text.replaceAll(COMMENT_INDICATORS_REGEX, ""), textWithChildSeparators = toPlainTextWithChildSeparators(matchedBlock), { patches } = diffText(selectionText, selectionMember.text), diffedText = diffApply(textWithChildSeparators, patches), startIndex = diffedText.indexOf(COMMENT_INDICATORS[0]), endIndex = diffedText.replaceAll(COMMENT_INDICATORS[0], "").indexOf(COMMENT_INDICATORS[1]), textWithoutCommentTags = diffedText.replaceAll(COMMENT_INDICATORS_REGEX, "");
906
+ if (startIndex === -1 || endIndex === -1) continue;
907
+ let oldCommentedText = selectionMember.text.slice(selectionMember.text.indexOf(COMMENT_INDICATORS[0]) + 1, selectionMember.text.indexOf(COMMENT_INDICATORS[1])), newCommentedText = textWithoutCommentTags.slice(startIndex, endIndex), { levenshtein } = diffText(newCommentedText, oldCommentedText), threshold = Math.round(newCommentedText.length + oldCommentedText.length / 2);
908
+ if (newCommentedText.length === 0 || levenshtein > threshold || startIndex + 1 === endIndex) continue;
909
+ let childIndexAnchor = 0, anchorOffset = 0, childIndexFocus = 0, focusOffset = 0;
910
+ for (let i = 0; i < textWithoutCommentTags.length && (textWithoutCommentTags[i] === "" && (i <= startIndex && (anchorOffset = -1, childIndexAnchor++), focusOffset = -1, childIndexFocus++), i < startIndex && anchorOffset++, i < startIndex + newCommentedText.length && focusOffset++, i !== startIndex + newCommentedText.length); i++);
911
+ anchored.push({
912
+ commentId,
913
+ selection: {
914
+ anchor: {
915
+ path: [
916
+ ...relativePath,
917
+ { _key: matchedBlock._key },
918
+ "children",
919
+ { _key: matchedBlock.children[childIndexAnchor]._key }
920
+ ],
921
+ offset: anchorOffset
922
+ },
923
+ focus: {
924
+ path: [
925
+ ...relativePath,
926
+ { _key: matchedBlock._key },
927
+ "children",
928
+ { _key: matchedBlock.children[childIndexFocus]._key }
929
+ ],
930
+ offset: focusOffset
931
+ }
932
+ }
933
+ });
934
+ }
935
+ return anchored;
936
+ }
937
+ function diffsLevenshtein(diffs) {
938
+ let levenshtein = 0, insertions = 0, deletions = 0;
939
+ for (let [op, data] of diffs) switch (op) {
940
+ case DIFF_INSERT:
941
+ insertions += data.length;
942
+ break;
943
+ case DIFF_DELETE:
944
+ deletions += data.length;
945
+ break;
946
+ case DIFF_EQUAL: levenshtein += Math.max(insertions, deletions), insertions = 0, deletions = 0;
947
+ }
948
+ return levenshtein += Math.max(insertions, deletions), levenshtein;
949
+ }
950
+ /**
951
+ * Reduces a comment's stored field path to a path inside this editor.
952
+ *
953
+ * `undefined` means the comment belongs to some other editor: a different
954
+ * field, a sibling container, or a stored path that does not parse. Skipping
955
+ * those is what lets several editors on one document each decorate only their
956
+ * own comments.
957
+ */
958
+ function relativeCommentPath(basePath, fieldPath) {
959
+ let parsed;
960
+ try {
961
+ parsed = arrayifyPath(fieldPath);
962
+ } catch {
963
+ return;
964
+ }
965
+ if (!(parsed.length < basePath.length)) {
966
+ for (let i = 0; i < basePath.length; i++) if (!segmentsEqual(basePath[i], parsed[i])) return;
967
+ return parsed.slice(basePath.length);
968
+ }
969
+ }
970
+ function segmentsEqual(a, b) {
971
+ return isKeyedSegment$1(a) && isKeyedSegment$1(b) ? a._key === b._key : a === b;
972
+ }
973
+ /**
974
+ * Turns the current editor selection into the stored comment anchor.
975
+ *
976
+ * Per selected block, the block's entire plain text with the selection
977
+ * boundaries marked by the two indicator characters. The write-time mirror of
978
+ * `resolveCommentSelections`, and deliberately built the way the Studio builds
979
+ * it so a comment written here re-anchors there and back.
980
+ *
981
+ * Returns `null` when there is nothing commentable: a collapsed selection,
982
+ * no selected text, or a selection spanning blocks from different containing
983
+ * arrays, which a single stored path cannot describe.
984
+ */
985
+ function buildStoredSelection(options) {
986
+ let { selection, selectedBlocks } = options;
987
+ if (!selection || selectedBlocks.length === 0) return null;
988
+ let [start, end] = selection.backward ? [selection.focus, selection.anchor] : [selection.anchor, selection.focus], containerPath = selectedBlocks[0].path.slice(0, -1);
989
+ if (!selectedBlocks.every((selected) => pathsEqual(selected.path.slice(0, -1), containerPath))) return null;
990
+ let selectedCharacters = 0, value = selectedBlocks.map((selected, index) => {
991
+ let isFirst = index === 0, isLast = index === selectedBlocks.length - 1, plain = plainText(selected.node), from = isFirst ? plainOffset(selected.node, start.path, start.offset) : 0, to = isLast ? plainOffset(selected.node, end.path, end.offset) : plain.length;
992
+ return selectedCharacters += Math.max(0, to - from), {
993
+ _key: selected.node._key,
994
+ text: `${plain.slice(0, from)}${COMMENT_INDICATORS[0]}${plain.slice(from, to)}${COMMENT_INDICATORS[1]}${plain.slice(to)}`
995
+ };
996
+ });
997
+ return selectedCharacters === 0 ? null : {
998
+ containerPath,
999
+ selection: {
1000
+ type: "text",
1001
+ value
1002
+ }
1003
+ };
1004
+ }
1005
+ function plainText(block) {
1006
+ return block.children.map((child) => isSpan(child) ? child.text ?? "" : "").join("");
1007
+ }
1008
+ /**
1009
+ * Converts a selection point into an offset within the block's plain text: the
1010
+ * text of every span before the point's child, plus the offset inside it. A
1011
+ * point whose child is not in this block clamps to the block edge, which is
1012
+ * where a cross-block selection boundary lands.
1013
+ */
1014
+ function plainOffset(block, pointPath, offset) {
1015
+ let childSegment = pointPath[pointPath.length - 1];
1016
+ if (!isKeyedSegment(childSegment)) return offset;
1017
+ let total = 0;
1018
+ for (let child of block.children) {
1019
+ if (child._key === childSegment._key) return total + (isSpan(child) ? offset : 0);
1020
+ isSpan(child) && (total += (child.text ?? "").length);
1021
+ }
1022
+ return total;
1023
+ }
1024
+ function isSpan(child) {
1025
+ return child._type === "span" && typeof child.text == "string";
1026
+ }
1027
+ function isKeyedSegment(segment) {
1028
+ return typeof segment == "object" && !!segment && "_key" in segment;
1029
+ }
1030
+ function pathsEqual(a, b) {
1031
+ return a.length === b.length && a.every((segment, index) => {
1032
+ let other = b[index];
1033
+ return isKeyedSegment(segment) && isKeyedSegment(other) ? segment._key === other._key : segment === other;
1034
+ });
1035
+ }
1036
+ const NO_MOVES = {};
1037
+ /**
1038
+ * Anchors count as unchanged when every comment still sits at the same spot,
1039
+ * so resolving on each editor emission only re-renders when a highlight
1040
+ * actually needs to draw somewhere else.
1041
+ */
1042
+ function sameAnchors(a, b) {
1043
+ return a.length === b.length && a.every((anchor, index) => {
1044
+ let other = b[index];
1045
+ return anchor.commentId === other.commentId && samePoint(anchor.selection.anchor, other.selection.anchor) && samePoint(anchor.selection.focus, other.selection.focus);
1046
+ });
1047
+ }
1048
+ function samePoint(a, b) {
1049
+ return a.offset !== b.offset || a.path.length !== b.path.length ? !1 : a.path.every((segment, index) => {
1050
+ let other = b.path[index];
1051
+ return typeof segment == "object" && segment && typeof other == "object" && other ? segment._key === other._key : segment === other;
1052
+ });
1053
+ }
1054
+ /**
1055
+ * Inline comment highlights for a Portable Text field, as range decorations.
1056
+ *
1057
+ * Pass the result to `<PortableTextEditable rangeDecorations={...} />`. Each
1058
+ * thread's first comment that carries a text anchor on this field gets one
1059
+ * decoration. Highlights stay put while the local user types, and a highlight
1060
+ * whose text has been deleted or rewritten beyond recognition is dropped
1061
+ * rather than drawn on the wrong words.
1062
+ *
1063
+ * Suspends while the document's comments load, like every SDK read hook.
1064
+ *
1065
+ * Resolved threads draw nothing: this mirrors the Studio, where resolving a
1066
+ * thread removes its highlight from the text.
1067
+ *
1068
+ * @public
1069
+ */
1070
+ function useSDKCommentDecorations(options) {
1071
+ let $ = c(19), handle, path, renderDecoration;
1072
+ $[0] === options ? (handle = $[1], path = $[2], renderDecoration = $[3]) : ({path, renderDecoration, ...handle} = options, $[0] = options, $[1] = handle, $[2] = path, $[3] = renderDecoration);
1073
+ let editor = useEditor(), t0;
1074
+ $[4] === handle ? t0 = $[5] : (t0 = { ...handle }, $[4] = handle, $[5] = t0);
1075
+ let { comments } = useComments(t0), t1;
1076
+ if ($[6] !== comments || $[7] !== path) {
1077
+ let basePath = arrayifyPath(path);
1078
+ t1 = comments.flatMap((comment) => {
1079
+ if (comment.parentCommentId || !comment.selection || comment.status !== "open") return [];
1080
+ let relativePath = relativeCommentPath(basePath, comment.fieldPath);
1081
+ return relativePath === void 0 ? [] : [{
1082
+ comment,
1083
+ relativePath,
1084
+ selection: comment.selection
1085
+ }];
1086
+ }), $[6] = comments, $[7] = path, $[8] = t1;
1087
+ } else t1 = $[8];
1088
+ let inline = t1, t2;
1089
+ $[9] === inline ? t2 = $[10] : (t2 = (snapshot) => resolveCommentSelections({
1090
+ value: snapshot.context.value,
1091
+ comments: inline.map(_temp)
1092
+ }), $[9] = inline, $[10] = t2);
1093
+ let anchored = useEditorSelector(editor, t2, sameAnchors), t3;
1094
+ $[11] === Symbol.for("react.memo_cache_sentinel") ? (t3 = {}, $[11] = t3) : t3 = $[11];
1095
+ let t4;
1096
+ $[12] === inline ? t4 = $[13] : (t4 = {
1097
+ forComments: inline,
1098
+ selections: t3
1099
+ }, $[12] = inline, $[13] = t4);
1100
+ let [moved, setMoved] = useState(t4), movedSelections = moved.forComments === inline ? moved.selections : NO_MOVES, t5;
1101
+ if ($[14] !== anchored || $[15] !== inline || $[16] !== movedSelections || $[17] !== renderDecoration) {
1102
+ let commentsById = new Map(inline.map(_temp2));
1103
+ t5 = anchored.flatMap((anchor) => {
1104
+ let comment_2 = commentsById.get(anchor.commentId);
1105
+ if (!comment_2) return [];
1106
+ let movedSelection = movedSelections[anchor.commentId], selection_0 = movedSelection === void 0 ? anchor.selection : movedSelection;
1107
+ return selection_0 === null ? [] : [{
1108
+ component: renderDecoration(comment_2),
1109
+ selection: selection_0,
1110
+ onMoved: (t6) => {
1111
+ let { newSelection } = t6;
1112
+ setMoved((previous) => ({
1113
+ forComments: inline,
1114
+ selections: {
1115
+ ...previous.forComments === inline ? previous.selections : {},
1116
+ [anchor.commentId]: newSelection
1117
+ }
1118
+ }));
1119
+ },
1120
+ payload: { commentId: anchor.commentId }
1121
+ }];
1122
+ }), $[14] = anchored, $[15] = inline, $[16] = movedSelections, $[17] = renderDecoration, $[18] = t5;
1123
+ } else t5 = $[18];
1124
+ return t5;
1125
+ }
1126
+ function _temp2(t0) {
1127
+ let { comment: comment_1 } = t0;
1128
+ return [comment_1.id, comment_1];
1129
+ }
1130
+ function _temp(t0) {
1131
+ let { comment: comment_0, relativePath: relativePath_0, selection } = t0;
1132
+ return {
1133
+ commentId: comment_0.id,
1134
+ relativePath: relativePath_0,
1135
+ selection
1136
+ };
1137
+ }
1138
+ /** Reports the selection when it can take a comment, `null` otherwise. */
1139
+ function getCommentableSelection(snapshot) {
1140
+ let selection = getSelection(snapshot);
1141
+ return buildStoredSelection({
1142
+ selection,
1143
+ selectedBlocks: getSelectedTextBlocks(snapshot)
1144
+ }) ? selection : null;
1145
+ }
1146
+ /**
1147
+ * Lets the app author inline comments on a Portable Text field.
1148
+ *
1149
+ * The plugin captures the selection and writes the comment; the composer UI is
1150
+ * the app's, the same split the Studio and Canvas use. Comments are written in
1151
+ * the shape the Studio stores, so a thread started here shows up there.
1152
+ *
1153
+ * @public
1154
+ */
1155
+ function useSDKCommentAuthoring(options) {
1156
+ let $ = c(11), handle, path;
1157
+ $[0] === options ? (handle = $[1], path = $[2]) : ({path, ...handle} = options, $[0] = options, $[1] = handle, $[2] = path);
1158
+ let editor = useEditor(), { createComment } = useCommentActions(), commentableSelection = useEditorSelector(editor, getCommentableSelection, isEqualSelections), t0;
1159
+ $[3] !== createComment || $[4] !== editor || $[5] !== handle || $[6] !== path ? (t0 = (t1) => {
1160
+ let { message, commentId } = t1, snapshot = editor.getSnapshot(), built = buildStoredSelection({
1161
+ selection: getSelection(snapshot),
1162
+ selectedBlocks: getSelectedTextBlocks(snapshot)
1163
+ });
1164
+ return built ? createComment({
1165
+ ...handle,
1166
+ fieldPath: stringifyPath([...arrayifyPath(path), ...built.containerPath]),
1167
+ selection: built.selection,
1168
+ message,
1169
+ ...commentId === void 0 ? {} : { commentId }
1170
+ }) : Promise.reject(/* @__PURE__ */ Error("Nothing commentable is selected, so there is nothing to anchor the comment to."));
1171
+ }, $[3] = createComment, $[4] = editor, $[5] = handle, $[6] = path, $[7] = t0) : t0 = $[7];
1172
+ let t1;
1173
+ return $[8] !== commentableSelection || $[9] !== t0 ? (t1 = {
1174
+ commentableSelection,
1175
+ createInlineComment: t0
1176
+ }, $[8] = commentableSelection, $[9] = t0, $[10] = t1) : t1 = $[10], t1;
1177
+ }
1178
+ /**
845
1179
  * Mid-tone hues, so a caret stays legible whether the app is light or dark. This
846
1180
  * package cannot read the app's theme, so it does not try.
847
1181
  */
@@ -1030,6 +1364,6 @@ function resolveCursorRenderer(renderCursor) {
1030
1364
  function mergePresenceDecorations(rangeDecorations, cursors, drawCursors) {
1031
1365
  return drawCursors ? [...rangeDecorations ?? [], ...cursors] : rangeDecorations;
1032
1366
  }
1033
- export { SDKPortableTextEditable, SDKPresencePlugin, SDKValuePlugin, ValueSyncPlugin, useSDKPresenceCursors };
1367
+ export { SDKPortableTextEditable, SDKPresencePlugin, SDKValuePlugin, ValueSyncPlugin, useSDKCommentAuthoring, useSDKCommentDecorations, useSDKPresenceCursors };
1034
1368
 
1035
1369
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["(cursor: TCursor, selection: EditorSelection) => {\n setOverrides((previous) =>\n recordCursorMove(previous, cursors, cursor, selection),\n )\n }","useCallback(\n (cursor: TCursor, selection: EditorSelection) => {\n setOverrides((previous) =>\n recordCursorMove(previous, cursors, cursor, selection),\n )\n },\n [cursors],\n )","toRangeDecorations({cursors, overrides, renderCursor, onCursorMoved})","useMemo(\n () => toRangeDecorations({cursors, overrides, renderCursor, onCursorMoved}),\n [cursors, overrides, renderCursor, onCursorMoved],\n )","(callback: (patches: PtePatch[]) => void) => {\n return subscribeDocumentEvents(instance, {\n eventHandler: (event) => {\n // widen before narrowing: `remote-patches` is not part of the\n // `DocumentEvent` union in older SDK typings\n const candidate: {type: string} = event\n if (!isRemotePatchesEvent(candidate)) {\n return\n }\n // our own transactions are already reflected in the editor\n if (candidate.origin !== 'remote') {\n return\n }\n if (\n getPublishedDocumentId(candidate.documentId) !==\n getPublishedDocumentId(documentId)\n ) {\n return\n }\n\n let patches: PtePatch[] | null\n try {\n patches = scopeRemotePatches(candidate.patches, path)\n } catch {\n // unconvertible patch shapes fall back to the whole-value sync\n // driven by `onRemoteValueChange`\n return\n }\n if (patches && patches.length > 0) {\n callback(patches)\n }\n },\n })\n }","useCallback(\n (callback: (patches: PtePatch[]) => void) => {\n return subscribeDocumentEvents(instance, {\n eventHandler: (event) => {\n // widen before narrowing: `remote-patches` is not part of the\n // `DocumentEvent` union in older SDK typings\n const candidate: {type: string} = event\n if (!isRemotePatchesEvent(candidate)) {\n return\n }\n // our own transactions are already reflected in the editor\n if (candidate.origin !== 'remote') {\n return\n }\n if (\n getPublishedDocumentId(candidate.documentId) !==\n getPublishedDocumentId(documentId)\n ) {\n return\n }\n\n let patches: PtePatch[] | null\n try {\n patches = scopeRemotePatches(candidate.patches, path)\n } catch {\n // unconvertible patch shapes fall back to the whole-value sync\n // driven by `onRemoteValueChange`\n return\n }\n if (patches && patches.length > 0) {\n callback(patches)\n }\n },\n })\n },\n [instance, documentId, path],\n )","(patches: PtePatch[]) => {\n const sanityPatches = convertPatchesToSanity(patches, {prefix: path})\n // `preserveOperations` ships in @sanity/sdk >= 2.17; the intersection\n // type keeps the plugin compatible with older SDK typings\n const action: EditDocumentAction & {preserveOperations?: boolean} = {\n ...editDocument(\n {documentId, documentType},\n sanityPatches as Parameters<typeof editDocument>[1],\n ),\n preserveOperations: true,\n }\n applyActions(action)\n }","patches","useCallback(\n (patches: PtePatch[]) => {\n const sanityPatches = convertPatchesToSanity(patches, {prefix: path})\n // `preserveOperations` ships in @sanity/sdk >= 2.17; the intersection\n // type keeps the plugin compatible with older SDK typings\n const action: EditDocumentAction & {preserveOperations?: boolean} = {\n ...editDocument(\n {documentId, documentType},\n sanityPatches as Parameters<typeof editDocument>[1],\n ),\n preserveOperations: true,\n }\n applyActions(action)\n },\n [applyActions, documentId, documentType, path],\n )","<ValueSyncPlugin\n getRemoteValue={getCurrent}\n pushValue={setSdkValue}\n onRemoteValueChange={subscribe}\n onRemotePatches={onRemotePatches}\n pushPatches={pushPatches}\n />","valueSyncMachine.provide({\n actions: {\n 'push to remote': ({context, event}) => {\n if (event.type !== 'mutation flushed') {\n return\n }\n\n if (pushPatches && event.patches.length > 0) {\n try {\n const mergeable = toMergeableMarkDefsPatches(\n event.patches,\n context.getRemoteValue,\n )\n debug.push('pushing patches %o', mergeable)\n pushPatches(mergeable)\n return\n } catch {\n // fall back to pushing the whole value below\n }\n }\n\n if (debug.push.enabled) {\n debug.push(\n 'pushing whole value %s',\n debugTextOf(\n event.value ?? context.editor.getSnapshot().context.value,\n ),\n )\n }\n pushValue(event.value ?? context.editor.getSnapshot().context.value)\n },\n },\n })","{context, event}","{\n input: {\n editor,\n getRemoteValue,\n onRemoteValueChange,\n onRemotePatches,\n },\n }","normalizeDocumentHandle(props)","{...handle, path: fieldPath, selection}","normalizeDocumentHandle(options)","{\n ...handle,\n path: fieldPath,\n excludeVersions: true,\n }","presence.flatMap((participant): SDKRemoteCursor[] =>\n participant.selection\n ? [\n {\n sessionId: participant.sessionId,\n selection: participant.selection,\n user: participant.user,\n },\n ]\n : [],\n )","(participant): SDKRemoteCursor[] =>\n participant.selection\n ? [\n {\n sessionId: participant.sessionId,\n selection: participant.selection,\n user: participant.user,\n },\n ]\n : []","useMemo(\n () =>\n presence.flatMap((participant): SDKRemoteCursor[] =>\n participant.selection\n ? [\n {\n sessionId: participant.sessionId,\n selection: participant.selection,\n user: participant.user,\n },\n ]\n : [],\n ),\n [presence],\n )","{cursors, renderCursor}","arrayifyPath(path)","useMemo(() => arrayifyPath(path), [path])","getCaretColor(cursor.user.sanityUserId)","sanityUserId","{\n borderLeft: `2px solid ${color}`,\n marginLeft: -1,\n position: 'relative',\n // The line must not swallow clicks meant for the text under it.\n pointerEvents: 'none',\n }","`2px solid ${color}`","{\n backgroundColor: color,\n borderRadius: '50%',\n height: DOT_SIZE,\n left: -1,\n pointerEvents: 'auto',\n position: 'absolute',\n top: -(DOT_SIZE - 1),\n transform: 'translateX(-50%)',\n width: DOT_SIZE,\n }","<span\n data-testid={`presence-caret-dot-${cursor.sessionId}`}\n style={{\n backgroundColor: color,\n borderRadius: '50%',\n height: DOT_SIZE,\n left: -1,\n pointerEvents: 'auto',\n position: 'absolute',\n top: -(DOT_SIZE - 1),\n transform: 'translateX(-50%)',\n width: DOT_SIZE,\n }}\n title={displayName}\n />","`presence-caret-dot-${cursor.sessionId}`","<span\n // Without this the caret becomes editable content and the local user can\n // put their own cursor inside it.\n contentEditable={false}\n data-testid={`presence-caret-${cursor.sessionId}`}\n style={{\n borderLeft: `2px solid ${color}`,\n marginLeft: -1,\n position: 'relative',\n // The line must not swallow clicks meant for the text under it.\n pointerEvents: 'none',\n }}\n >\n <span\n data-testid={`presence-caret-dot-${cursor.sessionId}`}\n style={{\n backgroundColor: color,\n borderRadius: '50%',\n height: DOT_SIZE,\n left: -1,\n pointerEvents: 'auto',\n position: 'absolute',\n top: -(DOT_SIZE - 1),\n transform: 'translateX(-50%)',\n width: DOT_SIZE,\n }}\n title={displayName}\n />\n </span>","`presence-caret-${cursor.sessionId}`","<>\n <span\n // Without this the caret becomes editable content and the local user can\n // put their own cursor inside it.\n contentEditable={false}\n data-testid={`presence-caret-${cursor.sessionId}`}\n style={{\n borderLeft: `2px solid ${color}`,\n marginLeft: -1,\n position: 'relative',\n // The line must not swallow clicks meant for the text under it.\n pointerEvents: 'none',\n }}\n >\n <span\n data-testid={`presence-caret-dot-${cursor.sessionId}`}\n style={{\n backgroundColor: color,\n borderRadius: '50%',\n height: DOT_SIZE,\n left: -1,\n pointerEvents: 'auto',\n position: 'absolute',\n top: -(DOT_SIZE - 1),\n transform: 'translateX(-50%)',\n width: DOT_SIZE,\n }}\n title={displayName}\n />\n </span>\n {children}\n </>","resolveCursorRenderer(renderCursor)","{\n ...handle,\n path,\n renderCursor: renderer.renderCursor,\n }","renderCursor","mergePresenceDecorations(rangeDecorations, cursors, renderer.drawCursors)","drawCursors","useMemo(\n () =>\n mergePresenceDecorations(rangeDecorations, cursors, renderer.drawCursors),\n [cursors, rangeDecorations, renderer.drawCursors],\n )","<PortableTextEditable {...editableProps} rangeDecorations={decorations} />","<SDKValuePlugin {...handle} path={path} />","<SDKPresencePlugin {...handle} path={path} />","<>\n <PortableTextEditable {...editableProps} rangeDecorations={decorations} />\n <SDKValuePlugin {...handle} path={path} />\n <SDKPresencePlugin {...handle} path={path} />\n </>"],"sources":["../src/presence-cursors.ts","../src/plugin.presence-sync.tsx","../src/debug.ts","../src/sdk-document-handle.ts","../src/plugin.sdk-value.tsx","../src/plugin.sdk-presence.tsx","../src/presence-caret.tsx","../src/sdk-editable.tsx"],"sourcesContent":["import type {EditorSelection, RangeDecoration} from '@portabletext/editor'\nimport {isEqualSelections} from '@portabletext/editor/utils'\nimport type {PropsWithChildren, ReactElement} from 'react'\n\n/**\n * A remote participant's position in the editor.\n *\n * @public\n */\nexport interface RemoteCursor {\n /**\n * Identifies one editing session rather than one person. The same user in two\n * tabs reports two sessions and therefore draws two carets.\n */\n sessionId: string\n /**\n * The selection the participant last reported, or `null` when they have none.\n */\n selection: EditorSelection\n}\n\n/**\n * Where the local editor has pushed a remote caret, and the reported selection\n * it was pushed from.\n *\n * Typing above or inside a remote caret moves it, and the editor reports the\n * new position through `onMoved`. Keeping the selection it moved from is what\n * lets a later report from that participant take over again.\n *\n * @public\n */\nexport interface CursorOverride {\n reported: EditorSelection\n current: EditorSelection\n}\n\n/**\n * Options for {@link toRangeDecorations}.\n *\n * @public\n */\nexport interface RangeDecorationOptions<TCursor extends RemoteCursor> {\n cursors: readonly TCursor[]\n overrides: ReadonlyMap<string, CursorOverride>\n /**\n * Builds the component that draws one caret. The plugin has no opinion about\n * how a caret looks, so this is the caller's to provide.\n */\n renderCursor: (cursor: TCursor) => (props: PropsWithChildren) => ReactElement\n onCursorMoved?: (cursor: TCursor, selection: EditorSelection) => void\n}\n\n/**\n * Reduces a selection to a caret at its focus point.\n *\n * Presence answers where someone is, so decorating their whole selection would\n * highlight text the local user never selected. The Studio collapses the same\n * way, which keeps the two consistent when both are open on a document.\n *\n * @public\n */\nexport function collapseToCaret(selection: EditorSelection): EditorSelection {\n if (selection === null) {\n return null\n }\n return {anchor: selection.focus, focus: selection.focus}\n}\n\n/**\n * Where one remote caret should be drawn.\n *\n * A caret the local editor has moved keeps that moved position for as long as\n * the participant keeps reporting the same selection. A changed report wins,\n * because the participant has actually moved.\n *\n * @public\n */\nexport function resolveCursorSelection(\n cursor: RemoteCursor,\n override: CursorOverride | undefined,\n): EditorSelection {\n if (override && isEqualSelections(override.reported, cursor.selection)) {\n return override.current\n }\n return collapseToCaret(cursor.selection)\n}\n\n/**\n * Records where the editor moved a caret to.\n *\n * Overrides for sessions that are no longer present are dropped, so a long\n * editing session does not accumulate one entry per participant who ever\n * visited. Returns the map untouched when nothing changed.\n *\n * @public\n */\nexport function recordCursorMove(\n overrides: ReadonlyMap<string, CursorOverride>,\n cursors: readonly RemoteCursor[],\n cursor: RemoteCursor,\n selection: EditorSelection,\n): ReadonlyMap<string, CursorOverride> {\n const moved = collapseToCaret(selection)\n const existing = overrides.get(cursor.sessionId)\n const alreadyRecorded =\n existing !== undefined &&\n isEqualSelections(existing.reported, cursor.selection) &&\n isEqualSelections(existing.current, moved)\n\n const live = new Set(cursors.map((candidate) => candidate.sessionId))\n const staleKeys = [...overrides.keys()].filter((key) => !live.has(key))\n\n if (alreadyRecorded && staleKeys.length === 0) {\n return overrides\n }\n\n const next = new Map(overrides)\n for (const key of staleKeys) {\n next.delete(key)\n }\n next.set(cursor.sessionId, {reported: cursor.selection, current: moved})\n return next\n}\n\n/**\n * Maps remote cursors to range decorations for\n * `<PortableTextEditable rangeDecorations={...} />`.\n *\n * Participants with no caret to draw are skipped, so someone who clears their\n * selection stops drawing without being treated as having left.\n *\n * @public\n */\nexport function toRangeDecorations<TCursor extends RemoteCursor>(\n options: RangeDecorationOptions<TCursor>,\n): RangeDecoration[] {\n const {cursors, overrides, renderCursor, onCursorMoved} = options\n const decorations: RangeDecoration[] = []\n\n for (const cursor of cursors) {\n const selection = resolveCursorSelection(\n cursor,\n overrides.get(cursor.sessionId),\n )\n if (selection === null) {\n continue\n }\n decorations.push({\n component: renderCursor(cursor),\n selection,\n payload: {sessionId: cursor.sessionId},\n onMoved: onCursorMoved\n ? (details) => onCursorMoved(cursor, details.newSelection)\n : undefined,\n })\n }\n\n return decorations\n}\n","import {\n useEditor,\n useEditorSelector,\n type EditorSelection,\n type RangeDecoration,\n} from '@portabletext/editor'\nimport {getSelection} from '@portabletext/editor/selectors'\nimport {isEqualSelections} from '@portabletext/editor/utils'\nimport {\n useCallback,\n useMemo,\n useState,\n type PropsWithChildren,\n type ReactElement,\n} from 'react'\nimport {\n recordCursorMove,\n toRangeDecorations,\n type CursorOverride,\n type RemoteCursor,\n} from './presence-cursors'\n\n/**\n * Options for {@link useRemoteCursors}.\n *\n * @public\n */\nexport interface UseRemoteCursorsOptions<TCursor extends RemoteCursor> {\n /**\n * The remote participants to draw. Keep this array referentially stable, for\n * example with `useMemo`, so that decorations are not rebuilt on every\n * render.\n */\n cursors: readonly TCursor[]\n renderCursor: (cursor: TCursor) => (props: PropsWithChildren) => ReactElement\n}\n\n/**\n * The local user's selection, deduped by value.\n *\n * The editor produces a fresh selection object on every snapshot change, so\n * subscribing to it directly would report presence far more often than the\n * caret actually moves.\n *\n * @public\n */\nexport function useLocalSelection(): EditorSelection {\n const editor = useEditor()\n // Direction is ignored, which `isEqualSelections` already does: a caret is\n // drawn at the focus point, so reversing a selection moves nothing.\n return useEditorSelector(editor, getSelection, isEqualSelections)\n}\n\n/**\n * Turns remote cursors into range decorations, keeping each caret anchored as\n * the local user edits.\n *\n * Knows nothing about Sanity or the SDK, so it can also drive carets from\n * another source or from a test fixture. `useSDKPresenceCursors` is the version\n * wired to SDK presence.\n *\n * @public\n */\nexport function useRemoteCursors<TCursor extends RemoteCursor>(\n options: UseRemoteCursorsOptions<TCursor>,\n): RangeDecoration[] {\n const {cursors, renderCursor} = options\n // Only the moved carets need to survive a render. Where every caret is drawn\n // is derived from them plus the latest report, so there is no state to keep\n // in step with the incoming cursors.\n const [overrides, setOverrides] =\n useState<ReadonlyMap<string, CursorOverride>>(EMPTY_OVERRIDES)\n\n const onCursorMoved = useCallback(\n (cursor: TCursor, selection: EditorSelection) => {\n setOverrides((previous) =>\n recordCursorMove(previous, cursors, cursor, selection),\n )\n },\n [cursors],\n )\n\n return useMemo(\n () => toRangeDecorations({cursors, overrides, renderCursor, onCursorMoved}),\n [cursors, overrides, renderCursor, onCursorMoved],\n )\n}\n\nconst EMPTY_OVERRIDES: ReadonlyMap<string, CursorOverride> = new Map()\n","import rawDebug from 'debug'\n\n// Keep in sync with `packages/editor/src/internal-utils/debug.ts`: sharing\n// the `pte:` root lets `localStorage.debug = 'pte:*'` interleave this\n// plugin's sync traces with the editor's own output on one timeline.\nconst rootName = 'pte:plugin-sdk-value:'\n\nfunction createDebugger(name: string): rawDebug.Debugger {\n const namespace = `${rootName}${name}`\n if (rawDebug && rawDebug.enabled(namespace)) {\n return rawDebug(namespace)\n }\n return rawDebug(rootName)\n}\n\nexport const debug = {\n mutation: createDebugger('mutation'),\n push: createDebugger('push'),\n remote: createDebugger('remote'),\n repair: createDebugger('repair'),\n}\n","import type {DocumentHandle, DocumentResource} from '@sanity/sdk-react'\n\ntype DocumentHandleWithLegacySource = DocumentHandle & {\n source?: DocumentResource\n}\n\nexport function normalizeDocumentHandle<T extends DocumentHandle>(\n handle: T & DocumentHandleWithLegacySource,\n): Omit<T, 'source'> {\n const {source, ...normalizedHandle} = handle\n\n if (normalizedHandle.resource !== undefined || source === undefined) {\n return normalizedHandle\n }\n\n return {...normalizedHandle, resource: source}\n}\n","import {\n useEditor,\n type Editor,\n type PortableTextBlock,\n type Patch as PtePatch,\n} from '@portabletext/editor'\nimport {\n applyAll,\n type JSONValue,\n type Path,\n type PathSegment,\n type InsertPatch as PteInsertPatch,\n} from '@portabletext/patches'\nimport {diffValue, type SanityPatchOperations} from '@sanity/diff-patch'\nimport {\n parsePath,\n type ExprNode,\n type PathNode,\n type SegmentNode,\n type ThisNode,\n} from '@sanity/json-match'\nimport {\n editDocument,\n getDocumentState,\n subscribeDocumentEvents,\n useApplyDocumentActions,\n useEditDocument,\n useSanityInstance,\n type DocumentHandle,\n type DocumentResource,\n type EditDocumentAction,\n} from '@sanity/sdk-react'\nimport {useActorRef} from '@xstate/react'\nimport {useCallback} from 'react'\nimport {fromCallback, setup, type AnyEventObject} from 'xstate'\nimport {debug} from './debug'\nimport {normalizeDocumentHandle} from './sdk-document-handle'\n\ntype InsertPatch = Required<Pick<SanityPatchOperations, 'insert'>>\n\nconst ARRAYIFY_ERROR_MESSAGE =\n 'Unexpected path format from diffValue output. Please report this issue.'\n\nfunction* getSegments(\n node: PathNode,\n): Generator<Exclude<SegmentNode, ThisNode>> {\n if (node.base) {\n yield* getSegments(node.base)\n }\n if (node.segment.type !== 'This') {\n yield node.segment\n }\n}\n\nfunction isKeyPath(node: ExprNode): node is PathNode {\n if (node.type !== 'Path') {\n return false\n }\n if (node.base) {\n return false\n }\n if (node.recursive) {\n return false\n }\n if (node.segment.type !== 'Identifier') {\n return false\n }\n return node.segment.name === '_key'\n}\n\nexport function arrayifyPath(pathExpr: string): Path {\n const node = parsePath(pathExpr)\n if (!node) {\n return []\n }\n if (node.type !== 'Path') {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n\n return Array.from(getSegments(node)).map((segment): PathSegment => {\n if (segment.type === 'Identifier') {\n return segment.name\n }\n if (segment.type !== 'Subscript') {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n if (segment.elements.length !== 1) {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n\n const [element] = segment.elements\n if (element.type === 'Number') {\n return element.value\n }\n\n if (element.type !== 'Comparison') {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n if (element.operator !== '==') {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n const keyPathNode = [element.left, element.right].find(isKeyPath)\n if (!keyPathNode) {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n const other = element.left === keyPathNode ? element.right : element.left\n if (other.type !== 'String') {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n return {_key: other.value}\n })\n}\n\nexport function convertPatches(patches: SanityPatchOperations[]): PtePatch[] {\n return patches.flatMap((p) => {\n return Object.entries(p).flatMap(([type, values]): PtePatch[] => {\n const origin = 'remote'\n\n switch (type) {\n case 'set':\n case 'setIfMissing':\n case 'diffMatchPatch':\n case 'inc':\n case 'dec': {\n return Object.entries(values).map(\n ([pathExpr, value]) =>\n ({type, value, origin, path: arrayifyPath(pathExpr)}) as PtePatch,\n )\n }\n case 'unset': {\n if (!Array.isArray(values)) {\n return []\n }\n return values.map(arrayifyPath).map((path) => ({type, origin, path}))\n }\n case 'insert': {\n const {items, ...rest} = values as InsertPatch['insert']\n type InsertPosition = PteInsertPatch['position']\n const position = Object.keys(rest).at(0) as InsertPosition | undefined\n\n if (!position) {\n return []\n }\n const pathExpr = (rest as {[K in InsertPosition]: string})[position]\n const insertPatch: PteInsertPatch = {\n type,\n origin,\n position,\n path: arrayifyPath(pathExpr),\n items: items as JSONValue[],\n }\n\n return [insertPatch]\n }\n\n default: {\n return []\n }\n }\n })\n })\n}\n\nconst STRINGIFY_ERROR_MESSAGE =\n 'Unable to convert an editor patch path to a Sanity path expression.'\n\n/**\n * Converts a Portable Text Editor patch path (an array of segments) into a\n * Sanity json-match path expression. The inverse of `arrayifyPath`.\n *\n * @internal\n */\nexport function stringifyPatchPath(path: Path): string {\n let result = ''\n for (const segment of path) {\n if (typeof segment === 'string') {\n result = result === '' ? segment : `${result}.${segment}`\n } else if (typeof segment === 'number') {\n result = `${result}[${segment}]`\n } else if (\n typeof segment === 'object' &&\n segment !== null &&\n '_key' in segment\n ) {\n result = `${result}[_key==\"${segment._key}\"]`\n } else {\n throw new Error(STRINGIFY_ERROR_MESSAGE)\n }\n }\n return result\n}\n\nfunction prefixPathExpression(prefix: string, expression: string): string {\n if (expression === '') {\n return prefix\n }\n return expression.startsWith('[')\n ? `${prefix}${expression}`\n : `${prefix}.${expression}`\n}\n\n/**\n * `SanityPatchOperations` from `@sanity/diff-patch` only covers the\n * operations `diffValue` emits; the editor can additionally produce these.\n */\ntype SanityPatchOperationsWithExtras = SanityPatchOperations & {\n setIfMissing?: {[path: string]: unknown}\n inc?: {[path: string]: number}\n dec?: {[path: string]: number}\n}\n\n/**\n * Converts Portable Text Editor patches into Sanity patch operations rooted\n * at the given document field path. Throws if a patch cannot be converted;\n * callers should fall back to pushing the whole value.\n *\n * @internal\n */\nexport function convertPatchesToSanity(\n patches: PtePatch[],\n options: {prefix: string},\n): SanityPatchOperationsWithExtras[] {\n return patches.map((patch): SanityPatchOperationsWithExtras => {\n const pathExpression = prefixPathExpression(\n options.prefix,\n stringifyPatchPath(patch.path),\n )\n\n switch (patch.type) {\n case 'set':\n return {set: {[pathExpression]: patch.value}}\n case 'setIfMissing':\n return {setIfMissing: {[pathExpression]: patch.value}}\n case 'unset':\n // The editor unsets the whole field when it becomes empty. Write an\n // empty array instead: unsetting the field would leave other clients\n // unable to reconcile against it (their remote value disappears).\n if (patch.path.length === 0) {\n return {set: {[pathExpression]: []}}\n }\n return {unset: [pathExpression]}\n case 'diffMatchPatch':\n return {diffMatchPatch: {[pathExpression]: patch.value}}\n case 'inc':\n return {inc: {[pathExpression]: patch.value as number}}\n case 'dec':\n return {dec: {[pathExpression]: patch.value as number}}\n case 'insert':\n return {\n insert: {\n [patch.position]: pathExpression,\n items: patch.items,\n } as SanityPatchOperations['insert'],\n }\n default:\n throw new Error(STRINGIFY_ERROR_MESSAGE)\n }\n })\n}\n\nfunction segmentsEqual(a: PathSegment, b: PathSegment): boolean {\n if (typeof a === 'string' || typeof a === 'number') {\n return a === b\n }\n if (typeof b === 'string' || typeof b === 'number') {\n return false\n }\n if (Array.isArray(a) || Array.isArray(b)) {\n return false\n }\n return a._key === b._key\n}\n\nfunction pathsEqual(a: Path, b: Path): boolean {\n return (\n a.length === b.length &&\n a.every((segment, index) => segmentsEqual(segment, b[index]))\n )\n}\n\n/**\n * The editor engine can only resolve keyed/indexed segments against the\n * root block array and (possibly nested) `children` arrays. Operations\n * that address items of any other array, e.g. a block's `markDefs` or a\n * span's `marks`, misapply. When a patch path enters such a sidecar\n * array, this returns the path of the array itself so callers can fall\n * back to replacing the whole property; returns `null` for paths the\n * engine can apply.\n *\n * @internal\n */\nexport function findSidecarArrayPath(path: Path): Path | null {\n // a keyed/numeric segment is only resolvable first (root block array)\n // or directly after a `children` property\n let expectNode = true\n for (let index = 0; index < path.length; index++) {\n const segment = path[index]\n if (typeof segment === 'string') {\n expectNode = segment === 'children'\n } else {\n if (!expectNode) {\n return path.slice(0, index)\n }\n expectNode = false\n }\n }\n return null\n}\n\nfunction getValueAtPath(value: JSONValue, path: Path): JSONValue | undefined {\n let current: JSONValue | undefined = value\n for (const segment of path) {\n if (current === null || current === undefined) {\n return undefined\n }\n if (typeof segment === 'number') {\n if (!Array.isArray(current)) {\n return undefined\n }\n current = current[segment < 0 ? current.length + segment : segment]\n } else if (typeof segment === 'string') {\n if (typeof current !== 'object' || Array.isArray(current)) {\n return undefined\n }\n current = (current as {[key: string]: JSONValue})[segment]\n } else if (Array.isArray(segment)) {\n // index tuples address ranges, not single values\n return undefined\n } else {\n if (!Array.isArray(current)) {\n return undefined\n }\n current = current.find(\n (item) =>\n typeof item === 'object' &&\n item !== null &&\n !Array.isArray(item) &&\n (item as {_key?: unknown})._key === segment._key,\n )\n }\n }\n return current\n}\n\n/**\n * Converts a target-value diff into patches the editor engine can apply.\n * Patches addressing items inside sidecar arrays are coalesced into whole\n * `set`s (or `unset`s) of the owning property, taken from the target\n * value.\n *\n * @internal\n */\nexport function toEngineSafePatches(\n patches: PtePatch[],\n targetValue: PortableTextBlock[],\n): PtePatch[] {\n const safe: PtePatch[] = []\n const sidecarPaths: Path[] = []\n\n for (const patch of patches) {\n const sidecarPath = findSidecarArrayPath(patch.path)\n if (!sidecarPath) {\n safe.push(patch)\n continue\n }\n if (!sidecarPaths.some((existing) => pathsEqual(existing, sidecarPath))) {\n sidecarPaths.push(sidecarPath)\n }\n }\n\n for (const sidecarPath of sidecarPaths) {\n const value = getValueAtPath(\n targetValue as unknown as JSONValue,\n sidecarPath,\n )\n safe.push(\n value === undefined\n ? {type: 'unset', path: sidecarPath, origin: 'remote'}\n : {type: 'set', path: sidecarPath, value, origin: 'remote'},\n )\n }\n\n return safe\n}\n\n/**\n * Drops insert items whose `_key` is absent from the remote value. A text\n * paste can stage a temporary span and remove it again within the same\n * transaction; applying the insert without the cleanup would flash the\n * staged content. Callers must only run this once the store value reflects\n * the transaction the patches belong to (see `'apply remote patches'`),\n * otherwise every legitimately new node would be dropped.\n */\nfunction filterInsertsMissingFromRemoteValue(\n patches: PtePatch[],\n remoteValue: PortableTextBlock[],\n): PtePatch[] {\n return patches.flatMap((patch): PtePatch[] => {\n if (patch.type !== 'insert') {\n return [patch]\n }\n\n const parentValue = getValueAtPath(\n remoteValue as unknown as JSONValue,\n patch.path.slice(0, -1),\n )\n if (!Array.isArray(parentValue)) {\n return [patch]\n }\n\n const items = patch.items.filter((item) => {\n const itemKey = getKey(item)\n if (!itemKey) {\n return true\n }\n return parentValue.some((candidate) => getKey(candidate) === itemKey)\n })\n\n return items.length > 0 ? [{...patch, items}] : []\n })\n}\n\nfunction getKey(value: JSONValue): string | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return undefined\n }\n const key = (value as {_key?: unknown})._key\n return typeof key === 'string' ? key : undefined\n}\n\n/**\n * The editor writes `markDefs` as whole-array `set`s. Two clients\n * formatting the same block concurrently then overwrite each other's\n * arrays at the server (last writer wins) while both clients' span\n * `marks` references survive, stranding marks without definitions.\n * Outgoing `markDefs` sets are therefore decomposed into item-level\n * operations against the store (server truth): new definitions insert,\n * changed definitions set by key, and removed definitions unset by key,\n * except definitions the store's own spans still reference (a diverged\n * client's normalizer prunes those spuriously; a later, converged flush\n * removes them for real). Item-keyed operations merge at the server\n * instead of overwriting.\n *\n * @internal\n */\nexport function toMergeableMarkDefsPatches(\n patches: PtePatch[],\n getCurrentValue: () => PortableTextBlock[] | null | undefined,\n): PtePatch[] {\n return patches.flatMap((patch): PtePatch[] => {\n if (\n patch.type !== 'set' ||\n patch.path.at(-1) !== 'markDefs' ||\n !Array.isArray(patch.value)\n ) {\n return [patch]\n }\n const currentValue = getCurrentValue()\n if (!currentValue) {\n return [patch]\n }\n const root = currentValue as unknown as JSONValue\n const storeMarkDefs = getValueAtPath(root, patch.path)\n const storeBlock = getValueAtPath(root, patch.path.slice(0, -1))\n if (\n !Array.isArray(storeMarkDefs) ||\n typeof storeBlock !== 'object' ||\n storeBlock === null\n ) {\n return [patch]\n }\n\n const local = patch.value as Array<{_key?: string}>\n const store = storeMarkDefs as Array<{_key?: string}>\n if (local.some((item) => item._key === undefined)) {\n return [patch]\n }\n\n const referencedKeys = new Set(\n (\n (storeBlock as {children?: Array<{marks?: string[]}>}).children ?? []\n ).flatMap((child) => child.marks ?? []),\n )\n const storeByKey = new Map(store.map((item) => [item._key, item]))\n const localKeys = new Set(local.map((item) => item._key))\n const origin = patch.origin\n\n const ops: PtePatch[] = []\n\n const inserted = local.filter((item) => !storeByKey.has(item._key))\n if (inserted.length > 0) {\n for (const item of inserted) {\n if (item._key === undefined) {\n continue\n }\n // The store view can wrongly lack an in-flight insert (a colliding\n // transaction makes the optimistic rebase fail silently), so a\n // re-send would stack a duplicate. The keyed unset makes the\n // insert an upsert: it is a no-op while the key is absent, and\n // the client's own transactions apply in order, so a re-send\n // self-cleans any earlier copy instead of duplicating it.\n ops.push({\n type: 'unset',\n origin,\n path: [...patch.path, {_key: item._key}],\n })\n }\n ops.push({\n type: 'insert',\n origin,\n position: 'after',\n path: [...patch.path, -1],\n items: inserted as JSONValue[],\n })\n }\n\n for (const item of local) {\n const existing = storeByKey.get(item._key)\n if (existing && JSON.stringify(existing) !== JSON.stringify(item)) {\n ops.push({\n type: 'set',\n origin,\n path: [...patch.path, {_key: item._key as string}],\n value: item as JSONValue,\n })\n }\n }\n\n for (const item of store) {\n if (\n item._key !== undefined &&\n !localKeys.has(item._key) &&\n !referencedKeys.has(item._key)\n ) {\n ops.push({\n type: 'unset',\n origin,\n path: [...patch.path, {_key: item._key}],\n })\n }\n }\n\n return ops\n })\n}\n\n/**\n * Whether a remote patch can resolve against the given editor value.\n * Concurrent edits routinely produce operations addressing nodes another\n * client has already removed or not yet created; sending those into the\n * engine fails loudly (console errors) before being skipped. Callers drop\n * unresolvable patches up front and rely on the follow-up repair sync to\n * converge instead.\n *\n * @internal\n */\nexport function canApplyToValue(\n patch: PtePatch,\n value: PortableTextBlock[] | undefined,\n): boolean {\n if (!value) {\n return true\n }\n const root = value as unknown as JSONValue\n switch (patch.type) {\n // unset needs the node itself; insert needs the sibling at `path`;\n // diffMatchPatch needs the existing string\n case 'unset':\n case 'insert':\n case 'diffMatchPatch':\n return getValueAtPath(root, patch.path) !== undefined\n // set creates its target property, so only the parent must resolve\n case 'set':\n return (\n patch.path.length === 0 ||\n getValueAtPath(root, patch.path.slice(0, -1)) !== undefined\n )\n default:\n return true\n }\n}\n\n/**\n * Filters a patch batch down to the patches that can resolve (see\n * `canApplyToValue`), checking each patch against the value as it stands\n * after the preceding patches applied. A transaction routinely inserts a\n * node and then addresses it, e.g. a span split followed by a `marks` set\n * on the new span, so checking every patch against the starting value\n * would drop valid operations.\n */\nfunction filterResolvablePatches(\n patches: PtePatch[],\n value: PortableTextBlock[] | undefined,\n): PtePatch[] {\n let projected = value\n const resolvable: PtePatch[] = []\n for (const patch of patches) {\n if (!canApplyToValue(patch, projected)) {\n continue\n }\n resolvable.push(patch)\n if (projected) {\n try {\n projected = applyAll(projected, [patch])\n } catch {\n // the engine applies best-effort too; keep the projection as-is\n }\n }\n }\n return resolvable\n}\n\n/**\n * Scopes document-rooted Sanity patches to the given field path, returning\n * field-relative Portable Text Editor patches. Patches outside the field are\n * dropped. Returns `null` when the field (or an ancestor of it) is replaced\n * wholesale, in which case the caller should fall back to a full value sync.\n * Throws when a path expression cannot be converted.\n *\n * @internal\n */\nexport function scopeRemotePatches(\n patches: SanityPatchOperations[],\n fieldPath: string,\n): PtePatch[] | null {\n const prefix = arrayifyPath(fieldPath)\n const converted = convertPatches(patches)\n const scoped: PtePatch[] = []\n\n for (const patch of converted) {\n const overlap = Math.min(patch.path.length, prefix.length)\n let touchesField = true\n for (let index = 0; index < overlap; index++) {\n if (!segmentsEqual(patch.path[index], prefix[index])) {\n touchesField = false\n break\n }\n }\n if (!touchesField) {\n continue\n }\n if (patch.path.length <= prefix.length) {\n // the patch targets the field itself or an ancestor of it, which\n // cannot be expressed as a field-relative operation\n return null\n }\n scoped.push({...patch, path: patch.path.slice(prefix.length)})\n }\n\n return scoped\n}\n\nfunction debugTextOf(value: unknown): string {\n if (!Array.isArray(value)) {\n return ''\n }\n return value\n .map((block) =>\n Array.isArray((block as {children?: unknown}).children)\n ? ((block as {children: Array<{text?: string}>}).children ?? [])\n .map((child) => child.text ?? '')\n .join('')\n : '',\n )\n .join('\\n')\n}\n\n/**\n * How long an editor-versus-store divergence must persist, unchanged,\n * before the whole-value repair acts on it. When a remote transaction\n * arrives interleaved with the listener echoes of this client's own recent\n * edits, the store value is transiently wrong until the echo returns and\n * the rebase corrects it. A repair fired inside that window copies the\n * garbage into the editor and a follow-up repair restores the text at a\n * drifted offset, scrambling words the user typed in the meantime. The\n * window therefore has to outlast a slow listener echo round trip; real\n * divergence is stable and loses nothing by being repaired a beat later.\n *\n * Tests use a short window: their mock stores are synchronous, so echo\n * transients cannot occur, and waiting the production interval would only\n * slow every repair assertion down.\n */\nconst REPAIR_CONFIRM_DELAY =\n // @ts-expect-error - dot notation required for Vite to replace at build time\n process.env.NODE_ENV === 'test' ? 150 : 1000\n\ntype PendingRepair = {signature: string; timer: ReturnType<typeof setTimeout>}\nconst pendingRepairs = new WeakMap<Editor, PendingRepair>()\n\n/**\n * Local edits the editor has emitted but not yet flushed into a mutation.\n * While any exist, the store necessarily lags the editor and a repair diff\n * would \"repair away\" the user's unflushed keystrokes, so repairs must wait.\n */\nconst unflushedEdits = new WeakMap<Editor, boolean>()\n\nfunction computeRepair(\n editor: Editor,\n remoteValue: PortableTextBlock[],\n): {patches: PtePatch[]; convertible: boolean; signature: string} {\n const snapshot = editor.getSnapshot().context.value\n // The signature covers the full (editor, store) state, not just the diff:\n // with repetitive text, two different transients can produce an identical\n // diff, and a repair must only act when the world actually stood still.\n const stateSignature = JSON.stringify([snapshot, remoteValue])\n try {\n const patches = toEngineSafePatches(\n convertPatches(diffValue(snapshot, remoteValue)),\n remoteValue,\n )\n return {patches, convertible: true, signature: stateSignature}\n } catch {\n // diffValue can emit path shapes the converter does not understand\n // (e.g. array slices when multiple items are dropped). The repair then\n // has to fall back to a whole-value update.\n return {patches: [], convertible: false, signature: `!${stateSignature}`}\n }\n}\n\nfunction cancelPendingRepair(editor: Editor) {\n const pending = pendingRepairs.get(editor)\n if (pending) {\n clearTimeout(pending.timer)\n pendingRepairs.delete(editor)\n }\n}\n\nfunction applySync({\n editor,\n getRemoteValue,\n}: {\n editor: Editor\n getRemoteValue: () => PortableTextBlock[] | null | undefined\n}) {\n const remoteValue = getRemoteValue()\n\n if (!remoteValue) {\n return\n }\n\n const first = computeRepair(editor, remoteValue)\n if (first.convertible && first.patches.length === 0) {\n cancelPendingRepair(editor)\n return\n }\n\n if (debug.repair.enabled) {\n debug.repair('editor and store texts diverged %o', {\n editorText: debugTextOf(editor.getSnapshot().context.value),\n remoteText: debugTextOf(remoteValue),\n })\n }\n\n // Same divergence already awaiting confirmation: let that timer decide.\n const pending = pendingRepairs.get(editor)\n if (pending && pending.signature === first.signature) {\n return\n }\n cancelPendingRepair(editor)\n\n const timer = setTimeout(() => {\n pendingRepairs.delete(editor)\n\n // Unflushed local keystrokes mean the store lags the editor by design;\n // a repair now would delete them. Re-arm and wait for the flush (the\n // divergence either disappears once the push round-trips, or persists\n // and gets repaired then).\n if (unflushedEdits.get(editor)) {\n applySync({editor, getRemoteValue})\n return\n }\n\n // Recompute from scratch: the store may have corrected itself (the\n // transient case) or the user may have typed (their edits will push\n // and reconverge through the normal flow); only a divergence that is\n // still byte-for-byte identical is real and safe to repair.\n const latestRemote = getRemoteValue()\n if (!latestRemote) {\n return\n }\n const second = computeRepair(editor, latestRemote)\n if (second.convertible && second.patches.length === 0) {\n return\n }\n if (second.signature !== first.signature) {\n // Still diverged but differently: re-arm so a stable state eventually\n // confirms. Transients converge to the empty diff; genuine divergence\n // stabilizes to a fixed signature within one flush cycle.\n applySync({editor, getRemoteValue})\n return\n }\n\n if (!second.convertible) {\n debug.repair('escalating to whole-value sync (unconvertible diff)')\n editor.send({type: 'update value', value: latestRemote})\n return\n }\n\n const snapshot = editor.getSnapshot().context.value\n debug.repair('applying confirmed repair patches %o', second.patches)\n editor.send({type: 'patches', patches: second.patches, snapshot})\n\n // Patch application is best-effort: the editor skips operations it\n // cannot resolve against its current tree, and concurrent edits to the\n // same range produce keyed operations whose targets no longer exist\n // locally. When the editor is still diverged after the diff-based\n // repair, escalate to the full value sync machinery, which reconciles\n // arbitrary divergence block by block.\n const valueAfterPatches = editor.getSnapshot().context.value\n if (diffValue(valueAfterPatches, latestRemote).length > 0) {\n if (debug.repair.enabled) {\n debug.repair('escalating to whole-value sync %o', {\n editorText: debugTextOf(valueAfterPatches),\n remoteText: debugTextOf(latestRemote),\n })\n }\n editor.send({type: 'update value', value: latestRemote})\n }\n }, REPAIR_CONFIRM_DELAY)\n\n pendingRepairs.set(editor, {signature: first.signature, timer})\n}\n\nconst listenToEditor = fromCallback<AnyEventObject, {editor: Editor}>(\n ({sendBack, input}) => {\n const patchSubscription = input.editor.on('patch', () => {\n // Every 'patch' event is a local edit (remote application suppresses\n // patch generation), so the store now lags the editor until the next\n // mutation flush.\n unflushedEdits.set(input.editor, true)\n sendBack({type: 'patch emitted'})\n })\n\n const mutationSubscription = input.editor.on('mutation', (event) => {\n unflushedEdits.set(input.editor, false)\n if (debug.mutation.enabled) {\n debug.mutation('flushed %o', {\n flushText: debugTextOf(event.value),\n snapshotText: debugTextOf(input.editor.getSnapshot().context.value),\n })\n }\n sendBack({\n type: 'mutation flushed',\n value: event.value,\n patches: event.patches,\n })\n })\n\n return () => {\n patchSubscription.unsubscribe()\n mutationSubscription.unsubscribe()\n }\n },\n)\n\nconst listenToRemote = fromCallback<\n AnyEventObject,\n {onRemoteValueChange: ValueSyncConfig['onRemoteValueChange']}\n>(({sendBack, input}) => {\n return input.onRemoteValueChange(() => {\n sendBack({type: 'remote value changed'})\n })\n})\n\nconst listenToRemotePatches = fromCallback<\n AnyEventObject,\n {onRemotePatches: ValueSyncConfig['onRemotePatches']}\n>(({sendBack, input}) => {\n return input.onRemotePatches?.((patches) => {\n sendBack({type: 'remote patches received', patches})\n })\n})\n\n/**\n * How long the machine must sit in 'idle' (no local keystrokes, no store\n * events) before the one-shot whole-value repair runs. Long enough for the\n * editor's external value snapshot to catch up after the last flush; short\n * enough that residual divergence from concurrent editing heals promptly.\n */\nconst QUIESCENT_REPAIR_DELAY = 500\n\nconst valueSyncMachine = setup({\n types: {\n context: {} as {\n editor: Editor\n getRemoteValue: ValueSyncConfig['getRemoteValue']\n onRemoteValueChange: ValueSyncConfig['onRemoteValueChange']\n onRemotePatches: ValueSyncConfig['onRemotePatches']\n },\n input: {} as {\n editor: Editor\n getRemoteValue: ValueSyncConfig['getRemoteValue']\n onRemoteValueChange: ValueSyncConfig['onRemoteValueChange']\n onRemotePatches: ValueSyncConfig['onRemotePatches']\n },\n events: {} as\n | {type: 'patch emitted'}\n | {\n type: 'mutation flushed'\n value: PortableTextBlock[] | undefined\n patches: PtePatch[]\n }\n | {type: 'remote value changed'}\n | {type: 'remote patches received'; patches: PtePatch[]},\n },\n actions: {\n 'send initial value': ({context}) => {\n context.editor.send({\n type: 'update value',\n value: context.getRemoteValue() ?? [],\n })\n },\n 'push to remote': () => {\n throw new Error('push to remote must be provided via .provide()')\n },\n 'apply sync': ({context}) => {\n applySync({\n editor: context.editor,\n getRemoteValue: context.getRemoteValue,\n })\n },\n 'apply remote patches': ({context, event}) => {\n if (event.type !== 'remote patches received') {\n return\n }\n debug.remote('applying remote patches %o', event.patches)\n // The SDK emits `remote-patches` while it is still computing the\n // state update for that transaction, so the store value visible at\n // this moment does not include it yet. The commit lands within the\n // same synchronous task, so after one microtask the store reflects\n // the transaction, whether the event announced a fresh transaction\n // or replayed one the store already had. Only then can the store\n // value serve as the reference for dropping inserts the transaction\n // itself cleaned up again, and as the target for coalescing\n // sidecar-array item operations (which the engine misroutes) into\n // whole-property sets.\n queueMicrotask(() => {\n const snapshot = context.editor.getSnapshot().context.value\n const remoteValue = context.getRemoteValue()\n const coalesced = remoteValue\n ? toEngineSafePatches(event.patches, remoteValue)\n : event.patches\n const patches = filterResolvablePatches(\n remoteValue\n ? filterInsertsMissingFromRemoteValue(coalesced, remoteValue)\n : coalesced,\n snapshot,\n )\n if (patches.length === 0) {\n return\n }\n context.editor.send({type: 'patches', patches, snapshot})\n })\n },\n },\n actors: {\n 'listen to editor': listenToEditor,\n 'listen to remote': listenToRemote,\n 'listen to remote patches': listenToRemotePatches,\n },\n}).createMachine({\n id: 'value sync',\n context: ({input}) => ({\n editor: input.editor,\n getRemoteValue: input.getRemoteValue,\n onRemoteValueChange: input.onRemoteValueChange,\n onRemotePatches: input.onRemotePatches,\n }),\n entry: ['send initial value'],\n invoke: [\n {\n src: 'listen to editor',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'listen to remote',\n input: ({context}) => ({\n onRemoteValueChange: context.onRemoteValueChange,\n }),\n },\n {\n src: 'listen to remote patches',\n input: ({context}) => ({\n onRemotePatches: context.onRemotePatches,\n }),\n },\n ],\n // Two invariants, learned the hard way:\n //\n // 1. EVERY 'mutation flushed' event must be pushed, in every state. The\n // editor emits mutation events in bursts (one per input batch, e.g.\n // each backspace of a quick delete), and any state without a handler\n // silently drops the flush. A dropped flush permanently diverges the\n // store from the editor, and the whole-value repair then \"heals\" the\n // editor backwards, resurrecting deleted text.\n // 2. The whole-value repair must only run when no local edits can be in\n // flight (quiescent 'idle'). Running it mid-typing diffs the editor\n // against a store that lags the user's keystrokes and stomps them.\n //\n // Operational patches from other clients still apply immediately in\n // every state; the editor merges them with in-flight local changes.\n initial: 'idle',\n states: {\n 'idle': {\n // One-shot repair after a quiet period. Covers divergence left by\n // best-effort patch application while local edits were in flight\n // (those states never repair; see below) when no further store\n // event arrives to trigger the on-change repair.\n after: {\n [QUIESCENT_REPAIR_DELAY]: {\n actions: ['apply sync'],\n },\n },\n on: {\n 'patch emitted': {\n target: 'local write',\n },\n // Mutation events arrive in bursts (one per input batch), so a\n // flush can land after a sibling flush already advanced the state.\n 'mutation flushed': {\n target: 'pushing to remote',\n actions: ['push to remote'],\n },\n 'remote value changed': {\n actions: ['apply sync'],\n },\n // No immediate repair here: every remote patch batch also updates\n // the store value, so the accompanying 'remote value changed'\n // event runs the whole-value repair right after.\n 'remote patches received': {\n actions: ['apply remote patches'],\n },\n },\n },\n 'local write': {\n on: {\n 'patch emitted': {},\n 'mutation flushed': {\n target: 'pushing to remote',\n actions: ['push to remote'],\n },\n 'remote value changed': {\n target: 'pending sync',\n },\n 'remote patches received': {\n target: 'pending sync',\n actions: ['apply remote patches'],\n },\n },\n },\n 'pushing to remote': {\n on: {\n 'patch emitted': {\n target: 'local write',\n },\n 'mutation flushed': {\n actions: ['push to remote'],\n },\n // No repair on the push acknowledgment: the editor snapshot can\n // lag the live document while the user keeps typing, so a diff\n // against the store here resurrects deleted text and duplicates\n // in-flight keystrokes. Once truly idle, the store change or the\n // quiescent delay runs the repair with a caught-up snapshot.\n 'remote value changed': {\n target: 'idle',\n },\n 'remote patches received': {\n actions: ['apply remote patches'],\n },\n },\n },\n 'pending sync': {\n on: {\n 'patch emitted': {},\n 'mutation flushed': {\n target: 'pushing to remote',\n actions: ['push to remote'],\n },\n 'remote value changed': {},\n 'remote patches received': {\n actions: ['apply remote patches'],\n },\n },\n },\n },\n})\n\ninterface SDKValuePluginProps extends DocumentHandle {\n source?: DocumentResource\n path: string\n}\n\n/**\n * The shape of the `remote-patches` document event emitted by\n * `@sanity/sdk` >= 2.17. Declared structurally so the plugin stays\n * compatible with older SDK typings until its dependency is bumped.\n */\ntype RemotePatchesDocumentEvent = {\n type: 'remote-patches'\n documentId: string\n transactionId: string\n timestamp: string\n previousRev?: string\n patches: SanityPatchOperations[]\n origin: 'local' | 'remote'\n}\n\nfunction isRemotePatchesEvent(event: {\n type: string\n}): event is RemotePatchesDocumentEvent {\n return event.type === 'remote-patches'\n}\n\nfunction getPublishedDocumentId(id: string): string {\n if (id.startsWith('drafts.')) {\n return id.slice('drafts.'.length)\n }\n if (id.startsWith('versions.')) {\n return id.split('.').slice(2).join('.')\n }\n return id\n}\n\n/**\n * @public\n */\nexport function SDKValuePlugin(props: SDKValuePluginProps) {\n const normalizedProps = normalizeDocumentHandle(props)\n const {documentId, documentType, path} = normalizedProps\n const setSdkValue = useEditDocument(normalizedProps)\n const instance = useSanityInstance()\n const applyActions = useApplyDocumentActions()\n\n const handle = {documentId, documentType, path}\n const {getCurrent, subscribe} = getDocumentState<PortableTextBlock[]>(\n instance,\n handle,\n )\n\n const onRemotePatches = useCallback(\n (callback: (patches: PtePatch[]) => void) => {\n return subscribeDocumentEvents(instance, {\n eventHandler: (event) => {\n // widen before narrowing: `remote-patches` is not part of the\n // `DocumentEvent` union in older SDK typings\n const candidate: {type: string} = event\n if (!isRemotePatchesEvent(candidate)) {\n return\n }\n // our own transactions are already reflected in the editor\n if (candidate.origin !== 'remote') {\n return\n }\n if (\n getPublishedDocumentId(candidate.documentId) !==\n getPublishedDocumentId(documentId)\n ) {\n return\n }\n\n let patches: PtePatch[] | null\n try {\n patches = scopeRemotePatches(candidate.patches, path)\n } catch {\n // unconvertible patch shapes fall back to the whole-value sync\n // driven by `onRemoteValueChange`\n return\n }\n if (patches && patches.length > 0) {\n callback(patches)\n }\n },\n })\n },\n [instance, documentId, path],\n )\n\n const pushPatches = useCallback(\n (patches: PtePatch[]) => {\n const sanityPatches = convertPatchesToSanity(patches, {prefix: path})\n // `preserveOperations` ships in @sanity/sdk >= 2.17; the intersection\n // type keeps the plugin compatible with older SDK typings\n const action: EditDocumentAction & {preserveOperations?: boolean} = {\n ...editDocument(\n {documentId, documentType},\n sanityPatches as Parameters<typeof editDocument>[1],\n ),\n preserveOperations: true,\n }\n applyActions(action)\n },\n [applyActions, documentId, documentType, path],\n )\n\n return (\n <ValueSyncPlugin\n getRemoteValue={getCurrent}\n pushValue={setSdkValue}\n onRemoteValueChange={subscribe}\n onRemotePatches={onRemotePatches}\n pushPatches={pushPatches}\n />\n )\n}\n\n/**\n * @internal\n */\ntype ValueSyncConfig = {\n getRemoteValue: () => PortableTextBlock[] | null | undefined\n pushValue: (value: PortableTextBlock[]) => void\n onRemoteValueChange: (callback: () => void) => () => void\n /**\n * Optional patch channel. When provided, operational patches from other\n * clients are applied directly to the editor (in every state) instead of\n * waiting for a whole-value diff, and local editor patches are pushed\n * through `pushPatches`. The whole-value sync remains as a fallback for\n * anything the patch channel cannot express.\n */\n onRemotePatches?: (\n callback: (patches: PtePatch[]) => void,\n ) => (() => void) | undefined\n /**\n * Pushes the editor's own operational patches to the remote store. May\n * throw when a patch cannot be converted, in which case the plugin falls\n * back to pushing the whole value.\n */\n pushPatches?: (patches: PtePatch[]) => void\n}\n\n/**\n * NOTE: You are probably looking for SDKValuePlugin instead of this.\n * This is a lower-level plugin that only handles syncing the value\n * between the editor and a remote source. It does not know anything\n * about Sanity documents or how to fetch/update them.\n *\n * May be removed in the future, do not rely on this directly.\n *\n * @internal\n */\nexport function ValueSyncPlugin(props: ValueSyncConfig) {\n const {\n getRemoteValue,\n pushValue,\n onRemoteValueChange,\n onRemotePatches,\n pushPatches,\n } = props\n const editor = useEditor()\n\n useActorRef(\n valueSyncMachine.provide({\n actions: {\n 'push to remote': ({context, event}) => {\n if (event.type !== 'mutation flushed') {\n return\n }\n\n if (pushPatches && event.patches.length > 0) {\n try {\n const mergeable = toMergeableMarkDefsPatches(\n event.patches,\n context.getRemoteValue,\n )\n debug.push('pushing patches %o', mergeable)\n pushPatches(mergeable)\n return\n } catch {\n // fall back to pushing the whole value below\n }\n }\n\n if (debug.push.enabled) {\n debug.push(\n 'pushing whole value %s',\n debugTextOf(\n event.value ?? context.editor.getSnapshot().context.value,\n ),\n )\n }\n pushValue(event.value ?? context.editor.getSnapshot().context.value)\n },\n },\n }),\n {\n input: {\n editor,\n getRemoteValue,\n onRemoteValueChange,\n onRemotePatches,\n },\n },\n )\n\n return null\n}\n","import type {EditorSelection, RangeDecoration} from '@portabletext/editor'\nimport {\n usePresenceForDocument,\n useReportPresence,\n type DocumentHandle,\n type DocumentResource,\n type UseReportPresenceOptions,\n} from '@sanity/sdk-react'\nimport {useMemo, type PropsWithChildren, type ReactElement} from 'react'\nimport {useLocalSelection, useRemoteCursors} from './plugin.presence-sync'\nimport {arrayifyPath} from './plugin.sdk-value'\nimport {normalizeDocumentHandle} from './sdk-document-handle'\n\n/**\n * `@sanity/sdk-react` exports the presence hooks and their option types but not\n * the presence data types, so these are derived from what it does export.\n */\ntype DocumentPresence = ReturnType<\n typeof usePresenceForDocument\n>['presence'][number]\n\n/**\n * Draws one remote participant's caret. This package has no opinion about how a\n * caret looks, so it is the caller's to provide.\n *\n * @public\n */\nexport type RenderCursorFunction = (\n cursor: SDKRemoteCursor,\n) => (props: PropsWithChildren) => ReactElement\n\n/**\n * A remote participant's caret, together with who they are.\n *\n * Declared in full rather than extending the internal cursor type, so nothing in\n * the public API refers to a type consumers cannot import.\n *\n * @public\n */\nexport interface SDKRemoteCursor {\n /**\n * Identifies one editing session rather than one person. The same user in two\n * tabs reports two sessions and therefore draws two carets.\n */\n sessionId: string\n /**\n * Where the participant last reported their selection.\n */\n selection: EditorSelection\n user: DocumentPresence['user']\n}\n\n/**\n * Props for {@link SDKPresencePlugin}.\n *\n * @public\n */\nexport interface SDKPresencePluginProps extends DocumentHandle {\n /**\n * @deprecated Use `resource` instead.\n */\n source?: DocumentResource\n /**\n * The document path of the Portable Text field, for example `content`. The\n * same form `SDKValuePlugin` takes.\n */\n path: string\n}\n\n/**\n * Options for {@link useSDKPresenceCursors}.\n *\n * @public\n */\nexport interface UseSDKPresenceCursorsOptions extends DocumentHandle {\n /**\n * @deprecated Use `resource` instead.\n */\n source?: DocumentResource\n path: string\n renderCursor: RenderCursorFunction\n}\n\n/**\n * Reports the local user's caret in a Portable Text field, so other people in\n * the same document can see where they are.\n *\n * Place it inside `EditorProvider`. Prefer `SDKPortableTextEditable`, which\n * reports and draws in one component; reach for this when you render\n * `PortableTextEditable` yourself.\n *\n * Which document is reported follows the handle's perspective, or the ambient\n * one from `ResourceProvider`. Pass the plain document id and let the\n * perspective select the draft, the published document, or a release version.\n *\n * @public\n */\nexport function SDKPresencePlugin(props: SDKPresencePluginProps) {\n const {path, ...handle} = normalizeDocumentHandle(props)\n const selection = useLocalSelection()\n const fieldPath = useFieldPath(path)\n\n useReportPresence({...handle, path: fieldPath, selection})\n\n return null\n}\n\n/**\n * Other people's carets in a Portable Text field, as range decorations.\n *\n * Pass the result to `<PortableTextEditable rangeDecorations={...} />`, or use\n * `SDKPortableTextEditable` and skip the wiring. Each caret stays anchored as\n * the local user types, and disappears when the participant leaves or their\n * session expires.\n *\n * The local user is never included, so an app does not draw its own caret.\n * Participants are counted by session, so the same person in two tabs draws two\n * carets.\n *\n * @public\n */\nexport function useSDKPresenceCursors(\n options: UseSDKPresenceCursorsOptions,\n): RangeDecoration[] {\n const {path, renderCursor, ...handle} = normalizeDocumentHandle(options)\n const fieldPath = useFieldPath(path)\n // Exact id matching: a caret reported while editing a draft must not be drawn\n // in a release version of the same document, where the text differs.\n const {presence} = usePresenceForDocument({\n ...handle,\n path: fieldPath,\n excludeVersions: true,\n })\n\n const cursors = useMemo(\n () =>\n presence.flatMap((participant): SDKRemoteCursor[] =>\n participant.selection\n ? [\n {\n sessionId: participant.sessionId,\n selection: participant.selection,\n user: participant.user,\n },\n ]\n : [],\n ),\n [presence],\n )\n\n return useRemoteCursors({cursors, renderCursor})\n}\n\n/**\n * The SDK's presence hooks address fields by path array, because a field-level\n * path inside Portable Text needs keyed segments. This package takes the same\n * string expression as `SDKValuePlugin` everywhere and converts here.\n */\nfunction useFieldPath(\n path: string,\n): NonNullable<UseReportPresenceOptions['path']> {\n return useMemo(() => arrayifyPath(path), [path])\n}\n","import type {PropsWithChildren} from 'react'\nimport type {RenderCursorFunction, SDKRemoteCursor} from './plugin.sdk-presence'\n\n/**\n * Mid-tone hues, so a caret stays legible whether the app is light or dark. This\n * package cannot read the app's theme, so it does not try.\n */\nconst CARET_COLORS = [\n '#e0508a',\n '#c05fd8',\n '#7c66e8',\n '#2f8fdd',\n '#1f9c8f',\n '#4f9c2f',\n '#c98a1c',\n '#d4603a',\n]\n\nconst DOT_SIZE = 6\n\n/**\n * Picks a stable colour for a participant.\n *\n * Keyed on the user rather than the session, so one person in two tabs draws two\n * carets in the same colour. The Studio colours by user for the same reason.\n *\n * @public\n */\nexport function getCaretColor(userId: string): string {\n let hash = 0\n for (let index = 0; index < userId.length; index++) {\n hash = (hash * 31 + userId.charCodeAt(index)) % 1000003\n }\n return CARET_COLORS[hash % CARET_COLORS.length]\n}\n\n/**\n * Draws a remote caret when no `renderCursor` was given: a coloured line with a\n * dot above it, and the participant's name on hover.\n *\n * Deliberately plain, and styled inline so it needs no stylesheet. Pass your own\n * `renderCursor` to match your design.\n *\n * @public\n */\nexport const renderDefaultCursor: RenderCursorFunction =\n (cursor) => (props) => (\n <DefaultCaret cursor={cursor}>{props.children}</DefaultCaret>\n )\n\nfunction DefaultCaret(props: PropsWithChildren<{cursor: SDKRemoteCursor}>) {\n const {cursor, children} = props\n const color = getCaretColor(cursor.user.sanityUserId)\n const displayName = cursor.user.profile.displayName\n\n return (\n <>\n <span\n // Without this the caret becomes editable content and the local user can\n // put their own cursor inside it.\n contentEditable={false}\n data-testid={`presence-caret-${cursor.sessionId}`}\n style={{\n borderLeft: `2px solid ${color}`,\n marginLeft: -1,\n position: 'relative',\n // The line must not swallow clicks meant for the text under it.\n pointerEvents: 'none',\n }}\n >\n <span\n data-testid={`presence-caret-dot-${cursor.sessionId}`}\n style={{\n backgroundColor: color,\n borderRadius: '50%',\n height: DOT_SIZE,\n left: -1,\n pointerEvents: 'auto',\n position: 'absolute',\n top: -(DOT_SIZE - 1),\n transform: 'translateX(-50%)',\n width: DOT_SIZE,\n }}\n title={displayName}\n />\n </span>\n {children}\n </>\n )\n}\n","import {\n PortableTextEditable,\n type PortableTextEditableProps,\n type RangeDecoration,\n} from '@portabletext/editor'\nimport type {DocumentHandle, DocumentResource} from '@sanity/sdk-react'\nimport {useMemo} from 'react'\nimport {\n SDKPresencePlugin,\n useSDKPresenceCursors,\n type RenderCursorFunction,\n} from './plugin.sdk-presence'\nimport {SDKValuePlugin} from './plugin.sdk-value'\nimport {renderDefaultCursor} from './presence-caret'\nimport {normalizeDocumentHandle} from './sdk-document-handle'\n\n/**\n * Props for {@link SDKPortableTextEditable}.\n *\n * @public\n */\nexport interface SDKPortableTextEditableProps\n extends\n DocumentHandle,\n // `resource` is both a document handle field and an RDFa HTML attribute, so\n // the handle wins. Dropping the attribute costs nothing in an editor.\n Omit<PortableTextEditableProps, keyof DocumentHandle> {\n /**\n * @deprecated Use `resource` instead.\n */\n source?: DocumentResource\n /**\n * The document path of the Portable Text field, for example `content`.\n */\n path: string\n /**\n * Draws one remote participant's caret. Omit it for the built-in caret, which\n * needs no styling of your own. Pass `null` to draw no carets at all while\n * still reporting the local user's presence.\n */\n renderCursor?: RenderCursorFunction | null\n}\n\n/**\n * What {@link splitEditableProps} pulls apart.\n *\n * @internal\n */\nexport interface SplitEditableProps {\n handle: DocumentHandle\n path: string\n renderCursor: RenderCursorFunction | null | undefined\n rangeDecorations: RangeDecoration[] | undefined\n editableProps: Omit<\n PortableTextEditableProps,\n keyof DocumentHandle | 'rangeDecorations'\n >\n}\n\n/**\n * A `PortableTextEditable` wired to a Sanity document: the field's value syncs\n * both ways, the local user's caret is reported, and other people's carets are\n * drawn.\n *\n * Place it inside `EditorProvider` in place of `PortableTextEditable`. Every\n * other prop is forwarded untouched, and `rangeDecorations` you pass are kept\n * and merged with the presence carets rather than replaced.\n *\n * Nothing else is needed: this replaces a separate `SDKValuePlugin`.\n *\n * @example\n * ```tsx\n * <EditorProvider initialConfig={{schemaDefinition}}>\n * <SDKPortableTextEditable\n * {...documentHandle}\n * path=\"content\"\n * renderCursor={({user}) => (props) => (\n * <Caret user={user}>{props.children}</Caret>\n * )}\n * />\n * </EditorProvider>\n * ```\n *\n * @public\n */\nexport function SDKPortableTextEditable(props: SDKPortableTextEditableProps) {\n const {handle, path, renderCursor, rangeDecorations, editableProps} =\n splitEditableProps(props)\n\n const renderer = resolveCursorRenderer(renderCursor)\n\n const cursors = useSDKPresenceCursors({\n ...handle,\n path,\n renderCursor: renderer.renderCursor,\n })\n\n const decorations = useMemo(\n () =>\n mergePresenceDecorations(rangeDecorations, cursors, renderer.drawCursors),\n [cursors, rangeDecorations, renderer.drawCursors],\n )\n\n return (\n <>\n <PortableTextEditable {...editableProps} rangeDecorations={decorations} />\n <SDKValuePlugin {...handle} path={path} />\n <SDKPresencePlugin {...handle} path={path} />\n </>\n )\n}\n\n/**\n * Splits the props into the document handle, this component's own props, and\n * what is forwarded to `PortableTextEditable`. Keeping handle fields out of the\n * forwarded set is what stops them reaching the DOM as attributes.\n *\n * @internal\n */\nexport function splitEditableProps(\n props: SDKPortableTextEditableProps,\n): SplitEditableProps {\n const normalizedProps = normalizeDocumentHandle(props)\n const {\n documentId,\n documentType,\n projectId,\n dataset,\n resource,\n resourceName,\n liveEdit,\n perspective,\n path,\n renderCursor,\n rangeDecorations,\n ...editableProps\n } = normalizedProps\n\n return {\n // Only the fields the caller actually passed. The SDK resolves an ambient\n // perspective and resource from context with `Object.hasOwn`, so forwarding\n // `perspective: undefined` would override what `ResourceProvider` set rather\n // than defer to it, and the field would sync and report against the draft\n // instead of the release the app is showing.\n handle: {\n documentId,\n documentType,\n ...('projectId' in props && {projectId}),\n ...('dataset' in props && {dataset}),\n ...('resource' in normalizedProps && {resource}),\n ...('resourceName' in props && {resourceName}),\n ...('liveEdit' in props && {liveEdit}),\n ...('perspective' in props && {perspective}),\n },\n path,\n renderCursor,\n rangeDecorations,\n editableProps,\n }\n}\n\n/**\n * Decides which caret component to draw with, and whether to draw at all.\n *\n * Presence is subscribed to either way, because hooks cannot be called\n * conditionally, so switching carets off discards the decorations rather than\n * skipping the work.\n *\n * @internal\n */\nexport function resolveCursorRenderer(\n renderCursor: RenderCursorFunction | null | undefined,\n): {renderCursor: RenderCursorFunction; drawCursors: boolean} {\n return {\n renderCursor: renderCursor ?? renderDefaultCursor,\n drawCursors: renderCursor !== null,\n }\n}\n\n/**\n * Appends the presence carets to whatever decorations the caller passed, so\n * theirs survive. The Studio merges the same way. When carets are switched off\n * the caller's own decorations pass straight through.\n *\n * @internal\n */\nexport function mergePresenceDecorations(\n rangeDecorations: RangeDecoration[] | undefined,\n cursors: RangeDecoration[],\n drawCursors: boolean,\n): RangeDecoration[] | undefined {\n if (!drawCursors) {\n return rangeDecorations\n }\n return [...(rangeDecorations ?? []), ...cursors]\n}\n\n/**\n * Splitting the props by hand is what keeps document handle fields off the DOM,\n * so every field has to be listed above. `sdk-editable.test.ts` fails to compile\n * if `DocumentHandle` gains one. Note that `@sanity/sdk-react` adds fields to the\n * core handle, so it has to be read from there.\n */\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["(cursor: TCursor, selection: EditorSelection) => {\n setOverrides((previous) =>\n recordCursorMove(previous, cursors, cursor, selection),\n )\n }","useCallback(\n (cursor: TCursor, selection: EditorSelection) => {\n setOverrides((previous) =>\n recordCursorMove(previous, cursors, cursor, selection),\n )\n },\n [cursors],\n )","toRangeDecorations({cursors, overrides, renderCursor, onCursorMoved})","useMemo(\n () => toRangeDecorations({cursors, overrides, renderCursor, onCursorMoved}),\n [cursors, overrides, renderCursor, onCursorMoved],\n )","(callback: (patches: PtePatch[]) => void) => {\n return subscribeDocumentEvents(instance, {\n eventHandler: (event) => {\n // widen before narrowing: `remote-patches` is not part of the\n // `DocumentEvent` union in older SDK typings\n const candidate: {type: string} = event\n if (!isRemotePatchesEvent(candidate)) {\n return\n }\n // our own transactions are already reflected in the editor\n if (candidate.origin !== 'remote') {\n return\n }\n if (\n getPublishedDocumentId(candidate.documentId) !==\n getPublishedDocumentId(documentId)\n ) {\n return\n }\n\n let patches: PtePatch[] | null\n try {\n patches = scopeRemotePatches(candidate.patches, path)\n } catch {\n // unconvertible patch shapes fall back to the whole-value sync\n // driven by `onRemoteValueChange`\n return\n }\n if (patches && patches.length > 0) {\n callback(patches)\n }\n },\n })\n }","useCallback(\n (callback: (patches: PtePatch[]) => void) => {\n return subscribeDocumentEvents(instance, {\n eventHandler: (event) => {\n // widen before narrowing: `remote-patches` is not part of the\n // `DocumentEvent` union in older SDK typings\n const candidate: {type: string} = event\n if (!isRemotePatchesEvent(candidate)) {\n return\n }\n // our own transactions are already reflected in the editor\n if (candidate.origin !== 'remote') {\n return\n }\n if (\n getPublishedDocumentId(candidate.documentId) !==\n getPublishedDocumentId(documentId)\n ) {\n return\n }\n\n let patches: PtePatch[] | null\n try {\n patches = scopeRemotePatches(candidate.patches, path)\n } catch {\n // unconvertible patch shapes fall back to the whole-value sync\n // driven by `onRemoteValueChange`\n return\n }\n if (patches && patches.length > 0) {\n callback(patches)\n }\n },\n })\n },\n [instance, documentId, path],\n )","(patches: PtePatch[]) => {\n const sanityPatches = convertPatchesToSanity(patches, {prefix: path})\n // `preserveOperations` ships in @sanity/sdk >= 2.17; the intersection\n // type keeps the plugin compatible with older SDK typings\n const action: EditDocumentAction & {preserveOperations?: boolean} = {\n ...editDocument(\n {documentId, documentType},\n sanityPatches as Parameters<typeof editDocument>[1],\n ),\n preserveOperations: true,\n }\n applyActions(action)\n }","patches","useCallback(\n (patches: PtePatch[]) => {\n const sanityPatches = convertPatchesToSanity(patches, {prefix: path})\n // `preserveOperations` ships in @sanity/sdk >= 2.17; the intersection\n // type keeps the plugin compatible with older SDK typings\n const action: EditDocumentAction & {preserveOperations?: boolean} = {\n ...editDocument(\n {documentId, documentType},\n sanityPatches as Parameters<typeof editDocument>[1],\n ),\n preserveOperations: true,\n }\n applyActions(action)\n },\n [applyActions, documentId, documentType, path],\n )","<ValueSyncPlugin\n getRemoteValue={getCurrent}\n pushValue={setSdkValue}\n onRemoteValueChange={subscribe}\n onRemotePatches={onRemotePatches}\n pushPatches={pushPatches}\n />","valueSyncMachine.provide({\n actions: {\n 'push to remote': ({context, event}) => {\n if (event.type !== 'mutation flushed') {\n return\n }\n\n if (pushPatches && event.patches.length > 0) {\n try {\n const mergeable = toMergeableMarkDefsPatches(\n event.patches,\n context.getRemoteValue,\n )\n debug.push('pushing patches %o', mergeable)\n pushPatches(mergeable)\n return\n } catch {\n // fall back to pushing the whole value below\n }\n }\n\n if (debug.push.enabled) {\n debug.push(\n 'pushing whole value %s',\n debugTextOf(\n event.value ?? context.editor.getSnapshot().context.value,\n ),\n )\n }\n pushValue(event.value ?? context.editor.getSnapshot().context.value)\n },\n },\n })","{context, event}","{\n input: {\n editor,\n getRemoteValue,\n onRemoteValueChange,\n onRemotePatches,\n },\n }","normalizeDocumentHandle(props)","{...handle, path: fieldPath, selection}","normalizeDocumentHandle(options)","{\n ...handle,\n path: fieldPath,\n excludeVersions: true,\n }","presence.flatMap((participant): SDKRemoteCursor[] =>\n participant.selection\n ? [\n {\n sessionId: participant.sessionId,\n selection: participant.selection,\n user: participant.user,\n },\n ]\n : [],\n )","(participant): SDKRemoteCursor[] =>\n participant.selection\n ? [\n {\n sessionId: participant.sessionId,\n selection: participant.selection,\n user: participant.user,\n },\n ]\n : []","useMemo(\n () =>\n presence.flatMap((participant): SDKRemoteCursor[] =>\n participant.selection\n ? [\n {\n sessionId: participant.sessionId,\n selection: participant.selection,\n user: participant.user,\n },\n ]\n : [],\n ),\n [presence],\n )","{cursors, renderCursor}","arrayifyPath(path)","useMemo(() => arrayifyPath(path), [path])","{...handle}","comments.flatMap((comment) => {\n if (\n comment.parentCommentId ||\n !comment.selection ||\n comment.status !== 'open'\n ) {\n return []\n }\n const relativePath = relativeCommentPath(basePath, comment.fieldPath)\n if (relativePath === undefined) {\n return []\n }\n return [{comment, relativePath, selection: comment.selection}]\n })","useMemo(() => {\n const basePath = arrayifyPath(path)\n return comments.flatMap((comment) => {\n if (\n comment.parentCommentId ||\n !comment.selection ||\n comment.status !== 'open'\n ) {\n return []\n }\n const relativePath = relativeCommentPath(basePath, comment.fieldPath)\n if (relativePath === undefined) {\n return []\n }\n return [{comment, relativePath, selection: comment.selection}]\n })\n }, [comments, path])","(snapshot: {context: {value: unknown[]}}) =>\n resolveCommentSelections({\n value: snapshot.context.value,\n comments: inline.map(({comment, relativePath, selection}) => ({\n commentId: comment.id,\n relativePath,\n selection,\n })),\n })","({comment, relativePath, selection}) => ({\n commentId: comment.id,\n relativePath,\n selection,\n })","useCallback(\n (snapshot: {context: {value: unknown[]}}) =>\n resolveCommentSelections({\n value: snapshot.context.value,\n comments: inline.map(({comment, relativePath, selection}) => ({\n commentId: comment.id,\n relativePath,\n selection,\n })),\n }),\n [inline],\n )","{}","{forComments: inline, selections: {}}","anchored.flatMap((anchor) => {\n const comment = commentsById.get(anchor.commentId)\n if (!comment) {\n return []\n }\n\n const movedSelection = movedSelections[anchor.commentId]\n const selection =\n movedSelection === undefined ? anchor.selection : movedSelection\n if (selection === null) {\n // The editor reported the range lost, for example its text was deleted.\n return []\n }\n\n return [\n {\n component: renderDecoration(comment),\n selection,\n onMoved: ({newSelection}) => {\n setMoved((previous) => ({\n forComments: inline,\n selections: {\n ...(previous.forComments === inline ? previous.selections : {}),\n [anchor.commentId]: newSelection,\n },\n }))\n },\n payload: {commentId: anchor.commentId},\n } satisfies RangeDecoration,\n ]\n })","({comment}) => [comment.id, comment]","comment","selection","{newSelection}","useMemo(() => {\n const commentsById = new Map(\n inline.map(({comment}) => [comment.id, comment]),\n )\n\n return anchored.flatMap((anchor) => {\n const comment = commentsById.get(anchor.commentId)\n if (!comment) {\n return []\n }\n\n const movedSelection = movedSelections[anchor.commentId]\n const selection =\n movedSelection === undefined ? anchor.selection : movedSelection\n if (selection === null) {\n // The editor reported the range lost, for example its text was deleted.\n return []\n }\n\n return [\n {\n component: renderDecoration(comment),\n selection,\n onMoved: ({newSelection}) => {\n setMoved((previous) => ({\n forComments: inline,\n selections: {\n ...(previous.forComments === inline ? previous.selections : {}),\n [anchor.commentId]: newSelection,\n },\n }))\n },\n payload: {commentId: anchor.commentId},\n } satisfies RangeDecoration,\n ]\n })\n }, [anchored, inline, movedSelections, renderDecoration])","{comment}","{comment, relativePath, selection}","({message, commentId}) => {\n const snapshot = editor.getSnapshot()\n const built = buildStoredSelection({\n selection: getSelection(snapshot),\n selectedBlocks: getSelectedTextBlocks(snapshot) as SelectedTextBlock[],\n })\n if (!built) {\n return Promise.reject(\n new Error(\n 'Nothing commentable is selected, so there is nothing to anchor the comment to.',\n ),\n )\n }\n\n return createComment({\n ...handle,\n fieldPath: stringifyPath([\n ...arrayifyPath(path),\n ...built.containerPath,\n ]),\n selection: built.selection,\n message,\n ...(commentId === undefined ? {} : {commentId}),\n })\n }","{message, commentId}","{\n commentableSelection,\n createInlineComment: ({message, commentId}) => {\n const snapshot = editor.getSnapshot()\n const built = buildStoredSelection({\n selection: getSelection(snapshot),\n selectedBlocks: getSelectedTextBlocks(snapshot) as SelectedTextBlock[],\n })\n if (!built) {\n return Promise.reject(\n new Error(\n 'Nothing commentable is selected, so there is nothing to anchor the comment to.',\n ),\n )\n }\n\n return createComment({\n ...handle,\n fieldPath: stringifyPath([\n ...arrayifyPath(path),\n ...built.containerPath,\n ]),\n selection: built.selection,\n message,\n ...(commentId === undefined ? {} : {commentId}),\n })\n },\n }","getCaretColor(cursor.user.sanityUserId)","sanityUserId","{\n borderLeft: `2px solid ${color}`,\n marginLeft: -1,\n position: 'relative',\n // The line must not swallow clicks meant for the text under it.\n pointerEvents: 'none',\n }","`2px solid ${color}`","{\n backgroundColor: color,\n borderRadius: '50%',\n height: DOT_SIZE,\n left: -1,\n pointerEvents: 'auto',\n position: 'absolute',\n top: -(DOT_SIZE - 1),\n transform: 'translateX(-50%)',\n width: DOT_SIZE,\n }","<span\n data-testid={`presence-caret-dot-${cursor.sessionId}`}\n style={{\n backgroundColor: color,\n borderRadius: '50%',\n height: DOT_SIZE,\n left: -1,\n pointerEvents: 'auto',\n position: 'absolute',\n top: -(DOT_SIZE - 1),\n transform: 'translateX(-50%)',\n width: DOT_SIZE,\n }}\n title={displayName}\n />","`presence-caret-dot-${cursor.sessionId}`","<span\n // Without this the caret becomes editable content and the local user can\n // put their own cursor inside it.\n contentEditable={false}\n data-testid={`presence-caret-${cursor.sessionId}`}\n style={{\n borderLeft: `2px solid ${color}`,\n marginLeft: -1,\n position: 'relative',\n // The line must not swallow clicks meant for the text under it.\n pointerEvents: 'none',\n }}\n >\n <span\n data-testid={`presence-caret-dot-${cursor.sessionId}`}\n style={{\n backgroundColor: color,\n borderRadius: '50%',\n height: DOT_SIZE,\n left: -1,\n pointerEvents: 'auto',\n position: 'absolute',\n top: -(DOT_SIZE - 1),\n transform: 'translateX(-50%)',\n width: DOT_SIZE,\n }}\n title={displayName}\n />\n </span>","`presence-caret-${cursor.sessionId}`","<>\n <span\n // Without this the caret becomes editable content and the local user can\n // put their own cursor inside it.\n contentEditable={false}\n data-testid={`presence-caret-${cursor.sessionId}`}\n style={{\n borderLeft: `2px solid ${color}`,\n marginLeft: -1,\n position: 'relative',\n // The line must not swallow clicks meant for the text under it.\n pointerEvents: 'none',\n }}\n >\n <span\n data-testid={`presence-caret-dot-${cursor.sessionId}`}\n style={{\n backgroundColor: color,\n borderRadius: '50%',\n height: DOT_SIZE,\n left: -1,\n pointerEvents: 'auto',\n position: 'absolute',\n top: -(DOT_SIZE - 1),\n transform: 'translateX(-50%)',\n width: DOT_SIZE,\n }}\n title={displayName}\n />\n </span>\n {children}\n </>","resolveCursorRenderer(renderCursor)","{\n ...handle,\n path,\n renderCursor: renderer.renderCursor,\n }","renderCursor","mergePresenceDecorations(rangeDecorations, cursors, renderer.drawCursors)","drawCursors","useMemo(\n () =>\n mergePresenceDecorations(rangeDecorations, cursors, renderer.drawCursors),\n [cursors, rangeDecorations, renderer.drawCursors],\n )","<PortableTextEditable {...editableProps} rangeDecorations={decorations} />","<SDKValuePlugin {...handle} path={path} />","<SDKPresencePlugin {...handle} path={path} />","<>\n <PortableTextEditable {...editableProps} rangeDecorations={decorations} />\n <SDKValuePlugin {...handle} path={path} />\n <SDKPresencePlugin {...handle} path={path} />\n </>"],"sources":["../src/presence-cursors.ts","../src/plugin.presence-sync.tsx","../src/debug.ts","../src/sdk-document-handle.ts","../src/plugin.sdk-value.tsx","../src/plugin.sdk-presence.tsx","../src/comments-anchoring.ts","../src/comments-selection.ts","../src/plugin.sdk-comments.tsx","../src/presence-caret.tsx","../src/sdk-editable.tsx"],"sourcesContent":["import type {EditorSelection, RangeDecoration} from '@portabletext/editor'\nimport {isEqualSelections} from '@portabletext/editor/utils'\nimport type {PropsWithChildren, ReactElement} from 'react'\n\n/**\n * A remote participant's position in the editor.\n *\n * @public\n */\nexport interface RemoteCursor {\n /**\n * Identifies one editing session rather than one person. The same user in two\n * tabs reports two sessions and therefore draws two carets.\n */\n sessionId: string\n /**\n * The selection the participant last reported, or `null` when they have none.\n */\n selection: EditorSelection\n}\n\n/**\n * Where the local editor has pushed a remote caret, and the reported selection\n * it was pushed from.\n *\n * Typing above or inside a remote caret moves it, and the editor reports the\n * new position through `onMoved`. Keeping the selection it moved from is what\n * lets a later report from that participant take over again.\n *\n * @public\n */\nexport interface CursorOverride {\n reported: EditorSelection\n current: EditorSelection\n}\n\n/**\n * Options for {@link toRangeDecorations}.\n *\n * @public\n */\nexport interface RangeDecorationOptions<TCursor extends RemoteCursor> {\n cursors: readonly TCursor[]\n overrides: ReadonlyMap<string, CursorOverride>\n /**\n * Builds the component that draws one caret. The plugin has no opinion about\n * how a caret looks, so this is the caller's to provide.\n */\n renderCursor: (cursor: TCursor) => (props: PropsWithChildren) => ReactElement\n onCursorMoved?: (cursor: TCursor, selection: EditorSelection) => void\n}\n\n/**\n * Reduces a selection to a caret at its focus point.\n *\n * Presence answers where someone is, so decorating their whole selection would\n * highlight text the local user never selected. The Studio collapses the same\n * way, which keeps the two consistent when both are open on a document.\n *\n * @public\n */\nexport function collapseToCaret(selection: EditorSelection): EditorSelection {\n if (selection === null) {\n return null\n }\n return {anchor: selection.focus, focus: selection.focus}\n}\n\n/**\n * Where one remote caret should be drawn.\n *\n * A caret the local editor has moved keeps that moved position for as long as\n * the participant keeps reporting the same selection. A changed report wins,\n * because the participant has actually moved.\n *\n * @public\n */\nexport function resolveCursorSelection(\n cursor: RemoteCursor,\n override: CursorOverride | undefined,\n): EditorSelection {\n if (override && isEqualSelections(override.reported, cursor.selection)) {\n return override.current\n }\n return collapseToCaret(cursor.selection)\n}\n\n/**\n * Records where the editor moved a caret to.\n *\n * Overrides for sessions that are no longer present are dropped, so a long\n * editing session does not accumulate one entry per participant who ever\n * visited. Returns the map untouched when nothing changed.\n *\n * @public\n */\nexport function recordCursorMove(\n overrides: ReadonlyMap<string, CursorOverride>,\n cursors: readonly RemoteCursor[],\n cursor: RemoteCursor,\n selection: EditorSelection,\n): ReadonlyMap<string, CursorOverride> {\n const moved = collapseToCaret(selection)\n const existing = overrides.get(cursor.sessionId)\n const alreadyRecorded =\n existing !== undefined &&\n isEqualSelections(existing.reported, cursor.selection) &&\n isEqualSelections(existing.current, moved)\n\n const live = new Set(cursors.map((candidate) => candidate.sessionId))\n const staleKeys = [...overrides.keys()].filter((key) => !live.has(key))\n\n if (alreadyRecorded && staleKeys.length === 0) {\n return overrides\n }\n\n const next = new Map(overrides)\n for (const key of staleKeys) {\n next.delete(key)\n }\n next.set(cursor.sessionId, {reported: cursor.selection, current: moved})\n return next\n}\n\n/**\n * Maps remote cursors to range decorations for\n * `<PortableTextEditable rangeDecorations={...} />`.\n *\n * Participants with no caret to draw are skipped, so someone who clears their\n * selection stops drawing without being treated as having left.\n *\n * @public\n */\nexport function toRangeDecorations<TCursor extends RemoteCursor>(\n options: RangeDecorationOptions<TCursor>,\n): RangeDecoration[] {\n const {cursors, overrides, renderCursor, onCursorMoved} = options\n const decorations: RangeDecoration[] = []\n\n for (const cursor of cursors) {\n const selection = resolveCursorSelection(\n cursor,\n overrides.get(cursor.sessionId),\n )\n if (selection === null) {\n continue\n }\n decorations.push({\n component: renderCursor(cursor),\n selection,\n payload: {sessionId: cursor.sessionId},\n onMoved: onCursorMoved\n ? (details) => onCursorMoved(cursor, details.newSelection)\n : undefined,\n })\n }\n\n return decorations\n}\n","import {\n useEditor,\n useEditorSelector,\n type EditorSelection,\n type RangeDecoration,\n} from '@portabletext/editor'\nimport {getSelection} from '@portabletext/editor/selectors'\nimport {isEqualSelections} from '@portabletext/editor/utils'\nimport {\n useCallback,\n useMemo,\n useState,\n type PropsWithChildren,\n type ReactElement,\n} from 'react'\nimport {\n recordCursorMove,\n toRangeDecorations,\n type CursorOverride,\n type RemoteCursor,\n} from './presence-cursors'\n\n/**\n * Options for {@link useRemoteCursors}.\n *\n * @public\n */\nexport interface UseRemoteCursorsOptions<TCursor extends RemoteCursor> {\n /**\n * The remote participants to draw. Keep this array referentially stable, for\n * example with `useMemo`, so that decorations are not rebuilt on every\n * render.\n */\n cursors: readonly TCursor[]\n renderCursor: (cursor: TCursor) => (props: PropsWithChildren) => ReactElement\n}\n\n/**\n * The local user's selection, deduped by value.\n *\n * The editor produces a fresh selection object on every snapshot change, so\n * subscribing to it directly would report presence far more often than the\n * caret actually moves.\n *\n * @public\n */\nexport function useLocalSelection(): EditorSelection {\n const editor = useEditor()\n // Direction is ignored, which `isEqualSelections` already does: a caret is\n // drawn at the focus point, so reversing a selection moves nothing.\n return useEditorSelector(editor, getSelection, isEqualSelections)\n}\n\n/**\n * Turns remote cursors into range decorations, keeping each caret anchored as\n * the local user edits.\n *\n * Knows nothing about Sanity or the SDK, so it can also drive carets from\n * another source or from a test fixture. `useSDKPresenceCursors` is the version\n * wired to SDK presence.\n *\n * @public\n */\nexport function useRemoteCursors<TCursor extends RemoteCursor>(\n options: UseRemoteCursorsOptions<TCursor>,\n): RangeDecoration[] {\n const {cursors, renderCursor} = options\n // Only the moved carets need to survive a render. Where every caret is drawn\n // is derived from them plus the latest report, so there is no state to keep\n // in step with the incoming cursors.\n const [overrides, setOverrides] =\n useState<ReadonlyMap<string, CursorOverride>>(EMPTY_OVERRIDES)\n\n const onCursorMoved = useCallback(\n (cursor: TCursor, selection: EditorSelection) => {\n setOverrides((previous) =>\n recordCursorMove(previous, cursors, cursor, selection),\n )\n },\n [cursors],\n )\n\n return useMemo(\n () => toRangeDecorations({cursors, overrides, renderCursor, onCursorMoved}),\n [cursors, overrides, renderCursor, onCursorMoved],\n )\n}\n\nconst EMPTY_OVERRIDES: ReadonlyMap<string, CursorOverride> = new Map()\n","import rawDebug from 'debug'\n\n// Keep in sync with `packages/editor/src/internal-utils/debug.ts`: sharing\n// the `pte:` root lets `localStorage.debug = 'pte:*'` interleave this\n// plugin's sync traces with the editor's own output on one timeline.\nconst rootName = 'pte:plugin-sdk-value:'\n\nfunction createDebugger(name: string): rawDebug.Debugger {\n const namespace = `${rootName}${name}`\n if (rawDebug && rawDebug.enabled(namespace)) {\n return rawDebug(namespace)\n }\n return rawDebug(rootName)\n}\n\nexport const debug = {\n mutation: createDebugger('mutation'),\n push: createDebugger('push'),\n remote: createDebugger('remote'),\n repair: createDebugger('repair'),\n}\n","import type {DocumentHandle, DocumentResource} from '@sanity/sdk-react'\n\ntype DocumentHandleWithLegacySource = DocumentHandle & {\n source?: DocumentResource\n}\n\nexport function normalizeDocumentHandle<T extends DocumentHandle>(\n handle: T & DocumentHandleWithLegacySource,\n): Omit<T, 'source'> {\n const {source, ...normalizedHandle} = handle\n\n if (normalizedHandle.resource !== undefined || source === undefined) {\n return normalizedHandle\n }\n\n return {...normalizedHandle, resource: source}\n}\n","import {\n useEditor,\n type Editor,\n type PortableTextBlock,\n type Patch as PtePatch,\n} from '@portabletext/editor'\nimport {\n applyAll,\n type JSONValue,\n type Path,\n type PathSegment,\n type InsertPatch as PteInsertPatch,\n} from '@portabletext/patches'\nimport {diffValue, type SanityPatchOperations} from '@sanity/diff-patch'\nimport {\n parsePath,\n type ExprNode,\n type PathNode,\n type SegmentNode,\n type ThisNode,\n} from '@sanity/json-match'\nimport {\n editDocument,\n getDocumentState,\n subscribeDocumentEvents,\n useApplyDocumentActions,\n useEditDocument,\n useSanityInstance,\n type DocumentHandle,\n type DocumentResource,\n type EditDocumentAction,\n} from '@sanity/sdk-react'\nimport {useActorRef} from '@xstate/react'\nimport {useCallback} from 'react'\nimport {fromCallback, setup, type AnyEventObject} from 'xstate'\nimport {debug} from './debug'\nimport {normalizeDocumentHandle} from './sdk-document-handle'\n\ntype InsertPatch = Required<Pick<SanityPatchOperations, 'insert'>>\n\nconst ARRAYIFY_ERROR_MESSAGE =\n 'Unexpected path format from diffValue output. Please report this issue.'\n\nfunction* getSegments(\n node: PathNode,\n): Generator<Exclude<SegmentNode, ThisNode>> {\n if (node.base) {\n yield* getSegments(node.base)\n }\n if (node.segment.type !== 'This') {\n yield node.segment\n }\n}\n\nfunction isKeyPath(node: ExprNode): node is PathNode {\n if (node.type !== 'Path') {\n return false\n }\n if (node.base) {\n return false\n }\n if (node.recursive) {\n return false\n }\n if (node.segment.type !== 'Identifier') {\n return false\n }\n return node.segment.name === '_key'\n}\n\nexport function arrayifyPath(pathExpr: string): Path {\n const node = parsePath(pathExpr)\n if (!node) {\n return []\n }\n if (node.type !== 'Path') {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n\n return Array.from(getSegments(node)).map((segment): PathSegment => {\n if (segment.type === 'Identifier') {\n return segment.name\n }\n if (segment.type !== 'Subscript') {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n if (segment.elements.length !== 1) {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n\n const [element] = segment.elements\n if (element.type === 'Number') {\n return element.value\n }\n\n if (element.type !== 'Comparison') {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n if (element.operator !== '==') {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n const keyPathNode = [element.left, element.right].find(isKeyPath)\n if (!keyPathNode) {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n const other = element.left === keyPathNode ? element.right : element.left\n if (other.type !== 'String') {\n throw new Error(ARRAYIFY_ERROR_MESSAGE)\n }\n return {_key: other.value}\n })\n}\n\nexport function convertPatches(patches: SanityPatchOperations[]): PtePatch[] {\n return patches.flatMap((p) => {\n return Object.entries(p).flatMap(([type, values]): PtePatch[] => {\n const origin = 'remote'\n\n switch (type) {\n case 'set':\n case 'setIfMissing':\n case 'diffMatchPatch':\n case 'inc':\n case 'dec': {\n return Object.entries(values).map(\n ([pathExpr, value]) =>\n ({type, value, origin, path: arrayifyPath(pathExpr)}) as PtePatch,\n )\n }\n case 'unset': {\n if (!Array.isArray(values)) {\n return []\n }\n return values.map(arrayifyPath).map((path) => ({type, origin, path}))\n }\n case 'insert': {\n const {items, ...rest} = values as InsertPatch['insert']\n type InsertPosition = PteInsertPatch['position']\n const position = Object.keys(rest).at(0) as InsertPosition | undefined\n\n if (!position) {\n return []\n }\n const pathExpr = (rest as {[K in InsertPosition]: string})[position]\n const insertPatch: PteInsertPatch = {\n type,\n origin,\n position,\n path: arrayifyPath(pathExpr),\n items: items as JSONValue[],\n }\n\n return [insertPatch]\n }\n\n default: {\n return []\n }\n }\n })\n })\n}\n\nconst STRINGIFY_ERROR_MESSAGE =\n 'Unable to convert an editor patch path to a Sanity path expression.'\n\n/**\n * Converts a Portable Text Editor patch path (an array of segments) into a\n * Sanity json-match path expression. The inverse of `arrayifyPath`.\n *\n * @internal\n */\nexport function stringifyPatchPath(path: Path): string {\n let result = ''\n for (const segment of path) {\n if (typeof segment === 'string') {\n result = result === '' ? segment : `${result}.${segment}`\n } else if (typeof segment === 'number') {\n result = `${result}[${segment}]`\n } else if (\n typeof segment === 'object' &&\n segment !== null &&\n '_key' in segment\n ) {\n result = `${result}[_key==\"${segment._key}\"]`\n } else {\n throw new Error(STRINGIFY_ERROR_MESSAGE)\n }\n }\n return result\n}\n\nfunction prefixPathExpression(prefix: string, expression: string): string {\n if (expression === '') {\n return prefix\n }\n return expression.startsWith('[')\n ? `${prefix}${expression}`\n : `${prefix}.${expression}`\n}\n\n/**\n * `SanityPatchOperations` from `@sanity/diff-patch` only covers the\n * operations `diffValue` emits; the editor can additionally produce these.\n */\ntype SanityPatchOperationsWithExtras = SanityPatchOperations & {\n setIfMissing?: {[path: string]: unknown}\n inc?: {[path: string]: number}\n dec?: {[path: string]: number}\n}\n\n/**\n * Converts Portable Text Editor patches into Sanity patch operations rooted\n * at the given document field path. Throws if a patch cannot be converted;\n * callers should fall back to pushing the whole value.\n *\n * @internal\n */\nexport function convertPatchesToSanity(\n patches: PtePatch[],\n options: {prefix: string},\n): SanityPatchOperationsWithExtras[] {\n return patches.map((patch): SanityPatchOperationsWithExtras => {\n const pathExpression = prefixPathExpression(\n options.prefix,\n stringifyPatchPath(patch.path),\n )\n\n switch (patch.type) {\n case 'set':\n return {set: {[pathExpression]: patch.value}}\n case 'setIfMissing':\n return {setIfMissing: {[pathExpression]: patch.value}}\n case 'unset':\n // The editor unsets the whole field when it becomes empty. Write an\n // empty array instead: unsetting the field would leave other clients\n // unable to reconcile against it (their remote value disappears).\n if (patch.path.length === 0) {\n return {set: {[pathExpression]: []}}\n }\n return {unset: [pathExpression]}\n case 'diffMatchPatch':\n return {diffMatchPatch: {[pathExpression]: patch.value}}\n case 'inc':\n return {inc: {[pathExpression]: patch.value as number}}\n case 'dec':\n return {dec: {[pathExpression]: patch.value as number}}\n case 'insert':\n return {\n insert: {\n [patch.position]: pathExpression,\n items: patch.items,\n } as SanityPatchOperations['insert'],\n }\n default:\n throw new Error(STRINGIFY_ERROR_MESSAGE)\n }\n })\n}\n\nfunction segmentsEqual(a: PathSegment, b: PathSegment): boolean {\n if (typeof a === 'string' || typeof a === 'number') {\n return a === b\n }\n if (typeof b === 'string' || typeof b === 'number') {\n return false\n }\n if (Array.isArray(a) || Array.isArray(b)) {\n return false\n }\n return a._key === b._key\n}\n\nfunction pathsEqual(a: Path, b: Path): boolean {\n return (\n a.length === b.length &&\n a.every((segment, index) => segmentsEqual(segment, b[index]))\n )\n}\n\n/**\n * The editor engine can only resolve keyed/indexed segments against the\n * root block array and (possibly nested) `children` arrays. Operations\n * that address items of any other array, e.g. a block's `markDefs` or a\n * span's `marks`, misapply. When a patch path enters such a sidecar\n * array, this returns the path of the array itself so callers can fall\n * back to replacing the whole property; returns `null` for paths the\n * engine can apply.\n *\n * @internal\n */\nexport function findSidecarArrayPath(path: Path): Path | null {\n // a keyed/numeric segment is only resolvable first (root block array)\n // or directly after a `children` property\n let expectNode = true\n for (let index = 0; index < path.length; index++) {\n const segment = path[index]\n if (typeof segment === 'string') {\n expectNode = segment === 'children'\n } else {\n if (!expectNode) {\n return path.slice(0, index)\n }\n expectNode = false\n }\n }\n return null\n}\n\nfunction getValueAtPath(value: JSONValue, path: Path): JSONValue | undefined {\n let current: JSONValue | undefined = value\n for (const segment of path) {\n if (current === null || current === undefined) {\n return undefined\n }\n if (typeof segment === 'number') {\n if (!Array.isArray(current)) {\n return undefined\n }\n current = current[segment < 0 ? current.length + segment : segment]\n } else if (typeof segment === 'string') {\n if (typeof current !== 'object' || Array.isArray(current)) {\n return undefined\n }\n current = (current as {[key: string]: JSONValue})[segment]\n } else if (Array.isArray(segment)) {\n // index tuples address ranges, not single values\n return undefined\n } else {\n if (!Array.isArray(current)) {\n return undefined\n }\n current = current.find(\n (item) =>\n typeof item === 'object' &&\n item !== null &&\n !Array.isArray(item) &&\n (item as {_key?: unknown})._key === segment._key,\n )\n }\n }\n return current\n}\n\n/**\n * Converts a target-value diff into patches the editor engine can apply.\n * Patches addressing items inside sidecar arrays are coalesced into whole\n * `set`s (or `unset`s) of the owning property, taken from the target\n * value.\n *\n * @internal\n */\nexport function toEngineSafePatches(\n patches: PtePatch[],\n targetValue: PortableTextBlock[],\n): PtePatch[] {\n const safe: PtePatch[] = []\n const sidecarPaths: Path[] = []\n\n for (const patch of patches) {\n const sidecarPath = findSidecarArrayPath(patch.path)\n if (!sidecarPath) {\n safe.push(patch)\n continue\n }\n if (!sidecarPaths.some((existing) => pathsEqual(existing, sidecarPath))) {\n sidecarPaths.push(sidecarPath)\n }\n }\n\n for (const sidecarPath of sidecarPaths) {\n const value = getValueAtPath(\n targetValue as unknown as JSONValue,\n sidecarPath,\n )\n safe.push(\n value === undefined\n ? {type: 'unset', path: sidecarPath, origin: 'remote'}\n : {type: 'set', path: sidecarPath, value, origin: 'remote'},\n )\n }\n\n return safe\n}\n\n/**\n * Drops insert items whose `_key` is absent from the remote value. A text\n * paste can stage a temporary span and remove it again within the same\n * transaction; applying the insert without the cleanup would flash the\n * staged content. Callers must only run this once the store value reflects\n * the transaction the patches belong to (see `'apply remote patches'`),\n * otherwise every legitimately new node would be dropped.\n */\nfunction filterInsertsMissingFromRemoteValue(\n patches: PtePatch[],\n remoteValue: PortableTextBlock[],\n): PtePatch[] {\n return patches.flatMap((patch): PtePatch[] => {\n if (patch.type !== 'insert') {\n return [patch]\n }\n\n const parentValue = getValueAtPath(\n remoteValue as unknown as JSONValue,\n patch.path.slice(0, -1),\n )\n if (!Array.isArray(parentValue)) {\n return [patch]\n }\n\n const items = patch.items.filter((item) => {\n const itemKey = getKey(item)\n if (!itemKey) {\n return true\n }\n return parentValue.some((candidate) => getKey(candidate) === itemKey)\n })\n\n return items.length > 0 ? [{...patch, items}] : []\n })\n}\n\nfunction getKey(value: JSONValue): string | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return undefined\n }\n const key = (value as {_key?: unknown})._key\n return typeof key === 'string' ? key : undefined\n}\n\n/**\n * The editor writes `markDefs` as whole-array `set`s. Two clients\n * formatting the same block concurrently then overwrite each other's\n * arrays at the server (last writer wins) while both clients' span\n * `marks` references survive, stranding marks without definitions.\n * Outgoing `markDefs` sets are therefore decomposed into item-level\n * operations against the store (server truth): new definitions insert,\n * changed definitions set by key, and removed definitions unset by key,\n * except definitions the store's own spans still reference (a diverged\n * client's normalizer prunes those spuriously; a later, converged flush\n * removes them for real). Item-keyed operations merge at the server\n * instead of overwriting.\n *\n * @internal\n */\nexport function toMergeableMarkDefsPatches(\n patches: PtePatch[],\n getCurrentValue: () => PortableTextBlock[] | null | undefined,\n): PtePatch[] {\n return patches.flatMap((patch): PtePatch[] => {\n if (\n patch.type !== 'set' ||\n patch.path.at(-1) !== 'markDefs' ||\n !Array.isArray(patch.value)\n ) {\n return [patch]\n }\n const currentValue = getCurrentValue()\n if (!currentValue) {\n return [patch]\n }\n const root = currentValue as unknown as JSONValue\n const storeMarkDefs = getValueAtPath(root, patch.path)\n const storeBlock = getValueAtPath(root, patch.path.slice(0, -1))\n if (\n !Array.isArray(storeMarkDefs) ||\n typeof storeBlock !== 'object' ||\n storeBlock === null\n ) {\n return [patch]\n }\n\n const local = patch.value as Array<{_key?: string}>\n const store = storeMarkDefs as Array<{_key?: string}>\n if (local.some((item) => item._key === undefined)) {\n return [patch]\n }\n\n const referencedKeys = new Set(\n (\n (storeBlock as {children?: Array<{marks?: string[]}>}).children ?? []\n ).flatMap((child) => child.marks ?? []),\n )\n const storeByKey = new Map(store.map((item) => [item._key, item]))\n const localKeys = new Set(local.map((item) => item._key))\n const origin = patch.origin\n\n const ops: PtePatch[] = []\n\n const inserted = local.filter((item) => !storeByKey.has(item._key))\n if (inserted.length > 0) {\n for (const item of inserted) {\n if (item._key === undefined) {\n continue\n }\n // The store view can wrongly lack an in-flight insert (a colliding\n // transaction makes the optimistic rebase fail silently), so a\n // re-send would stack a duplicate. The keyed unset makes the\n // insert an upsert: it is a no-op while the key is absent, and\n // the client's own transactions apply in order, so a re-send\n // self-cleans any earlier copy instead of duplicating it.\n ops.push({\n type: 'unset',\n origin,\n path: [...patch.path, {_key: item._key}],\n })\n }\n ops.push({\n type: 'insert',\n origin,\n position: 'after',\n path: [...patch.path, -1],\n items: inserted as JSONValue[],\n })\n }\n\n for (const item of local) {\n const existing = storeByKey.get(item._key)\n if (existing && JSON.stringify(existing) !== JSON.stringify(item)) {\n ops.push({\n type: 'set',\n origin,\n path: [...patch.path, {_key: item._key as string}],\n value: item as JSONValue,\n })\n }\n }\n\n for (const item of store) {\n if (\n item._key !== undefined &&\n !localKeys.has(item._key) &&\n !referencedKeys.has(item._key)\n ) {\n ops.push({\n type: 'unset',\n origin,\n path: [...patch.path, {_key: item._key}],\n })\n }\n }\n\n return ops\n })\n}\n\n/**\n * Whether a remote patch can resolve against the given editor value.\n * Concurrent edits routinely produce operations addressing nodes another\n * client has already removed or not yet created; sending those into the\n * engine fails loudly (console errors) before being skipped. Callers drop\n * unresolvable patches up front and rely on the follow-up repair sync to\n * converge instead.\n *\n * @internal\n */\nexport function canApplyToValue(\n patch: PtePatch,\n value: PortableTextBlock[] | undefined,\n): boolean {\n if (!value) {\n return true\n }\n const root = value as unknown as JSONValue\n switch (patch.type) {\n // unset needs the node itself; insert needs the sibling at `path`;\n // diffMatchPatch needs the existing string\n case 'unset':\n case 'insert':\n case 'diffMatchPatch':\n return getValueAtPath(root, patch.path) !== undefined\n // set creates its target property, so only the parent must resolve\n case 'set':\n return (\n patch.path.length === 0 ||\n getValueAtPath(root, patch.path.slice(0, -1)) !== undefined\n )\n default:\n return true\n }\n}\n\n/**\n * Filters a patch batch down to the patches that can resolve (see\n * `canApplyToValue`), checking each patch against the value as it stands\n * after the preceding patches applied. A transaction routinely inserts a\n * node and then addresses it, e.g. a span split followed by a `marks` set\n * on the new span, so checking every patch against the starting value\n * would drop valid operations.\n */\nfunction filterResolvablePatches(\n patches: PtePatch[],\n value: PortableTextBlock[] | undefined,\n): PtePatch[] {\n let projected = value\n const resolvable: PtePatch[] = []\n for (const patch of patches) {\n if (!canApplyToValue(patch, projected)) {\n continue\n }\n resolvable.push(patch)\n if (projected) {\n try {\n projected = applyAll(projected, [patch])\n } catch {\n // the engine applies best-effort too; keep the projection as-is\n }\n }\n }\n return resolvable\n}\n\n/**\n * Scopes document-rooted Sanity patches to the given field path, returning\n * field-relative Portable Text Editor patches. Patches outside the field are\n * dropped. Returns `null` when the field (or an ancestor of it) is replaced\n * wholesale, in which case the caller should fall back to a full value sync.\n * Throws when a path expression cannot be converted.\n *\n * @internal\n */\nexport function scopeRemotePatches(\n patches: SanityPatchOperations[],\n fieldPath: string,\n): PtePatch[] | null {\n const prefix = arrayifyPath(fieldPath)\n const converted = convertPatches(patches)\n const scoped: PtePatch[] = []\n\n for (const patch of converted) {\n const overlap = Math.min(patch.path.length, prefix.length)\n let touchesField = true\n for (let index = 0; index < overlap; index++) {\n if (!segmentsEqual(patch.path[index], prefix[index])) {\n touchesField = false\n break\n }\n }\n if (!touchesField) {\n continue\n }\n if (patch.path.length <= prefix.length) {\n // the patch targets the field itself or an ancestor of it, which\n // cannot be expressed as a field-relative operation\n return null\n }\n scoped.push({...patch, path: patch.path.slice(prefix.length)})\n }\n\n return scoped\n}\n\nfunction debugTextOf(value: unknown): string {\n if (!Array.isArray(value)) {\n return ''\n }\n return value\n .map((block) =>\n Array.isArray((block as {children?: unknown}).children)\n ? ((block as {children: Array<{text?: string}>}).children ?? [])\n .map((child) => child.text ?? '')\n .join('')\n : '',\n )\n .join('\\n')\n}\n\n/**\n * How long an editor-versus-store divergence must persist, unchanged,\n * before the whole-value repair acts on it. When a remote transaction\n * arrives interleaved with the listener echoes of this client's own recent\n * edits, the store value is transiently wrong until the echo returns and\n * the rebase corrects it. A repair fired inside that window copies the\n * garbage into the editor and a follow-up repair restores the text at a\n * drifted offset, scrambling words the user typed in the meantime. The\n * window therefore has to outlast a slow listener echo round trip; real\n * divergence is stable and loses nothing by being repaired a beat later.\n *\n * Tests use a short window: their mock stores are synchronous, so echo\n * transients cannot occur, and waiting the production interval would only\n * slow every repair assertion down.\n */\nconst REPAIR_CONFIRM_DELAY =\n // @ts-expect-error - dot notation required for Vite to replace at build time\n process.env.NODE_ENV === 'test' ? 150 : 1000\n\ntype PendingRepair = {signature: string; timer: ReturnType<typeof setTimeout>}\nconst pendingRepairs = new WeakMap<Editor, PendingRepair>()\n\n/**\n * Local edits the editor has emitted but not yet flushed into a mutation.\n * While any exist, the store necessarily lags the editor and a repair diff\n * would \"repair away\" the user's unflushed keystrokes, so repairs must wait.\n */\nconst unflushedEdits = new WeakMap<Editor, boolean>()\n\nfunction computeRepair(\n editor: Editor,\n remoteValue: PortableTextBlock[],\n): {patches: PtePatch[]; convertible: boolean; signature: string} {\n const snapshot = editor.getSnapshot().context.value\n // The signature covers the full (editor, store) state, not just the diff:\n // with repetitive text, two different transients can produce an identical\n // diff, and a repair must only act when the world actually stood still.\n const stateSignature = JSON.stringify([snapshot, remoteValue])\n try {\n const patches = toEngineSafePatches(\n convertPatches(diffValue(snapshot, remoteValue)),\n remoteValue,\n )\n return {patches, convertible: true, signature: stateSignature}\n } catch {\n // diffValue can emit path shapes the converter does not understand\n // (e.g. array slices when multiple items are dropped). The repair then\n // has to fall back to a whole-value update.\n return {patches: [], convertible: false, signature: `!${stateSignature}`}\n }\n}\n\nfunction cancelPendingRepair(editor: Editor) {\n const pending = pendingRepairs.get(editor)\n if (pending) {\n clearTimeout(pending.timer)\n pendingRepairs.delete(editor)\n }\n}\n\nfunction applySync({\n editor,\n getRemoteValue,\n}: {\n editor: Editor\n getRemoteValue: () => PortableTextBlock[] | null | undefined\n}) {\n const remoteValue = getRemoteValue()\n\n if (!remoteValue) {\n return\n }\n\n const first = computeRepair(editor, remoteValue)\n if (first.convertible && first.patches.length === 0) {\n cancelPendingRepair(editor)\n return\n }\n\n if (debug.repair.enabled) {\n debug.repair('editor and store texts diverged %o', {\n editorText: debugTextOf(editor.getSnapshot().context.value),\n remoteText: debugTextOf(remoteValue),\n })\n }\n\n // Same divergence already awaiting confirmation: let that timer decide.\n const pending = pendingRepairs.get(editor)\n if (pending && pending.signature === first.signature) {\n return\n }\n cancelPendingRepair(editor)\n\n const timer = setTimeout(() => {\n pendingRepairs.delete(editor)\n\n // Unflushed local keystrokes mean the store lags the editor by design;\n // a repair now would delete them. Re-arm and wait for the flush (the\n // divergence either disappears once the push round-trips, or persists\n // and gets repaired then).\n if (unflushedEdits.get(editor)) {\n applySync({editor, getRemoteValue})\n return\n }\n\n // Recompute from scratch: the store may have corrected itself (the\n // transient case) or the user may have typed (their edits will push\n // and reconverge through the normal flow); only a divergence that is\n // still byte-for-byte identical is real and safe to repair.\n const latestRemote = getRemoteValue()\n if (!latestRemote) {\n return\n }\n const second = computeRepair(editor, latestRemote)\n if (second.convertible && second.patches.length === 0) {\n return\n }\n if (second.signature !== first.signature) {\n // Still diverged but differently: re-arm so a stable state eventually\n // confirms. Transients converge to the empty diff; genuine divergence\n // stabilizes to a fixed signature within one flush cycle.\n applySync({editor, getRemoteValue})\n return\n }\n\n if (!second.convertible) {\n debug.repair('escalating to whole-value sync (unconvertible diff)')\n editor.send({type: 'update value', value: latestRemote})\n return\n }\n\n const snapshot = editor.getSnapshot().context.value\n debug.repair('applying confirmed repair patches %o', second.patches)\n editor.send({type: 'patches', patches: second.patches, snapshot})\n\n // Patch application is best-effort: the editor skips operations it\n // cannot resolve against its current tree, and concurrent edits to the\n // same range produce keyed operations whose targets no longer exist\n // locally. When the editor is still diverged after the diff-based\n // repair, escalate to the full value sync machinery, which reconciles\n // arbitrary divergence block by block.\n const valueAfterPatches = editor.getSnapshot().context.value\n if (diffValue(valueAfterPatches, latestRemote).length > 0) {\n if (debug.repair.enabled) {\n debug.repair('escalating to whole-value sync %o', {\n editorText: debugTextOf(valueAfterPatches),\n remoteText: debugTextOf(latestRemote),\n })\n }\n editor.send({type: 'update value', value: latestRemote})\n }\n }, REPAIR_CONFIRM_DELAY)\n\n pendingRepairs.set(editor, {signature: first.signature, timer})\n}\n\nconst listenToEditor = fromCallback<AnyEventObject, {editor: Editor}>(\n ({sendBack, input}) => {\n const patchSubscription = input.editor.on('patch', () => {\n // Every 'patch' event is a local edit (remote application suppresses\n // patch generation), so the store now lags the editor until the next\n // mutation flush.\n unflushedEdits.set(input.editor, true)\n sendBack({type: 'patch emitted'})\n })\n\n const mutationSubscription = input.editor.on('mutation', (event) => {\n unflushedEdits.set(input.editor, false)\n if (debug.mutation.enabled) {\n debug.mutation('flushed %o', {\n flushText: debugTextOf(event.value),\n snapshotText: debugTextOf(input.editor.getSnapshot().context.value),\n })\n }\n sendBack({\n type: 'mutation flushed',\n value: event.value,\n patches: event.patches,\n })\n })\n\n return () => {\n patchSubscription.unsubscribe()\n mutationSubscription.unsubscribe()\n }\n },\n)\n\nconst listenToRemote = fromCallback<\n AnyEventObject,\n {onRemoteValueChange: ValueSyncConfig['onRemoteValueChange']}\n>(({sendBack, input}) => {\n return input.onRemoteValueChange(() => {\n sendBack({type: 'remote value changed'})\n })\n})\n\nconst listenToRemotePatches = fromCallback<\n AnyEventObject,\n {onRemotePatches: ValueSyncConfig['onRemotePatches']}\n>(({sendBack, input}) => {\n return input.onRemotePatches?.((patches) => {\n sendBack({type: 'remote patches received', patches})\n })\n})\n\n/**\n * How long the machine must sit in 'idle' (no local keystrokes, no store\n * events) before the one-shot whole-value repair runs. Long enough for the\n * editor's external value snapshot to catch up after the last flush; short\n * enough that residual divergence from concurrent editing heals promptly.\n */\nconst QUIESCENT_REPAIR_DELAY = 500\n\nconst valueSyncMachine = setup({\n types: {\n context: {} as {\n editor: Editor\n getRemoteValue: ValueSyncConfig['getRemoteValue']\n onRemoteValueChange: ValueSyncConfig['onRemoteValueChange']\n onRemotePatches: ValueSyncConfig['onRemotePatches']\n },\n input: {} as {\n editor: Editor\n getRemoteValue: ValueSyncConfig['getRemoteValue']\n onRemoteValueChange: ValueSyncConfig['onRemoteValueChange']\n onRemotePatches: ValueSyncConfig['onRemotePatches']\n },\n events: {} as\n | {type: 'patch emitted'}\n | {\n type: 'mutation flushed'\n value: PortableTextBlock[] | undefined\n patches: PtePatch[]\n }\n | {type: 'remote value changed'}\n | {type: 'remote patches received'; patches: PtePatch[]},\n },\n actions: {\n 'send initial value': ({context}) => {\n context.editor.send({\n type: 'update value',\n value: context.getRemoteValue() ?? [],\n })\n },\n 'push to remote': () => {\n throw new Error('push to remote must be provided via .provide()')\n },\n 'apply sync': ({context}) => {\n applySync({\n editor: context.editor,\n getRemoteValue: context.getRemoteValue,\n })\n },\n 'apply remote patches': ({context, event}) => {\n if (event.type !== 'remote patches received') {\n return\n }\n debug.remote('applying remote patches %o', event.patches)\n // The SDK emits `remote-patches` while it is still computing the\n // state update for that transaction, so the store value visible at\n // this moment does not include it yet. The commit lands within the\n // same synchronous task, so after one microtask the store reflects\n // the transaction, whether the event announced a fresh transaction\n // or replayed one the store already had. Only then can the store\n // value serve as the reference for dropping inserts the transaction\n // itself cleaned up again, and as the target for coalescing\n // sidecar-array item operations (which the engine misroutes) into\n // whole-property sets.\n queueMicrotask(() => {\n const snapshot = context.editor.getSnapshot().context.value\n const remoteValue = context.getRemoteValue()\n const coalesced = remoteValue\n ? toEngineSafePatches(event.patches, remoteValue)\n : event.patches\n const patches = filterResolvablePatches(\n remoteValue\n ? filterInsertsMissingFromRemoteValue(coalesced, remoteValue)\n : coalesced,\n snapshot,\n )\n if (patches.length === 0) {\n return\n }\n context.editor.send({type: 'patches', patches, snapshot})\n })\n },\n },\n actors: {\n 'listen to editor': listenToEditor,\n 'listen to remote': listenToRemote,\n 'listen to remote patches': listenToRemotePatches,\n },\n}).createMachine({\n id: 'value sync',\n context: ({input}) => ({\n editor: input.editor,\n getRemoteValue: input.getRemoteValue,\n onRemoteValueChange: input.onRemoteValueChange,\n onRemotePatches: input.onRemotePatches,\n }),\n entry: ['send initial value'],\n invoke: [\n {\n src: 'listen to editor',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'listen to remote',\n input: ({context}) => ({\n onRemoteValueChange: context.onRemoteValueChange,\n }),\n },\n {\n src: 'listen to remote patches',\n input: ({context}) => ({\n onRemotePatches: context.onRemotePatches,\n }),\n },\n ],\n // Two invariants, learned the hard way:\n //\n // 1. EVERY 'mutation flushed' event must be pushed, in every state. The\n // editor emits mutation events in bursts (one per input batch, e.g.\n // each backspace of a quick delete), and any state without a handler\n // silently drops the flush. A dropped flush permanently diverges the\n // store from the editor, and the whole-value repair then \"heals\" the\n // editor backwards, resurrecting deleted text.\n // 2. The whole-value repair must only run when no local edits can be in\n // flight (quiescent 'idle'). Running it mid-typing diffs the editor\n // against a store that lags the user's keystrokes and stomps them.\n //\n // Operational patches from other clients still apply immediately in\n // every state; the editor merges them with in-flight local changes.\n initial: 'idle',\n states: {\n 'idle': {\n // One-shot repair after a quiet period. Covers divergence left by\n // best-effort patch application while local edits were in flight\n // (those states never repair; see below) when no further store\n // event arrives to trigger the on-change repair.\n after: {\n [QUIESCENT_REPAIR_DELAY]: {\n actions: ['apply sync'],\n },\n },\n on: {\n 'patch emitted': {\n target: 'local write',\n },\n // Mutation events arrive in bursts (one per input batch), so a\n // flush can land after a sibling flush already advanced the state.\n 'mutation flushed': {\n target: 'pushing to remote',\n actions: ['push to remote'],\n },\n 'remote value changed': {\n actions: ['apply sync'],\n },\n // No immediate repair here: every remote patch batch also updates\n // the store value, so the accompanying 'remote value changed'\n // event runs the whole-value repair right after.\n 'remote patches received': {\n actions: ['apply remote patches'],\n },\n },\n },\n 'local write': {\n on: {\n 'patch emitted': {},\n 'mutation flushed': {\n target: 'pushing to remote',\n actions: ['push to remote'],\n },\n 'remote value changed': {\n target: 'pending sync',\n },\n 'remote patches received': {\n target: 'pending sync',\n actions: ['apply remote patches'],\n },\n },\n },\n 'pushing to remote': {\n on: {\n 'patch emitted': {\n target: 'local write',\n },\n 'mutation flushed': {\n actions: ['push to remote'],\n },\n // No repair on the push acknowledgment: the editor snapshot can\n // lag the live document while the user keeps typing, so a diff\n // against the store here resurrects deleted text and duplicates\n // in-flight keystrokes. Once truly idle, the store change or the\n // quiescent delay runs the repair with a caught-up snapshot.\n 'remote value changed': {\n target: 'idle',\n },\n 'remote patches received': {\n actions: ['apply remote patches'],\n },\n },\n },\n 'pending sync': {\n on: {\n 'patch emitted': {},\n 'mutation flushed': {\n target: 'pushing to remote',\n actions: ['push to remote'],\n },\n 'remote value changed': {},\n 'remote patches received': {\n actions: ['apply remote patches'],\n },\n },\n },\n },\n})\n\ninterface SDKValuePluginProps extends DocumentHandle {\n source?: DocumentResource\n path: string\n}\n\n/**\n * The shape of the `remote-patches` document event emitted by\n * `@sanity/sdk` >= 2.17. Declared structurally so the plugin stays\n * compatible with older SDK typings until its dependency is bumped.\n */\ntype RemotePatchesDocumentEvent = {\n type: 'remote-patches'\n documentId: string\n transactionId: string\n timestamp: string\n previousRev?: string\n patches: SanityPatchOperations[]\n origin: 'local' | 'remote'\n}\n\nfunction isRemotePatchesEvent(event: {\n type: string\n}): event is RemotePatchesDocumentEvent {\n return event.type === 'remote-patches'\n}\n\nfunction getPublishedDocumentId(id: string): string {\n if (id.startsWith('drafts.')) {\n return id.slice('drafts.'.length)\n }\n if (id.startsWith('versions.')) {\n return id.split('.').slice(2).join('.')\n }\n return id\n}\n\n/**\n * @public\n */\nexport function SDKValuePlugin(props: SDKValuePluginProps) {\n const normalizedProps = normalizeDocumentHandle(props)\n const {documentId, documentType, path} = normalizedProps\n const setSdkValue = useEditDocument(normalizedProps)\n const instance = useSanityInstance()\n const applyActions = useApplyDocumentActions()\n\n const handle = {documentId, documentType, path}\n const {getCurrent, subscribe} = getDocumentState<PortableTextBlock[]>(\n instance,\n handle,\n )\n\n const onRemotePatches = useCallback(\n (callback: (patches: PtePatch[]) => void) => {\n return subscribeDocumentEvents(instance, {\n eventHandler: (event) => {\n // widen before narrowing: `remote-patches` is not part of the\n // `DocumentEvent` union in older SDK typings\n const candidate: {type: string} = event\n if (!isRemotePatchesEvent(candidate)) {\n return\n }\n // our own transactions are already reflected in the editor\n if (candidate.origin !== 'remote') {\n return\n }\n if (\n getPublishedDocumentId(candidate.documentId) !==\n getPublishedDocumentId(documentId)\n ) {\n return\n }\n\n let patches: PtePatch[] | null\n try {\n patches = scopeRemotePatches(candidate.patches, path)\n } catch {\n // unconvertible patch shapes fall back to the whole-value sync\n // driven by `onRemoteValueChange`\n return\n }\n if (patches && patches.length > 0) {\n callback(patches)\n }\n },\n })\n },\n [instance, documentId, path],\n )\n\n const pushPatches = useCallback(\n (patches: PtePatch[]) => {\n const sanityPatches = convertPatchesToSanity(patches, {prefix: path})\n // `preserveOperations` ships in @sanity/sdk >= 2.17; the intersection\n // type keeps the plugin compatible with older SDK typings\n const action: EditDocumentAction & {preserveOperations?: boolean} = {\n ...editDocument(\n {documentId, documentType},\n sanityPatches as Parameters<typeof editDocument>[1],\n ),\n preserveOperations: true,\n }\n applyActions(action)\n },\n [applyActions, documentId, documentType, path],\n )\n\n return (\n <ValueSyncPlugin\n getRemoteValue={getCurrent}\n pushValue={setSdkValue}\n onRemoteValueChange={subscribe}\n onRemotePatches={onRemotePatches}\n pushPatches={pushPatches}\n />\n )\n}\n\n/**\n * @internal\n */\ntype ValueSyncConfig = {\n getRemoteValue: () => PortableTextBlock[] | null | undefined\n pushValue: (value: PortableTextBlock[]) => void\n onRemoteValueChange: (callback: () => void) => () => void\n /**\n * Optional patch channel. When provided, operational patches from other\n * clients are applied directly to the editor (in every state) instead of\n * waiting for a whole-value diff, and local editor patches are pushed\n * through `pushPatches`. The whole-value sync remains as a fallback for\n * anything the patch channel cannot express.\n */\n onRemotePatches?: (\n callback: (patches: PtePatch[]) => void,\n ) => (() => void) | undefined\n /**\n * Pushes the editor's own operational patches to the remote store. May\n * throw when a patch cannot be converted, in which case the plugin falls\n * back to pushing the whole value.\n */\n pushPatches?: (patches: PtePatch[]) => void\n}\n\n/**\n * NOTE: You are probably looking for SDKValuePlugin instead of this.\n * This is a lower-level plugin that only handles syncing the value\n * between the editor and a remote source. It does not know anything\n * about Sanity documents or how to fetch/update them.\n *\n * May be removed in the future, do not rely on this directly.\n *\n * @internal\n */\nexport function ValueSyncPlugin(props: ValueSyncConfig) {\n const {\n getRemoteValue,\n pushValue,\n onRemoteValueChange,\n onRemotePatches,\n pushPatches,\n } = props\n const editor = useEditor()\n\n useActorRef(\n valueSyncMachine.provide({\n actions: {\n 'push to remote': ({context, event}) => {\n if (event.type !== 'mutation flushed') {\n return\n }\n\n if (pushPatches && event.patches.length > 0) {\n try {\n const mergeable = toMergeableMarkDefsPatches(\n event.patches,\n context.getRemoteValue,\n )\n debug.push('pushing patches %o', mergeable)\n pushPatches(mergeable)\n return\n } catch {\n // fall back to pushing the whole value below\n }\n }\n\n if (debug.push.enabled) {\n debug.push(\n 'pushing whole value %s',\n debugTextOf(\n event.value ?? context.editor.getSnapshot().context.value,\n ),\n )\n }\n pushValue(event.value ?? context.editor.getSnapshot().context.value)\n },\n },\n }),\n {\n input: {\n editor,\n getRemoteValue,\n onRemoteValueChange,\n onRemotePatches,\n },\n },\n )\n\n return null\n}\n","import type {EditorSelection, RangeDecoration} from '@portabletext/editor'\nimport {\n usePresenceForDocument,\n useReportPresence,\n type DocumentHandle,\n type DocumentResource,\n type UseReportPresenceOptions,\n} from '@sanity/sdk-react'\nimport {useMemo, type PropsWithChildren, type ReactElement} from 'react'\nimport {useLocalSelection, useRemoteCursors} from './plugin.presence-sync'\nimport {arrayifyPath} from './plugin.sdk-value'\nimport {normalizeDocumentHandle} from './sdk-document-handle'\n\n/**\n * `@sanity/sdk-react` exports the presence hooks and their option types but not\n * the presence data types, so these are derived from what it does export.\n */\ntype DocumentPresence = ReturnType<\n typeof usePresenceForDocument\n>['presence'][number]\n\n/**\n * Draws one remote participant's caret. This package has no opinion about how a\n * caret looks, so it is the caller's to provide.\n *\n * @public\n */\nexport type RenderCursorFunction = (\n cursor: SDKRemoteCursor,\n) => (props: PropsWithChildren) => ReactElement\n\n/**\n * A remote participant's caret, together with who they are.\n *\n * Declared in full rather than extending the internal cursor type, so nothing in\n * the public API refers to a type consumers cannot import.\n *\n * @public\n */\nexport interface SDKRemoteCursor {\n /**\n * Identifies one editing session rather than one person. The same user in two\n * tabs reports two sessions and therefore draws two carets.\n */\n sessionId: string\n /**\n * Where the participant last reported their selection.\n */\n selection: EditorSelection\n user: DocumentPresence['user']\n}\n\n/**\n * Props for {@link SDKPresencePlugin}.\n *\n * @public\n */\nexport interface SDKPresencePluginProps extends DocumentHandle {\n /**\n * @deprecated Use `resource` instead.\n */\n source?: DocumentResource\n /**\n * The document path of the Portable Text field, for example `content`. The\n * same form `SDKValuePlugin` takes.\n */\n path: string\n}\n\n/**\n * Options for {@link useSDKPresenceCursors}.\n *\n * @public\n */\nexport interface UseSDKPresenceCursorsOptions extends DocumentHandle {\n /**\n * @deprecated Use `resource` instead.\n */\n source?: DocumentResource\n path: string\n renderCursor: RenderCursorFunction\n}\n\n/**\n * Reports the local user's caret in a Portable Text field, so other people in\n * the same document can see where they are.\n *\n * Place it inside `EditorProvider`. Prefer `SDKPortableTextEditable`, which\n * reports and draws in one component; reach for this when you render\n * `PortableTextEditable` yourself.\n *\n * Which document is reported follows the handle's perspective, or the ambient\n * one from `ResourceProvider`. Pass the plain document id and let the\n * perspective select the draft, the published document, or a release version.\n *\n * @public\n */\nexport function SDKPresencePlugin(props: SDKPresencePluginProps) {\n const {path, ...handle} = normalizeDocumentHandle(props)\n const selection = useLocalSelection()\n const fieldPath = useFieldPath(path)\n\n useReportPresence({...handle, path: fieldPath, selection})\n\n return null\n}\n\n/**\n * Other people's carets in a Portable Text field, as range decorations.\n *\n * Pass the result to `<PortableTextEditable rangeDecorations={...} />`, or use\n * `SDKPortableTextEditable` and skip the wiring. Each caret stays anchored as\n * the local user types, and disappears when the participant leaves or their\n * session expires.\n *\n * The local user is never included, so an app does not draw its own caret.\n * Participants are counted by session, so the same person in two tabs draws two\n * carets.\n *\n * @public\n */\nexport function useSDKPresenceCursors(\n options: UseSDKPresenceCursorsOptions,\n): RangeDecoration[] {\n const {path, renderCursor, ...handle} = normalizeDocumentHandle(options)\n const fieldPath = useFieldPath(path)\n // Exact id matching: a caret reported while editing a draft must not be drawn\n // in a release version of the same document, where the text differs.\n const {presence} = usePresenceForDocument({\n ...handle,\n path: fieldPath,\n excludeVersions: true,\n })\n\n const cursors = useMemo(\n () =>\n presence.flatMap((participant): SDKRemoteCursor[] =>\n participant.selection\n ? [\n {\n sessionId: participant.sessionId,\n selection: participant.selection,\n user: participant.user,\n },\n ]\n : [],\n ),\n [presence],\n )\n\n return useRemoteCursors({cursors, renderCursor})\n}\n\n/**\n * The SDK's presence hooks address fields by path array, because a field-level\n * path inside Portable Text needs keyed segments. This package takes the same\n * string expression as `SDKValuePlugin` everywhere and converts here.\n */\nfunction useFieldPath(\n path: string,\n): NonNullable<UseReportPresenceOptions['path']> {\n return useMemo(() => arrayifyPath(path), [path])\n}\n","import type {EditorSelection} from '@portabletext/editor'\nimport type {Path, PathSegment} from '@portabletext/patches'\nimport {\n applyPatches,\n cleanupEfficiency,\n DIFF_DELETE,\n DIFF_EQUAL,\n DIFF_INSERT,\n makeDiff,\n makePatches,\n type Diff,\n type Patch,\n} from '@sanity/diff-match-patch'\nimport {arrayifyPath} from './plugin.sdk-value'\n\n/**\n * How the Studio stores an inline comment's anchor: per Portable Text block the\n * selection touches, the block's entire plain text with these two private-use\n * characters inserted where the selection starts and ends. Text rather than\n * offsets, so the anchor can be re-found after the text around it changes.\n */\nexport const COMMENT_INDICATORS = ['\\uF000', '\\uF001'] as const\n\nconst COMMENT_INDICATORS_REGEX = new RegExp(\n `[${COMMENT_INDICATORS.join('')}]`,\n 'g',\n)\n\n/**\n * Inserted between spans when diffing, so a plain-text offset can be mapped\n * back to the span it belongs to afterwards.\n */\nconst CHILD_SYMBOL = '\\uF0D0'\n\n/**\n * Kept from the Studio implementation: high enough to avoid re-anchoring onto\n * the wrong occurrence of a repeated word, low enough not to hurt.\n */\nconst DMP_MARGIN = 15\n\n/**\n * The stored anchor of one inline comment, in the shape the SDK returns it.\n * Structurally `CommentTextSelection` from `@sanity/sdk`, declared here so the\n * pure modules in this package stay import-light.\n */\nexport interface StoredTextSelection {\n type: 'text'\n value: {_key: string; text: string}[]\n}\n\nexport interface AnchoredComment {\n /** The comment's id, echoed back so the caller can correlate. */\n commentId: string\n /** Where the comment's text sits in the current editor value. */\n selection: NonNullable<EditorSelection>\n}\n\ninterface ResolveOptions {\n /** The editor's current value. */\n value: unknown[]\n /**\n * Each comment's stored anchor, with its field path already reduced to the\n * path *inside* the editor: `[]` for a block directly in the decorated\n * field, or the keyed path of the containing array for a nested block.\n */\n comments: Array<{\n commentId: string\n relativePath: Path\n selection: StoredTextSelection\n }>\n}\n\ninterface SpanLike {\n _key: string\n _type: string\n text?: string\n}\n\ninterface TextBlockLike {\n _key: string\n children: SpanLike[]\n}\n\nfunction isTextBlock(node: unknown): node is TextBlockLike {\n return (\n typeof node === 'object' &&\n node !== null &&\n Array.isArray((node as TextBlockLike).children) &&\n typeof (node as TextBlockLike)._key === 'string'\n )\n}\n\nfunction isSpan(child: SpanLike): boolean {\n return child._type === 'span' && typeof child.text === 'string'\n}\n\nfunction getValueAtPath(value: unknown, path: Path): unknown {\n let current: unknown = value\n for (const segment of path) {\n if (current === null || typeof current !== 'object') {\n return undefined\n }\n if (typeof segment === 'string') {\n current = (current as Record<string, unknown>)[segment]\n } else if (typeof segment === 'number') {\n current = Array.isArray(current) ? current[segment] : undefined\n } else if (isKeyedSegment(segment)) {\n current = Array.isArray(current)\n ? current.find(\n (item) =>\n typeof item === 'object' &&\n item !== null &&\n (item as {_key?: string})._key === segment._key,\n )\n : undefined\n } else {\n return undefined\n }\n }\n return current\n}\n\nfunction isKeyedSegment(segment: PathSegment): segment is {_key: string} {\n return typeof segment === 'object' && segment !== null && '_key' in segment\n}\n\nfunction toPlainTextWithChildSeparators(block: TextBlockLike): string {\n return block.children\n .map((child) =>\n isSpan(child) ? (child.text ?? '').replaceAll(CHILD_SYMBOL, ' ') : '',\n )\n .join(CHILD_SYMBOL)\n}\n\nfunction diffText(\n current: string,\n next: string,\n): {patches: Patch[]; levenshtein: number} {\n const diff = makeDiff(current, next)\n const diffs = cleanupEfficiency(diff)\n return {\n patches: makePatches(current, diffs, {margin: DMP_MARGIN}),\n levenshtein: diffsLevenshtein(diffs),\n }\n}\n\nfunction diffApply(current: string, patches: Patch[]): string {\n return applyPatches(patches, current, {\n allowExceedingIndices: true,\n margin: DMP_MARGIN,\n })[0]\n}\n\n/**\n * Finds each stored anchor in the current editor value.\n *\n * A direct port of the Studio's `buildRangeDecorationSelectionsFromComments`,\n * minus its Studio-only inputs. The stored text is diffed against the block's\n * current text, so an anchor survives edits around it and inside it up to a\n * similarity threshold. An anchor whose text is gone, or changed beyond\n * recognition, is dropped rather than drawn somewhere wrong.\n *\n * Live tracking while the user types is not this function's job: the editor's\n * `RangeDecoration.onMoved` does that. This runs when comments load or change.\n */\nexport function resolveCommentSelections(\n options: ResolveOptions,\n): AnchoredComment[] {\n const {value, comments} = options\n const anchored: AnchoredComment[] = []\n\n for (const {commentId, relativePath, selection} of comments) {\n for (const selectionMember of selection.value) {\n const container =\n relativePath.length > 0 ? getValueAtPath(value, relativePath) : value\n const matchedBlock = Array.isArray(container)\n ? container.find(\n (block) =>\n isTextBlock(block) && block._key === selectionMember._key,\n )\n : undefined\n if (!matchedBlock || !isTextBlock(matchedBlock)) {\n continue\n }\n\n const selectionText = selectionMember.text.replaceAll(\n COMMENT_INDICATORS_REGEX,\n '',\n )\n const textWithChildSeparators =\n toPlainTextWithChildSeparators(matchedBlock)\n const {patches} = diffText(selectionText, selectionMember.text)\n const diffedText = diffApply(textWithChildSeparators, patches)\n const startIndex = diffedText.indexOf(COMMENT_INDICATORS[0])\n const endIndex = diffedText\n .replaceAll(COMMENT_INDICATORS[0], '')\n .indexOf(COMMENT_INDICATORS[1])\n const textWithoutCommentTags = diffedText.replaceAll(\n COMMENT_INDICATORS_REGEX,\n '',\n )\n\n if (startIndex === -1 || endIndex === -1) {\n continue\n }\n\n const oldCommentedText = selectionMember.text.slice(\n selectionMember.text.indexOf(COMMENT_INDICATORS[0]) + 1,\n selectionMember.text.indexOf(COMMENT_INDICATORS[1]),\n )\n const newCommentedText = textWithoutCommentTags.slice(\n startIndex,\n endIndex,\n )\n const {levenshtein} = diffText(newCommentedText, oldCommentedText)\n // Kept from the Studio, oddity included: only the *old* length is halved.\n const threshold = Math.round(\n newCommentedText.length + oldCommentedText.length / 2,\n )\n\n // The anchor is lost when its text is gone or no longer recognisable.\n // Better no highlight than a highlight on the wrong words.\n if (\n newCommentedText.length === 0 ||\n levenshtein > threshold ||\n startIndex + 1 === endIndex\n ) {\n continue\n }\n\n let childIndexAnchor = 0\n let anchorOffset = 0\n let childIndexFocus = 0\n let focusOffset = 0\n for (let i = 0; i < textWithoutCommentTags.length; i++) {\n if (textWithoutCommentTags[i] === CHILD_SYMBOL) {\n if (i <= startIndex) {\n anchorOffset = -1\n childIndexAnchor++\n }\n focusOffset = -1\n childIndexFocus++\n }\n if (i < startIndex) {\n anchorOffset++\n }\n if (i < startIndex + newCommentedText.length) {\n focusOffset++\n }\n if (i === startIndex + newCommentedText.length) {\n break\n }\n }\n\n anchored.push({\n commentId,\n selection: {\n anchor: {\n path: [\n ...relativePath,\n {_key: matchedBlock._key},\n 'children',\n {_key: matchedBlock.children[childIndexAnchor]._key},\n ],\n offset: anchorOffset,\n },\n focus: {\n path: [\n ...relativePath,\n {_key: matchedBlock._key},\n 'children',\n {_key: matchedBlock.children[childIndexFocus]._key},\n ],\n offset: focusOffset,\n },\n },\n })\n }\n }\n\n return anchored\n}\n\nfunction diffsLevenshtein(diffs: Diff[]): number {\n let levenshtein = 0\n let insertions = 0\n let deletions = 0\n for (const [op, data] of diffs) {\n switch (op) {\n case DIFF_INSERT:\n insertions += data.length\n break\n case DIFF_DELETE:\n deletions += data.length\n break\n case DIFF_EQUAL:\n // A deletion and an insertion together count as one substitution.\n levenshtein += Math.max(insertions, deletions)\n insertions = 0\n deletions = 0\n break\n default:\n break\n }\n }\n levenshtein += Math.max(insertions, deletions)\n return levenshtein\n}\n\n/**\n * Reduces a comment's stored field path to a path inside this editor.\n *\n * `undefined` means the comment belongs to some other editor: a different\n * field, a sibling container, or a stored path that does not parse. Skipping\n * those is what lets several editors on one document each decorate only their\n * own comments.\n */\nexport function relativeCommentPath(\n basePath: Path,\n fieldPath: string,\n): Path | undefined {\n let parsed: Path\n try {\n parsed = arrayifyPath(fieldPath)\n } catch {\n return undefined\n }\n if (parsed.length < basePath.length) {\n return undefined\n }\n for (let i = 0; i < basePath.length; i++) {\n if (!segmentsEqual(basePath[i], parsed[i])) {\n return undefined\n }\n }\n return parsed.slice(basePath.length)\n}\n\nfunction segmentsEqual(a: PathSegment, b: PathSegment): boolean {\n if (isKeyedSegment(a) && isKeyedSegment(b)) {\n return a._key === b._key\n }\n return a === b\n}\n","import type {EditorSelection} from '@portabletext/editor'\nimport type {Path, PathSegment} from '@portabletext/patches'\nimport {\n COMMENT_INDICATORS,\n type StoredTextSelection,\n} from './comments-anchoring'\n\ninterface SpanLike {\n _key: string\n _type: string\n text?: string\n}\n\ninterface TextBlockLike {\n _key: string\n children: SpanLike[]\n}\n\nexport interface SelectedTextBlock {\n node: TextBlockLike\n path: Path\n}\n\nexport interface BuiltSelection {\n /**\n * The path of the array holding the selected blocks, relative to the editor.\n * Empty for blocks at the top level. Comments anchor on the containing array,\n * matching what the Studio stores.\n */\n containerPath: Path\n /** The anchor in the Studio's stored shape, ready to pass to `createComment`. */\n selection: StoredTextSelection\n}\n\n/**\n * Turns the current editor selection into the stored comment anchor.\n *\n * Per selected block, the block's entire plain text with the selection\n * boundaries marked by the two indicator characters. The write-time mirror of\n * `resolveCommentSelections`, and deliberately built the way the Studio builds\n * it so a comment written here re-anchors there and back.\n *\n * Returns `null` when there is nothing commentable: a collapsed selection,\n * no selected text, or a selection spanning blocks from different containing\n * arrays, which a single stored path cannot describe.\n */\nexport function buildStoredSelection(options: {\n selection: EditorSelection\n selectedBlocks: SelectedTextBlock[]\n}): BuiltSelection | null {\n const {selection, selectedBlocks} = options\n if (!selection || selectedBlocks.length === 0) {\n return null\n }\n\n const [start, end] = selection.backward\n ? [selection.focus, selection.anchor]\n : [selection.anchor, selection.focus]\n\n const containerPath = selectedBlocks[0].path.slice(0, -1)\n const sharedContainer = selectedBlocks.every((selected) =>\n pathsEqual(selected.path.slice(0, -1), containerPath),\n )\n if (!sharedContainer) {\n return null\n }\n\n let selectedCharacters = 0\n const value = selectedBlocks.map((selected, index) => {\n const isFirst = index === 0\n const isLast = index === selectedBlocks.length - 1\n const plain = plainText(selected.node)\n\n const from = isFirst\n ? plainOffset(selected.node, start.path, start.offset)\n : 0\n const to = isLast\n ? plainOffset(selected.node, end.path, end.offset)\n : plain.length\n\n selectedCharacters += Math.max(0, to - from)\n\n return {\n _key: selected.node._key,\n text: `${plain.slice(0, from)}${COMMENT_INDICATORS[0]}${plain.slice(from, to)}${COMMENT_INDICATORS[1]}${plain.slice(to)}`,\n }\n })\n\n // A collapsed selection, or one that only touches empty text, anchors to\n // nothing worth highlighting.\n if (selectedCharacters === 0) {\n return null\n }\n\n return {containerPath, selection: {type: 'text', value}}\n}\n\nfunction plainText(block: TextBlockLike): string {\n return block.children\n .map((child) => (isSpan(child) ? (child.text ?? '') : ''))\n .join('')\n}\n\n/**\n * Converts a selection point into an offset within the block's plain text: the\n * text of every span before the point's child, plus the offset inside it. A\n * point whose child is not in this block clamps to the block edge, which is\n * where a cross-block selection boundary lands.\n */\nfunction plainOffset(\n block: TextBlockLike,\n pointPath: Path,\n offset: number,\n): number {\n const childSegment = pointPath[pointPath.length - 1]\n if (!isKeyedSegment(childSegment)) {\n return offset\n }\n\n let total = 0\n for (const child of block.children) {\n if (child._key === childSegment._key) {\n return total + (isSpan(child) ? offset : 0)\n }\n if (isSpan(child)) {\n total += (child.text ?? '').length\n }\n }\n return total\n}\n\nfunction isSpan(child: SpanLike): boolean {\n return child._type === 'span' && typeof child.text === 'string'\n}\n\nfunction isKeyedSegment(\n segment: PathSegment | undefined,\n): segment is {_key: string} {\n return typeof segment === 'object' && segment !== null && '_key' in segment\n}\n\nfunction pathsEqual(a: Path, b: Path): boolean {\n if (a.length !== b.length) {\n return false\n }\n return a.every((segment, index) => {\n const other = b[index]\n if (isKeyedSegment(segment) && isKeyedSegment(other)) {\n return segment._key === other._key\n }\n return segment === other\n })\n}\n","import {\n useEditor,\n useEditorSelector,\n type EditorSelection,\n type RangeDecoration,\n} from '@portabletext/editor'\nimport {\n getSelectedTextBlocks,\n getSelection,\n} from '@portabletext/editor/selectors'\nimport {isEqualSelections} from '@portabletext/editor/utils'\nimport {stringifyPath} from '@sanity/json-match'\nimport {\n useCommentActions,\n useComments,\n type Comment,\n type CommentMessage,\n type DocumentHandle,\n} from '@sanity/sdk-react'\nimport {\n useCallback,\n useMemo,\n useState,\n type PropsWithChildren,\n type ReactElement,\n} from 'react'\nimport {\n relativeCommentPath,\n resolveCommentSelections,\n type AnchoredComment,\n} from './comments-anchoring'\nimport {\n buildStoredSelection,\n type SelectedTextBlock,\n} from './comments-selection'\nimport {arrayifyPath} from './plugin.sdk-value'\n\nconst NO_MOVES: Record<string, EditorSelection> = {}\n\n/**\n * Anchors count as unchanged when every comment still sits at the same spot,\n * so resolving on each editor emission only re-renders when a highlight\n * actually needs to draw somewhere else.\n */\nfunction sameAnchors(a: AnchoredComment[], b: AnchoredComment[]): boolean {\n if (a.length !== b.length) {\n return false\n }\n return a.every((anchor, index) => {\n const other = b[index]\n return (\n anchor.commentId === other.commentId &&\n samePoint(anchor.selection.anchor, other.selection.anchor) &&\n samePoint(anchor.selection.focus, other.selection.focus)\n )\n })\n}\n\nfunction samePoint(\n a: {path: unknown[]; offset: number},\n b: {path: unknown[]; offset: number},\n): boolean {\n if (a.offset !== b.offset || a.path.length !== b.path.length) {\n return false\n }\n return a.path.every((segment, index) => {\n const other = b.path[index]\n if (\n typeof segment === 'object' &&\n segment !== null &&\n typeof other === 'object' &&\n other !== null\n ) {\n return (\n (segment as {_key?: string})._key === (other as {_key?: string})._key\n )\n }\n return segment === other\n })\n}\n\n/**\n * Draws one comment's highlight. The plugin has no opinion about how a\n * highlight looks, so it is the caller's to provide, the same way presence\n * takes `renderCursor`.\n *\n * @public\n */\nexport type RenderCommentDecorationFunction = (\n comment: Comment,\n) => (props: PropsWithChildren) => ReactElement\n\n/**\n * Options for {@link useSDKCommentDecorations}.\n *\n * @public\n */\nexport interface UseSDKCommentDecorationsOptions extends DocumentHandle {\n /**\n * The document path of the Portable Text field, for example `content`. The\n * same form `SDKValuePlugin` takes.\n */\n path: string\n renderDecoration: RenderCommentDecorationFunction\n}\n\n/**\n * Inline comment highlights for a Portable Text field, as range decorations.\n *\n * Pass the result to `<PortableTextEditable rangeDecorations={...} />`. Each\n * thread's first comment that carries a text anchor on this field gets one\n * decoration. Highlights stay put while the local user types, and a highlight\n * whose text has been deleted or rewritten beyond recognition is dropped\n * rather than drawn on the wrong words.\n *\n * Suspends while the document's comments load, like every SDK read hook.\n *\n * Resolved threads draw nothing: this mirrors the Studio, where resolving a\n * thread removes its highlight from the text.\n *\n * @public\n */\nexport function useSDKCommentDecorations(\n options: UseSDKCommentDecorationsOptions,\n): RangeDecoration[] {\n const {path, renderDecoration, ...handle} = options\n const editor = useEditor()\n const {comments} = useComments({...handle})\n\n const inline = useMemo(() => {\n const basePath = arrayifyPath(path)\n return comments.flatMap((comment) => {\n if (\n comment.parentCommentId ||\n !comment.selection ||\n comment.status !== 'open'\n ) {\n return []\n }\n const relativePath = relativeCommentPath(basePath, comment.fieldPath)\n if (relativePath === undefined) {\n return []\n }\n return [{comment, relativePath, selection: comment.selection}]\n })\n }, [comments, path])\n\n // Resolved inside the selector so every resolution reads the text as it is\n // right now. Anything less fresh mis-anchors a comment written on text typed\n // since the staler reading, and on first load finds nothing at all, since\n // the field's value can arrive after the comments do. The anchor equality\n // keeps re-renders to actual highlight changes, and positions the editor is\n // already tracking through `onMoved` win over these resolutions anyway.\n const resolveAnchors = useCallback(\n (snapshot: {context: {value: unknown[]}}) =>\n resolveCommentSelections({\n value: snapshot.context.value,\n comments: inline.map(({comment, relativePath, selection}) => ({\n commentId: comment.id,\n relativePath,\n selection,\n })),\n }),\n [inline],\n )\n const anchored = useEditorSelector(editor, resolveAnchors, sameAnchors)\n\n // Moves are remembered against the comment list they were reported for, so a\n // re-resolution wins over stale positions without a state reset.\n const [moved, setMoved] = useState<{\n forComments: typeof inline\n selections: Record<string, EditorSelection>\n }>({forComments: inline, selections: {}})\n const movedSelections =\n moved.forComments === inline ? moved.selections : NO_MOVES\n\n return useMemo(() => {\n const commentsById = new Map(\n inline.map(({comment}) => [comment.id, comment]),\n )\n\n return anchored.flatMap((anchor) => {\n const comment = commentsById.get(anchor.commentId)\n if (!comment) {\n return []\n }\n\n const movedSelection = movedSelections[anchor.commentId]\n const selection =\n movedSelection === undefined ? anchor.selection : movedSelection\n if (selection === null) {\n // The editor reported the range lost, for example its text was deleted.\n return []\n }\n\n return [\n {\n component: renderDecoration(comment),\n selection,\n onMoved: ({newSelection}) => {\n setMoved((previous) => ({\n forComments: inline,\n selections: {\n ...(previous.forComments === inline ? previous.selections : {}),\n [anchor.commentId]: newSelection,\n },\n }))\n },\n payload: {commentId: anchor.commentId},\n } satisfies RangeDecoration,\n ]\n })\n }, [anchored, inline, movedSelections, renderDecoration])\n}\n\n/**\n * Options for {@link useSDKCommentAuthoring}.\n *\n * @public\n */\nexport interface UseSDKCommentAuthoringOptions extends DocumentHandle {\n /**\n * The document path of the Portable Text field, for example `content`.\n */\n path: string\n}\n\n/**\n * What {@link useSDKCommentAuthoring} returns.\n *\n * @public\n */\nexport interface SDKCommentAuthoring {\n /**\n * The current selection when it can take a comment, `null` otherwise. Show\n * the comment affordance when this is set, and position it off the\n * selection. A selection can take a comment when it is expanded, contains\n * text, and stays within one array of blocks.\n */\n commentableSelection: EditorSelection\n /**\n * Starts a comment thread anchored to the text selected right now.\n *\n * The anchor is captured from the live selection at call time, so call this\n * from the affordance while the selection still stands. Rejects when nothing\n * commentable is selected.\n */\n createInlineComment: (options: {\n message: CommentMessage\n /** Reuse the id of a failed comment to retry it. */\n commentId?: string\n }) => Promise<Comment>\n}\n\n/** Reports the selection when it can take a comment, `null` otherwise. */\nfunction getCommentableSelection(\n snapshot: Parameters<typeof getSelection>[0],\n): EditorSelection {\n const selection = getSelection(snapshot)\n const built = buildStoredSelection({\n selection,\n selectedBlocks: getSelectedTextBlocks(snapshot) as SelectedTextBlock[],\n })\n return built ? selection : null\n}\n\n/**\n * Lets the app author inline comments on a Portable Text field.\n *\n * The plugin captures the selection and writes the comment; the composer UI is\n * the app's, the same split the Studio and Canvas use. Comments are written in\n * the shape the Studio stores, so a thread started here shows up there.\n *\n * @public\n */\nexport function useSDKCommentAuthoring(\n options: UseSDKCommentAuthoringOptions,\n): SDKCommentAuthoring {\n const {path, ...handle} = options\n const editor = useEditor()\n const {createComment} = useCommentActions()\n\n const commentableSelection = useEditorSelector(\n editor,\n getCommentableSelection,\n isEqualSelections,\n )\n\n return {\n commentableSelection,\n createInlineComment: ({message, commentId}) => {\n const snapshot = editor.getSnapshot()\n const built = buildStoredSelection({\n selection: getSelection(snapshot),\n selectedBlocks: getSelectedTextBlocks(snapshot) as SelectedTextBlock[],\n })\n if (!built) {\n return Promise.reject(\n new Error(\n 'Nothing commentable is selected, so there is nothing to anchor the comment to.',\n ),\n )\n }\n\n return createComment({\n ...handle,\n fieldPath: stringifyPath([\n ...arrayifyPath(path),\n ...built.containerPath,\n ]),\n selection: built.selection,\n message,\n ...(commentId === undefined ? {} : {commentId}),\n })\n },\n }\n}\n","import type {PropsWithChildren} from 'react'\nimport type {RenderCursorFunction, SDKRemoteCursor} from './plugin.sdk-presence'\n\n/**\n * Mid-tone hues, so a caret stays legible whether the app is light or dark. This\n * package cannot read the app's theme, so it does not try.\n */\nconst CARET_COLORS = [\n '#e0508a',\n '#c05fd8',\n '#7c66e8',\n '#2f8fdd',\n '#1f9c8f',\n '#4f9c2f',\n '#c98a1c',\n '#d4603a',\n]\n\nconst DOT_SIZE = 6\n\n/**\n * Picks a stable colour for a participant.\n *\n * Keyed on the user rather than the session, so one person in two tabs draws two\n * carets in the same colour. The Studio colours by user for the same reason.\n *\n * @public\n */\nexport function getCaretColor(userId: string): string {\n let hash = 0\n for (let index = 0; index < userId.length; index++) {\n hash = (hash * 31 + userId.charCodeAt(index)) % 1000003\n }\n return CARET_COLORS[hash % CARET_COLORS.length]\n}\n\n/**\n * Draws a remote caret when no `renderCursor` was given: a coloured line with a\n * dot above it, and the participant's name on hover.\n *\n * Deliberately plain, and styled inline so it needs no stylesheet. Pass your own\n * `renderCursor` to match your design.\n *\n * @public\n */\nexport const renderDefaultCursor: RenderCursorFunction =\n (cursor) => (props) => (\n <DefaultCaret cursor={cursor}>{props.children}</DefaultCaret>\n )\n\nfunction DefaultCaret(props: PropsWithChildren<{cursor: SDKRemoteCursor}>) {\n const {cursor, children} = props\n const color = getCaretColor(cursor.user.sanityUserId)\n const displayName = cursor.user.profile.displayName\n\n return (\n <>\n <span\n // Without this the caret becomes editable content and the local user can\n // put their own cursor inside it.\n contentEditable={false}\n data-testid={`presence-caret-${cursor.sessionId}`}\n style={{\n borderLeft: `2px solid ${color}`,\n marginLeft: -1,\n position: 'relative',\n // The line must not swallow clicks meant for the text under it.\n pointerEvents: 'none',\n }}\n >\n <span\n data-testid={`presence-caret-dot-${cursor.sessionId}`}\n style={{\n backgroundColor: color,\n borderRadius: '50%',\n height: DOT_SIZE,\n left: -1,\n pointerEvents: 'auto',\n position: 'absolute',\n top: -(DOT_SIZE - 1),\n transform: 'translateX(-50%)',\n width: DOT_SIZE,\n }}\n title={displayName}\n />\n </span>\n {children}\n </>\n )\n}\n","import {\n PortableTextEditable,\n type PortableTextEditableProps,\n type RangeDecoration,\n} from '@portabletext/editor'\nimport type {DocumentHandle, DocumentResource} from '@sanity/sdk-react'\nimport {useMemo} from 'react'\nimport {\n SDKPresencePlugin,\n useSDKPresenceCursors,\n type RenderCursorFunction,\n} from './plugin.sdk-presence'\nimport {SDKValuePlugin} from './plugin.sdk-value'\nimport {renderDefaultCursor} from './presence-caret'\nimport {normalizeDocumentHandle} from './sdk-document-handle'\n\n/**\n * Props for {@link SDKPortableTextEditable}.\n *\n * @public\n */\nexport interface SDKPortableTextEditableProps\n extends\n DocumentHandle,\n // `resource` is both a document handle field and an RDFa HTML attribute, so\n // the handle wins. Dropping the attribute costs nothing in an editor.\n Omit<PortableTextEditableProps, keyof DocumentHandle> {\n /**\n * @deprecated Use `resource` instead.\n */\n source?: DocumentResource\n /**\n * The document path of the Portable Text field, for example `content`.\n */\n path: string\n /**\n * Draws one remote participant's caret. Omit it for the built-in caret, which\n * needs no styling of your own. Pass `null` to draw no carets at all while\n * still reporting the local user's presence.\n */\n renderCursor?: RenderCursorFunction | null\n}\n\n/**\n * What {@link splitEditableProps} pulls apart.\n *\n * @internal\n */\nexport interface SplitEditableProps {\n handle: DocumentHandle\n path: string\n renderCursor: RenderCursorFunction | null | undefined\n rangeDecorations: RangeDecoration[] | undefined\n editableProps: Omit<\n PortableTextEditableProps,\n keyof DocumentHandle | 'rangeDecorations'\n >\n}\n\n/**\n * A `PortableTextEditable` wired to a Sanity document: the field's value syncs\n * both ways, the local user's caret is reported, and other people's carets are\n * drawn.\n *\n * Place it inside `EditorProvider` in place of `PortableTextEditable`. Every\n * other prop is forwarded untouched, and `rangeDecorations` you pass are kept\n * and merged with the presence carets rather than replaced.\n *\n * Nothing else is needed: this replaces a separate `SDKValuePlugin`.\n *\n * @example\n * ```tsx\n * <EditorProvider initialConfig={{schemaDefinition}}>\n * <SDKPortableTextEditable\n * {...documentHandle}\n * path=\"content\"\n * renderCursor={({user}) => (props) => (\n * <Caret user={user}>{props.children}</Caret>\n * )}\n * />\n * </EditorProvider>\n * ```\n *\n * @public\n */\nexport function SDKPortableTextEditable(props: SDKPortableTextEditableProps) {\n const {handle, path, renderCursor, rangeDecorations, editableProps} =\n splitEditableProps(props)\n\n const renderer = resolveCursorRenderer(renderCursor)\n\n const cursors = useSDKPresenceCursors({\n ...handle,\n path,\n renderCursor: renderer.renderCursor,\n })\n\n const decorations = useMemo(\n () =>\n mergePresenceDecorations(rangeDecorations, cursors, renderer.drawCursors),\n [cursors, rangeDecorations, renderer.drawCursors],\n )\n\n return (\n <>\n <PortableTextEditable {...editableProps} rangeDecorations={decorations} />\n <SDKValuePlugin {...handle} path={path} />\n <SDKPresencePlugin {...handle} path={path} />\n </>\n )\n}\n\n/**\n * Splits the props into the document handle, this component's own props, and\n * what is forwarded to `PortableTextEditable`. Keeping handle fields out of the\n * forwarded set is what stops them reaching the DOM as attributes.\n *\n * @internal\n */\nexport function splitEditableProps(\n props: SDKPortableTextEditableProps,\n): SplitEditableProps {\n const normalizedProps = normalizeDocumentHandle(props)\n const {\n documentId,\n documentType,\n projectId,\n dataset,\n resource,\n resourceName,\n liveEdit,\n perspective,\n path,\n renderCursor,\n rangeDecorations,\n ...editableProps\n } = normalizedProps\n\n return {\n // Only the fields the caller actually passed. The SDK resolves an ambient\n // perspective and resource from context with `Object.hasOwn`, so forwarding\n // `perspective: undefined` would override what `ResourceProvider` set rather\n // than defer to it, and the field would sync and report against the draft\n // instead of the release the app is showing.\n handle: {\n documentId,\n documentType,\n ...('projectId' in props && {projectId}),\n ...('dataset' in props && {dataset}),\n ...('resource' in normalizedProps && {resource}),\n ...('resourceName' in props && {resourceName}),\n ...('liveEdit' in props && {liveEdit}),\n ...('perspective' in props && {perspective}),\n },\n path,\n renderCursor,\n rangeDecorations,\n editableProps,\n }\n}\n\n/**\n * Decides which caret component to draw with, and whether to draw at all.\n *\n * Presence is subscribed to either way, because hooks cannot be called\n * conditionally, so switching carets off discards the decorations rather than\n * skipping the work.\n *\n * @internal\n */\nexport function resolveCursorRenderer(\n renderCursor: RenderCursorFunction | null | undefined,\n): {renderCursor: RenderCursorFunction; drawCursors: boolean} {\n return {\n renderCursor: renderCursor ?? renderDefaultCursor,\n drawCursors: renderCursor !== null,\n }\n}\n\n/**\n * Appends the presence carets to whatever decorations the caller passed, so\n * theirs survive. The Studio merges the same way. When carets are switched off\n * the caller's own decorations pass straight through.\n *\n * @internal\n */\nexport function mergePresenceDecorations(\n rangeDecorations: RangeDecoration[] | undefined,\n cursors: RangeDecoration[],\n drawCursors: boolean,\n): RangeDecoration[] | undefined {\n if (!drawCursors) {\n return rangeDecorations\n }\n return [...(rangeDecorations ?? []), ...cursors]\n}\n\n/**\n * Splitting the props by hand is what keeps document handle fields off the DOM,\n * so every field has to be listed above. `sdk-editable.test.ts` fails to compile\n * if `DocumentHandle` gains one. Note that `@sanity/sdk-react` adds fields to the\n * core handle, so it has to be read from there.\n */\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/plugin-sdk-value",
3
- "version": "8.0.6",
3
+ "version": "8.1.1",
4
4
  "description": "Connect a Portable Text Editor with a Sanity document using the SDK",
5
5
  "keywords": [
6
6
  "@sanity/sdk",
@@ -33,6 +33,7 @@
33
33
  "./package.json": "./package.json"
34
34
  },
35
35
  "dependencies": {
36
+ "@sanity/diff-match-patch": "^3.2.0",
36
37
  "@sanity/diff-patch": "^6.0.0",
37
38
  "@sanity/json-match": "^1.0.5",
38
39
  "@xstate/react": "^6.1.0",
@@ -41,7 +42,6 @@
41
42
  "@portabletext/patches": "^3.0.0"
42
43
  },
43
44
  "devDependencies": {
44
- "@sanity/diff-match-patch": "^3.2.0",
45
45
  "@sanity/pkg-utils": "^12.3.0",
46
46
  "@sanity/sdk-react": "^3.0.0",
47
47
  "@sanity/tsconfig": "^2.1.0",
@@ -59,15 +59,15 @@
59
59
  "vite": "^8.2.0",
60
60
  "vitest": "^4.1.11",
61
61
  "vitest-browser-react": "^2.2.0",
62
+ "@portabletext/editor": "^8.1.2",
62
63
  "@portabletext/schema": "^3.0.0",
63
- "@portabletext/editor": "^8.1.1",
64
64
  "@portabletext/test": "^2.1.0"
65
65
  },
66
66
  "peerDependencies": {
67
- "@sanity/sdk-react": "^2.19.0 || ^3",
67
+ "@sanity/sdk-react": "^2.20.1 || ^3",
68
68
  "react": "^19.2",
69
69
  "react-dom": "^19.2",
70
- "@portabletext/editor": "^8.1.1"
70
+ "@portabletext/editor": "^8.1.2"
71
71
  },
72
72
  "engines": {
73
73
  "node": ">=22.12"