@wordpress/core-data 7.48.0 → 7.48.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.
Files changed (99) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/build/awareness/block-lookup.cjs +14 -26
  3. package/build/awareness/block-lookup.cjs.map +2 -2
  4. package/build/awareness/post-editor-awareness.cjs +4 -3
  5. package/build/awareness/post-editor-awareness.cjs.map +2 -2
  6. package/build/entities.cjs +4 -2
  7. package/build/entities.cjs.map +2 -2
  8. package/build/hooks/use-post-editor-awareness-state.cjs +8 -2
  9. package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
  10. package/build/private-actions.cjs +8 -0
  11. package/build/private-actions.cjs.map +2 -2
  12. package/build/private-selectors.cjs.map +2 -2
  13. package/build/reducer.cjs +13 -0
  14. package/build/reducer.cjs.map +2 -2
  15. package/build/resolvers.cjs +13 -8
  16. package/build/resolvers.cjs.map +2 -2
  17. package/build/selectors.cjs +7 -0
  18. package/build/selectors.cjs.map +2 -2
  19. package/build/utils/crdt-blocks.cjs +12 -2
  20. package/build/utils/crdt-blocks.cjs.map +2 -2
  21. package/build/utils/crdt.cjs +2 -1
  22. package/build/utils/crdt.cjs.map +2 -2
  23. package/build/utils/index.cjs +3 -0
  24. package/build/utils/index.cjs.map +2 -2
  25. package/build/utils/save-crdt-doc.cjs +75 -0
  26. package/build/utils/save-crdt-doc.cjs.map +7 -0
  27. package/build-module/awareness/block-lookup.mjs +13 -26
  28. package/build-module/awareness/block-lookup.mjs.map +2 -2
  29. package/build-module/awareness/post-editor-awareness.mjs +4 -3
  30. package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
  31. package/build-module/entities.mjs +4 -2
  32. package/build-module/entities.mjs.map +2 -2
  33. package/build-module/hooks/use-post-editor-awareness-state.mjs +9 -3
  34. package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
  35. package/build-module/private-actions.mjs +7 -0
  36. package/build-module/private-actions.mjs.map +2 -2
  37. package/build-module/private-selectors.mjs.map +2 -2
  38. package/build-module/reducer.mjs +12 -0
  39. package/build-module/reducer.mjs.map +2 -2
  40. package/build-module/resolvers.mjs +15 -9
  41. package/build-module/resolvers.mjs.map +2 -2
  42. package/build-module/selectors.mjs +7 -0
  43. package/build-module/selectors.mjs.map +2 -2
  44. package/build-module/utils/crdt-blocks.mjs +12 -2
  45. package/build-module/utils/crdt-blocks.mjs.map +2 -2
  46. package/build-module/utils/crdt.mjs +2 -1
  47. package/build-module/utils/crdt.mjs.map +2 -2
  48. package/build-module/utils/index.mjs +2 -0
  49. package/build-module/utils/index.mjs.map +2 -2
  50. package/build-module/utils/save-crdt-doc.mjs +40 -0
  51. package/build-module/utils/save-crdt-doc.mjs.map +7 -0
  52. package/build-types/awareness/block-lookup.d.ts +27 -7
  53. package/build-types/awareness/block-lookup.d.ts.map +1 -1
  54. package/build-types/awareness/post-editor-awareness.d.ts +3 -1
  55. package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
  56. package/build-types/entities.d.ts.map +1 -1
  57. package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
  58. package/build-types/private-actions.d.ts +15 -0
  59. package/build-types/private-actions.d.ts.map +1 -1
  60. package/build-types/private-selectors.d.ts +0 -12
  61. package/build-types/private-selectors.d.ts.map +1 -1
  62. package/build-types/reducer.d.ts +15 -0
  63. package/build-types/reducer.d.ts.map +1 -1
  64. package/build-types/resolvers.d.ts.map +1 -1
  65. package/build-types/selectors.d.ts +4 -0
  66. package/build-types/selectors.d.ts.map +1 -1
  67. package/build-types/utils/crdt-blocks.d.ts +5 -1
  68. package/build-types/utils/crdt-blocks.d.ts.map +1 -1
  69. package/build-types/utils/crdt.d.ts.map +1 -1
  70. package/build-types/utils/index.d.ts +1 -0
  71. package/build-types/utils/index.d.ts.map +1 -1
  72. package/build-types/utils/on-sub-key.d.ts +4 -0
  73. package/build-types/utils/on-sub-key.d.ts.map +1 -0
  74. package/build-types/utils/save-crdt-doc.d.ts +8 -0
  75. package/build-types/utils/save-crdt-doc.d.ts.map +1 -0
  76. package/package.json +22 -20
  77. package/src/awareness/block-lookup.ts +21 -62
  78. package/src/awareness/post-editor-awareness.ts +8 -3
  79. package/src/awareness/test/block-lookup.ts +98 -94
  80. package/src/awareness/test/post-editor-awareness.ts +177 -180
  81. package/src/entities.js +9 -3
  82. package/src/hooks/test/use-post-editor-awareness-state.ts +10 -2
  83. package/src/hooks/use-post-editor-awareness-state.ts +20 -7
  84. package/src/private-actions.js +18 -0
  85. package/src/private-selectors.ts +0 -12
  86. package/src/reducer.js +17 -0
  87. package/src/resolvers.js +20 -13
  88. package/src/selectors.ts +11 -0
  89. package/src/test/private-selectors.js +66 -0
  90. package/src/test/reducer.js +44 -0
  91. package/src/test/resolvers.js +121 -113
  92. package/src/test/selectors.js +48 -0
  93. package/src/utils/crdt-blocks.ts +27 -22
  94. package/src/utils/crdt.ts +2 -1
  95. package/src/utils/index.js +1 -0
  96. package/src/utils/save-crdt-doc.js +64 -0
  97. package/src/utils/test/crdt-blocks.ts +57 -2
  98. package/src/utils/test/rtc-rich-text-cursor-scope.test.js +2 -2
  99. package/src/utils/test/save-crdt-doc.js +185 -0
@@ -170,7 +170,8 @@ function mergeContentWithoutBlocks(ymap, rawContent, cursorPosition) {
170
170
  (0, import_crdt_blocks.mergeCrdtBlocks)(
171
171
  currentBlocks,
172
172
  (0, import_blocks.parse)(rawContent),
173
- cursorPosition
173
+ cursorPosition,
174
+ { preserveClientIds: true }
174
175
  );
175
176
  }
176
177
  function parseCursorSelection(selection) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/crdt.ts"],
4
- "sourcesContent": ["/**\n * External dependencies\n */\nimport fastDeepEqual from 'fast-deep-equal/es6/index.js';\n\n/**\n * WordPress dependencies\n */\nimport {\n\t__unstableSerializeAndClean,\n\tparse,\n\ttype Block as WPBlock,\n} from '@wordpress/blocks';\nimport {\n\ttype CRDTDoc,\n\ttype ObjectData,\n\ttype ObjectID,\n\ttype ObjectType,\n\ttype SyncConfig,\n\tY,\n} from '@wordpress/sync';\n\n/**\n * Internal dependencies\n */\nimport { BaseAwareness } from '../awareness/base-awareness';\nimport {\n\ttype Block,\n\tdeserializeBlockAttributes,\n\tmergeCrdtBlocks,\n\ttype MergeCursorPosition,\n\tmergeRichTextUpdate,\n\ttype YBlock,\n\ttype YBlocks,\n} from './crdt-blocks';\nimport { type Post } from '../entity-types/post';\nimport { CRDT_DOC_META_PERSISTENCE_KEY, CRDT_RECORD_MAP_KEY } from '../sync';\nimport type { WPSelection } from '../types';\nimport {\n\tgetSelectionHistory,\n\tgetShiftedSelection,\n\tupdateSelectionHistory,\n} from './crdt-selection';\nimport {\n\tasRichTextOffset,\n\tcreateYMap,\n\tgetRootMap,\n\tisYMap,\n\ttype YMapRecord,\n\ttype YMapWrap,\n} from './crdt-utils';\n\n// A function that derives content from blocks. Two callers produce this:\n// `useEntityBlockEditor` reads blocks from its argument (so the optional arg\n// lets it accept whatever caller is invoked with), and the receiver-side\n// injection in this file captures blocks in a closure and ignores the arg.\ntype ContentFromBlocksFn = ( args?: { blocks: Block[] } ) => string;\n\n// Changes that can be applied to a post entity record.\nexport type PostChanges = Partial< Post > & {\n\tblocks?: Block[];\n\tcontent?: Post[ 'content' ] | string | ContentFromBlocksFn;\n\texcerpt?: Post[ 'excerpt' ] | string;\n\tselection?: WPSelection;\n\ttitle?: Post[ 'title' ] | string;\n};\n\n// A post record as represented in the CRDT document (Y.Map).\nexport interface YPostRecord extends YMapRecord {\n\tauthor: number;\n\t// Blocks are undefined when they need to be re-parsed from content.\n\tblocks: YBlocks | undefined;\n\tcontent: Y.Text;\n\tcategories: number[];\n\tcomment_status: string;\n\tdate: string | null;\n\texcerpt: Y.Text;\n\tfeatured_media: number;\n\tformat: string;\n\tmeta: YMapWrap< YMapRecord >;\n\tping_status: string;\n\tslug: string;\n\tstatus: string;\n\tsticky: boolean;\n\ttags: number[];\n\ttemplate: string;\n\ttitle: Y.Text;\n}\n\nexport const POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE = '_crdt_document';\n\n// Post meta keys that should *not* be synced.\nconst disallowedPostMetaKeys = new Set< string >( [\n\tPOST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,\n] );\n\n/**\n * Given a set of local changes to a generic entity record, apply those changes\n * to the local Y.Doc.\n *\n * @param {CRDTDoc} ydoc\n * @param {Partial< ObjectData >} changes\n * @return {void}\n */\nfunction defaultApplyChangesToCRDTDoc(\n\tydoc: CRDTDoc,\n\tchanges: ObjectData\n): void {\n\tconst ymap = getRootMap( ydoc, CRDT_RECORD_MAP_KEY );\n\n\tObject.entries( changes ).forEach( ( [ key, newValue ] ) => {\n\t\t// Cannot serialize function values, so cannot sync them.\n\t\tif ( 'function' === typeof newValue ) {\n\t\t\treturn;\n\t\t}\n\n\t\tswitch ( key ) {\n\t\t\t// Add support for additional data types here.\n\n\t\t\tdefault: {\n\t\t\t\tconst currentValue = ymap.get( key );\n\t\t\t\tupdateMapValue( ymap, key, currentValue, newValue );\n\t\t\t}\n\t\t}\n\t} );\n}\n\n/**\n * Given a set of local changes to a post record, apply those changes to the\n * local Y.Doc.\n *\n * @param {CRDTDoc} ydoc\n * @param {PostChanges} changes\n * @param {Set<string>} syncedProperties\n * @return {void}\n */\nexport function applyPostChangesToCRDTDoc(\n\tydoc: CRDTDoc,\n\tchanges: PostChanges,\n\tsyncedProperties: Set< string >\n): void {\n\tconst ymap = getRootMap< YPostRecord >( ydoc, CRDT_RECORD_MAP_KEY );\n\n\tObject.keys( changes ).forEach( ( key ) => {\n\t\tif ( ! syncedProperties.has( key ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst newValue = changes[ key ];\n\n\t\t// Cannot serialize function values, so cannot sync them. `content` is\n\t\t// often passed as a lazy serializer by `useEntityBlockEditor`; the\n\t\t// receiver re-derives it from the synced blocks (see\n\t\t// getPostChangesFromCRDTDoc), so dropping it here is intentional.\n\t\tif ( 'function' === typeof newValue ) {\n\t\t\treturn;\n\t\t}\n\n\t\tswitch ( key ) {\n\t\t\tcase 'blocks': {\n\t\t\t\t// Block changes from typing are bundled with a 'selection' update.\n\t\t\t\t// Use the resulting cursor position for block merging.\n\t\t\t\tconst newCursorPosition = parseCursorSelection(\n\t\t\t\t\tchanges.selection\n\t\t\t\t);\n\n\t\t\t\t// Blocks are undefined when they need to be re-parsed from content.\n\t\t\t\t// When new content is also part of this change (e.g. the Code\n\t\t\t\t// Editor dispatching `{ content, blocks: undefined }` on every\n\t\t\t\t// keystroke), derive blocks from content so the merge keeps\n\t\t\t\t// stable YBlock identities for unchanged blocks.\n\n\t\t\t\tconst rawContent = getRawValue( changes.content );\n\t\t\t\tif ( ! newValue && typeof rawContent === 'string' ) {\n\t\t\t\t\t// We have no blocks but an updated content string.\n\t\t\t\t\tmergeContentWithoutBlocks(\n\t\t\t\t\t\tymap,\n\t\t\t\t\t\trawContent,\n\t\t\t\t\t\tnewCursorPosition\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t} else if ( ! newValue ) {\n\t\t\t\t\t// We have an update containing empty blocks and content.\n\t\t\t\t\t// Set to undefined instead of deleting the key. This is important\n\t\t\t\t\t// since we iterate over the Y.Map keys in getPostChangesFromCRDTDoc.\n\t\t\t\t\tymap.set( key, undefined );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tlet currentBlocks = ymap.get( key );\n\n\t\t\t\t// Initialize.\n\t\t\t\tif ( ! ( currentBlocks instanceof Y.Array ) ) {\n\t\t\t\t\tcurrentBlocks = new Y.Array< YBlock >();\n\t\t\t\t\tymap.set( key, currentBlocks );\n\t\t\t\t}\n\n\t\t\t\t// Merge blocks does not need `setValue` because it is operating on a\n\t\t\t\t// Yjs type that is already in the Y.Doc.\n\t\t\t\tmergeCrdtBlocks( currentBlocks, newValue, newCursorPosition );\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'content':\n\t\t\tcase 'excerpt':\n\t\t\tcase 'title': {\n\t\t\t\tconst currentValue = ymap.get( key );\n\t\t\t\tlet rawValue = getRawValue( newValue );\n\n\t\t\t\t// Copy logic from prePersistPostType to ensure that the \"Auto\n\t\t\t\t// Draft\" template title is not synced.\n\t\t\t\tif (\n\t\t\t\t\tkey === 'title' &&\n\t\t\t\t\t! currentValue?.toString() &&\n\t\t\t\t\t'Auto Draft' === rawValue\n\t\t\t\t) {\n\t\t\t\t\trawValue = '';\n\t\t\t\t}\n\n\t\t\t\tif ( currentValue instanceof Y.Text ) {\n\t\t\t\t\tmergeRichTextUpdate( currentValue, rawValue ?? '' );\n\t\t\t\t} else {\n\t\t\t\t\tconst newYText = new Y.Text( rawValue ?? '' );\n\t\t\t\t\tymap.set( key, newYText );\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// \"Meta\" is overloaded term; here, it refers to post meta.\n\t\t\tcase 'meta': {\n\t\t\t\tlet metaMap = ymap.get( 'meta' );\n\n\t\t\t\t// Initialize.\n\t\t\t\tif ( ! isYMap( metaMap ) ) {\n\t\t\t\t\tmetaMap = createYMap< YMapRecord >();\n\t\t\t\t\tymap.set( 'meta', metaMap );\n\t\t\t\t}\n\n\t\t\t\t// Iterate over each meta property in the new value and merge it if it\n\t\t\t\t// should be synced.\n\t\t\t\tObject.entries( newValue ?? {} ).forEach(\n\t\t\t\t\t( [ metaKey, metaValue ] ) => {\n\t\t\t\t\t\tif ( disallowedPostMetaKeys.has( metaKey ) ) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tupdateMapValue(\n\t\t\t\t\t\t\tmetaMap,\n\t\t\t\t\t\t\tmetaKey,\n\t\t\t\t\t\t\tmetaMap.get( metaKey ), // current value in CRDT\n\t\t\t\t\t\t\tmetaValue // new value from changes\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'slug': {\n\t\t\t\t// Do not sync an empty slug. This indicates that the post is using\n\t\t\t\t// the default auto-generated slug.\n\t\t\t\tif ( ! newValue ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tconst currentValue = ymap.get( key );\n\t\t\t\tupdateMapValue( ymap, key, currentValue, newValue );\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Add support for additional properties here.\n\n\t\t\tdefault: {\n\t\t\t\tconst currentValue = ymap.get( key );\n\t\t\t\tupdateMapValue( ymap, key, currentValue, newValue );\n\t\t\t}\n\t\t}\n\t} );\n\n\t// Process changes that we don't want to persist to the CRDT document.\n\tif ( changes.selection ) {\n\t\tconst selection = changes.selection;\n\t\t// Persist selection changes at the end of the current event loop.\n\t\t// This allows undo meta to be saved with the current selection before\n\t\t// it is overwritten by the new selection from Gutenberg.\n\t\t// Without this, selection history will already contain the latest\n\t\t// selection (after this change) when the undo stack is saved.\n\t\tsetTimeout( () => {\n\t\t\tupdateSelectionHistory( ydoc, selection );\n\t\t}, 0 );\n\t}\n}\n\n/**\n * Derive blocks from a raw content string and merge them into the post's\n * blocks Y.Array. Used when a caller dispatches a change with `blocks:\n * undefined` alongside new content, most notably the Code Editor's\n * per-keystroke dispatch.\n *\n * @param ymap The post's root Y.Map.\n * @param rawContent The raw HTML content to parse.\n * @param cursorPosition Cursor position derived from the change's selection,\n * used by mergeCrdtBlocks for rich-text cursor hints.\n */\nfunction mergeContentWithoutBlocks(\n\tymap: YMapWrap< YPostRecord >,\n\trawContent: string,\n\tcursorPosition: MergeCursorPosition\n): void {\n\tlet currentBlocks = ymap.get( 'blocks' );\n\n\tif ( ! ( currentBlocks instanceof Y.Array ) ) {\n\t\tcurrentBlocks = new Y.Array< YBlock >();\n\t\tymap.set( 'blocks', currentBlocks );\n\t}\n\n\tmergeCrdtBlocks(\n\t\tcurrentBlocks,\n\t\tparse( rawContent ) as Block[],\n\t\tcursorPosition\n\t);\n}\n\n/**\n * Only returns a selection object if it describes a selection within a block, with\n * a cursor inside a RichText field associated with one of that block\u2019s attributes.\n *\n * @param selection Selection object which might represent a selection within a block,\n * within a RichText field associated with a particular attribute of\n * that block, or none at all.\n */\nfunction parseCursorSelection( selection?: WPSelection ): MergeCursorPosition {\n\tconst selectionStart = selection?.selectionStart;\n\n\treturn selectionStart?.clientId &&\n\t\tselectionStart.attributeKey &&\n\t\t'number' === typeof selectionStart.offset &&\n\t\tNumber.isInteger( selectionStart.offset )\n\t\t? {\n\t\t\t\tattributeKey: selectionStart.attributeKey,\n\t\t\t\tclientId: selectionStart.clientId,\n\t\t\t\toffset: asRichTextOffset( selectionStart.offset ),\n\t\t }\n\t\t: null;\n}\n\nfunction defaultGetChangesFromCRDTDoc( crdtDoc: CRDTDoc ): ObjectData {\n\treturn getRootMap( crdtDoc, CRDT_RECORD_MAP_KEY ).toJSON();\n}\n\n/**\n * Given a local Y.Doc that *may* contain changes from remote peers, compare\n * against the local record and determine if there are changes (edits) we want\n * to dispatch.\n *\n * @param {CRDTDoc} ydoc\n * @param {Post} editedRecord\n * @param {Set<string>} syncedProperties\n * @return {Partial<PostChanges>} The changes that should be applied to the local record.\n */\nexport function getPostChangesFromCRDTDoc(\n\tydoc: CRDTDoc,\n\teditedRecord: Post,\n\tsyncedProperties: Set< string >\n): PostChanges {\n\tconst ymap = getRootMap< YPostRecord >( ydoc, CRDT_RECORD_MAP_KEY );\n\n\tlet allowedMetaChanges: Post[ 'meta' ] = {};\n\n\tconst changes = Object.fromEntries(\n\t\tObject.entries( ymap.toJSON() ).filter( ( [ key, newValue ] ) => {\n\t\t\tif ( ! syncedProperties.has( key ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst currentValue = editedRecord[ key ];\n\n\t\t\tswitch ( key ) {\n\t\t\t\tcase 'blocks': {\n\t\t\t\t\t// When we are passed a persisted CRDT document, make a special\n\t\t\t\t\t// comparison of the content and blocks.\n\t\t\t\t\t//\n\t\t\t\t\t// When other fields (besides `blocks`) are mutated outside the block\n\t\t\t\t\t// editor, the change is caught by an equality check (see other cases\n\t\t\t\t\t// in this `switch` statement). As a transient property, `blocks`\n\t\t\t\t\t// cannot be directly mutated outside the block editor -- only\n\t\t\t\t\t// `content` can.\n\t\t\t\t\t//\n\t\t\t\t\t// Therefore, for this special comparison, we serialize the `blocks`\n\t\t\t\t\t// from the persisted CRDT document and compare that to the content\n\t\t\t\t\t// from the persisted record. If they differ, we know that the content\n\t\t\t\t\t// in the database has changed, and therefore the blocks have changed.\n\t\t\t\t\t//\n\t\t\t\t\t// We cannot directly compare the `blocks` from the CRDT document to\n\t\t\t\t\t// the `blocks` derived from the `content` in the persisted record,\n\t\t\t\t\t// because the latter will have different client IDs.\n\t\t\t\t\tif (\n\t\t\t\t\t\tydoc.meta?.get( CRDT_DOC_META_PERSISTENCE_KEY ) &&\n\t\t\t\t\t\teditedRecord.content\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst blocksJson = ymap.get( 'blocks' )?.toJSON() ?? [];\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t__unstableSerializeAndClean( blocksJson ).trim() !==\n\t\t\t\t\t\t\tgetRawValue( editedRecord.content )\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tcase 'date': {\n\t\t\t\t\t// Do not overwrite a \"floating\" date. Borrowing logic from the\n\t\t\t\t\t// isEditedPostDateFloating selector.\n\t\t\t\t\tconst currentDateIsFloating =\n\t\t\t\t\t\tnull === currentValue ||\n\t\t\t\t\t\teditedRecord.modified === currentValue;\n\n\t\t\t\t\tif ( currentDateIsFloating ) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn haveValuesChanged( currentValue, newValue );\n\t\t\t\t}\n\n\t\t\t\tcase 'meta': {\n\t\t\t\t\tconst currentMeta =\n\t\t\t\t\t\t( currentValue as PostChanges[ 'meta' ] ) ?? {};\n\n\t\t\t\t\tallowedMetaChanges = Object.fromEntries(\n\t\t\t\t\t\tObject.entries( newValue ?? {} ).filter(\n\t\t\t\t\t\t\t( [ metaKey ] ) => {\n\t\t\t\t\t\t\t\tif ( disallowedPostMetaKeys.has( metaKey ) ) {\n\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Ignore meta keys that are no longer registered\n\t\t\t\t\t\t\t\t// for this post (absent from the REST response).\n\t\t\t\t\t\t\t\t// Without this, orphaned CRDT meta would mark\n\t\t\t\t\t\t\t\t// the post permanently dirty.\n\t\t\t\t\t\t\t\treturn metaKey in currentMeta;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\n\t\t\t\t\t// Merge the allowed meta changes with the current meta values since\n\t\t\t\t\t// not all meta properties are synced.\n\t\t\t\t\tconst mergedValue = {\n\t\t\t\t\t\t...currentMeta,\n\t\t\t\t\t\t...allowedMetaChanges,\n\t\t\t\t\t};\n\n\t\t\t\t\treturn haveValuesChanged( currentValue, mergedValue );\n\t\t\t\t}\n\n\t\t\t\tcase 'status': {\n\t\t\t\t\t// Do not sync an invalid status.\n\t\t\t\t\tif ( 'auto-draft' === newValue ) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn haveValuesChanged( currentValue, newValue );\n\t\t\t\t}\n\n\t\t\t\tcase 'content':\n\t\t\t\tcase 'excerpt':\n\t\t\t\tcase 'title': {\n\t\t\t\t\treturn haveValuesChanged(\n\t\t\t\t\t\tgetRawValue( currentValue ),\n\t\t\t\t\t\tnewValue\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Add support for additional data types here.\n\n\t\t\t\tdefault: {\n\t\t\t\t\treturn haveValuesChanged( currentValue, newValue );\n\t\t\t\t}\n\t\t\t}\n\t\t} )\n\t);\n\n\t// Blocks extracted from the CRDT document have rich-text attributes as\n\t// plain strings (from Y.Text.toJSON()). Convert them back to RichTextData\n\t// so block edit components receive the same types as locally-created blocks.\n\tif ( changes.blocks ) {\n\t\tchanges.blocks = deserializeBlockAttributes(\n\t\t\tchanges.blocks as Block[]\n\t\t);\n\t}\n\n\t// When blocks changed but content didn't (the sender internally used a lazy\n\t// serializer function), inject a closure that captures the synced blocks\n\t// and serializes them on demand. Mirrors what useEntityBlockEditor does\n\t// locally. A fresh function on every persistent edit marks the entity\n\t// dirty (so the save button reactivates for peers), while serialization\n\t// stays lazy (only runs when getEditedPostContent reads it). The closure\n\t// captures `capturedBlocks` so the right content is returned even if the\n\t// caller later clears `record.blocks` (e.g. the Code Editor re-parsing\n\t// from content).\n\tif ( changes.blocks && ! changes.content ) {\n\t\tconst capturedBlocks = changes.blocks;\n\t\tchanges.content = () =>\n\t\t\t__unstableSerializeAndClean( capturedBlocks as WPBlock[] );\n\t}\n\n\t// Meta changes must be merged with the edited record since not all meta\n\t// properties are synced.\n\tif ( 'object' === typeof changes.meta ) {\n\t\tchanges.meta = {\n\t\t\t...editedRecord.meta,\n\t\t\t...allowedMetaChanges,\n\t\t};\n\t}\n\n\t// When remote content changes are detected, recalculate the local user's\n\t// selection using Y.RelativePosition to account for text shifts. The ydoc\n\t// has already been updated with remote content at this point, so converting\n\t// relative positions to absolute gives corrected offsets. Including the\n\t// selection in PostChanges ensures it dispatches atomically with content.\n\tconst selectionHistory = getSelectionHistory( ydoc );\n\tconst shiftedSelection = getShiftedSelection( ydoc, selectionHistory );\n\tif ( shiftedSelection ) {\n\t\tchanges.selection = {\n\t\t\t...shiftedSelection,\n\t\t\tinitialPosition: 0,\n\t\t};\n\t}\n\n\treturn changes;\n}\n\n/**\n * This default sync config can be used for entities that are flat maps of\n * primitive values and do not require custom logic to merge changes.\n */\nexport const defaultSyncConfig: SyncConfig = {\n\tapplyChangesToCRDTDoc: defaultApplyChangesToCRDTDoc,\n\tcreateAwareness: ( ydoc: CRDTDoc ) => new BaseAwareness( ydoc ),\n\tgetChangesFromCRDTDoc: defaultGetChangesFromCRDTDoc,\n};\n\n/**\n * This default collection sync config can be used to sync entity collections\n * (e.g., block comments) where we are not interested in merging changes at the\n * individual record level, but instead want to replace the entire collection\n * when changes are detected.\n */\nexport const defaultCollectionSyncConfig: SyncConfig = {\n\tapplyChangesToCRDTDoc: () => {},\n\tgetChangesFromCRDTDoc: () => ( {} ),\n\tshouldSync: ( _: ObjectType, objectId: ObjectID | null ) =>\n\t\tnull === objectId,\n};\n\n/**\n * Extract the raw string value from a property that may be a string or an object\n * with a `raw` property (`RenderedText`).\n *\n * @param {unknown} value The value to extract from.\n * @return {string|undefined} The raw string value, or undefined if it could not be determined.\n */\nfunction getRawValue( value?: unknown ): string | undefined {\n\t// Value may be a string property or a nested object with a `raw` property.\n\tif ( 'string' === typeof value ) {\n\t\treturn value;\n\t}\n\n\tif (\n\t\tvalue &&\n\t\t'object' === typeof value &&\n\t\t'raw' in value &&\n\t\t'string' === typeof value.raw\n\t) {\n\t\treturn value.raw;\n\t}\n\n\treturn undefined;\n}\n\nfunction haveValuesChanged< ValueType >(\n\tcurrentValue: ValueType | undefined,\n\tnewValue: ValueType | undefined\n): boolean {\n\treturn ! fastDeepEqual( currentValue, newValue );\n}\n\nfunction updateMapValue< T extends YMapRecord, K extends keyof T >(\n\tmap: YMapWrap< T >,\n\tkey: K,\n\tcurrentValue: T[ K ] | undefined,\n\tnewValue: T[ K ] | undefined\n): void {\n\tif ( undefined === newValue ) {\n\t\tmap.delete( key );\n\t\treturn;\n\t}\n\n\tif ( haveValuesChanged< T[ K ] >( currentValue, newValue ) ) {\n\t\tmap.set( key, newValue );\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * External dependencies\n */\nimport fastDeepEqual from 'fast-deep-equal/es6/index.js';\n\n/**\n * WordPress dependencies\n */\nimport {\n\t__unstableSerializeAndClean,\n\tparse,\n\ttype Block as WPBlock,\n} from '@wordpress/blocks';\nimport {\n\ttype CRDTDoc,\n\ttype ObjectData,\n\ttype ObjectID,\n\ttype ObjectType,\n\ttype SyncConfig,\n\tY,\n} from '@wordpress/sync';\n\n/**\n * Internal dependencies\n */\nimport { BaseAwareness } from '../awareness/base-awareness';\nimport {\n\ttype Block,\n\tdeserializeBlockAttributes,\n\tmergeCrdtBlocks,\n\ttype MergeCursorPosition,\n\tmergeRichTextUpdate,\n\ttype YBlock,\n\ttype YBlocks,\n} from './crdt-blocks';\nimport { type Post } from '../entity-types/post';\nimport { CRDT_DOC_META_PERSISTENCE_KEY, CRDT_RECORD_MAP_KEY } from '../sync';\nimport type { WPSelection } from '../types';\nimport {\n\tgetSelectionHistory,\n\tgetShiftedSelection,\n\tupdateSelectionHistory,\n} from './crdt-selection';\nimport {\n\tasRichTextOffset,\n\tcreateYMap,\n\tgetRootMap,\n\tisYMap,\n\ttype YMapRecord,\n\ttype YMapWrap,\n} from './crdt-utils';\n\n// A function that derives content from blocks. Two callers produce this:\n// `useEntityBlockEditor` reads blocks from its argument (so the optional arg\n// lets it accept whatever caller is invoked with), and the receiver-side\n// injection in this file captures blocks in a closure and ignores the arg.\ntype ContentFromBlocksFn = ( args?: { blocks: Block[] } ) => string;\n\n// Changes that can be applied to a post entity record.\nexport type PostChanges = Partial< Post > & {\n\tblocks?: Block[];\n\tcontent?: Post[ 'content' ] | string | ContentFromBlocksFn;\n\texcerpt?: Post[ 'excerpt' ] | string;\n\tselection?: WPSelection;\n\ttitle?: Post[ 'title' ] | string;\n};\n\n// A post record as represented in the CRDT document (Y.Map).\nexport interface YPostRecord extends YMapRecord {\n\tauthor: number;\n\t// Blocks are undefined when they need to be re-parsed from content.\n\tblocks: YBlocks | undefined;\n\tcontent: Y.Text;\n\tcategories: number[];\n\tcomment_status: string;\n\tdate: string | null;\n\texcerpt: Y.Text;\n\tfeatured_media: number;\n\tformat: string;\n\tmeta: YMapWrap< YMapRecord >;\n\tping_status: string;\n\tslug: string;\n\tstatus: string;\n\tsticky: boolean;\n\ttags: number[];\n\ttemplate: string;\n\ttitle: Y.Text;\n}\n\nexport const POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE = '_crdt_document';\n\n// Post meta keys that should *not* be synced.\nconst disallowedPostMetaKeys = new Set< string >( [\n\tPOST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,\n] );\n\n/**\n * Given a set of local changes to a generic entity record, apply those changes\n * to the local Y.Doc.\n *\n * @param {CRDTDoc} ydoc\n * @param {Partial< ObjectData >} changes\n * @return {void}\n */\nfunction defaultApplyChangesToCRDTDoc(\n\tydoc: CRDTDoc,\n\tchanges: ObjectData\n): void {\n\tconst ymap = getRootMap( ydoc, CRDT_RECORD_MAP_KEY );\n\n\tObject.entries( changes ).forEach( ( [ key, newValue ] ) => {\n\t\t// Cannot serialize function values, so cannot sync them.\n\t\tif ( 'function' === typeof newValue ) {\n\t\t\treturn;\n\t\t}\n\n\t\tswitch ( key ) {\n\t\t\t// Add support for additional data types here.\n\n\t\t\tdefault: {\n\t\t\t\tconst currentValue = ymap.get( key );\n\t\t\t\tupdateMapValue( ymap, key, currentValue, newValue );\n\t\t\t}\n\t\t}\n\t} );\n}\n\n/**\n * Given a set of local changes to a post record, apply those changes to the\n * local Y.Doc.\n *\n * @param {CRDTDoc} ydoc\n * @param {PostChanges} changes\n * @param {Set<string>} syncedProperties\n * @return {void}\n */\nexport function applyPostChangesToCRDTDoc(\n\tydoc: CRDTDoc,\n\tchanges: PostChanges,\n\tsyncedProperties: Set< string >\n): void {\n\tconst ymap = getRootMap< YPostRecord >( ydoc, CRDT_RECORD_MAP_KEY );\n\n\tObject.keys( changes ).forEach( ( key ) => {\n\t\tif ( ! syncedProperties.has( key ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst newValue = changes[ key ];\n\n\t\t// Cannot serialize function values, so cannot sync them. `content` is\n\t\t// often passed as a lazy serializer by `useEntityBlockEditor`; the\n\t\t// receiver re-derives it from the synced blocks (see\n\t\t// getPostChangesFromCRDTDoc), so dropping it here is intentional.\n\t\tif ( 'function' === typeof newValue ) {\n\t\t\treturn;\n\t\t}\n\n\t\tswitch ( key ) {\n\t\t\tcase 'blocks': {\n\t\t\t\t// Block changes from typing are bundled with a 'selection' update.\n\t\t\t\t// Use the resulting cursor position for block merging.\n\t\t\t\tconst newCursorPosition = parseCursorSelection(\n\t\t\t\t\tchanges.selection\n\t\t\t\t);\n\n\t\t\t\t// Blocks are undefined when they need to be re-parsed from content.\n\t\t\t\t// When new content is also part of this change (e.g. the Code\n\t\t\t\t// Editor dispatching `{ content, blocks: undefined }` on every\n\t\t\t\t// keystroke), derive blocks from content so the merge keeps\n\t\t\t\t// stable YBlock identities for unchanged blocks.\n\n\t\t\t\tconst rawContent = getRawValue( changes.content );\n\t\t\t\tif ( ! newValue && typeof rawContent === 'string' ) {\n\t\t\t\t\t// We have no blocks but an updated content string.\n\t\t\t\t\tmergeContentWithoutBlocks(\n\t\t\t\t\t\tymap,\n\t\t\t\t\t\trawContent,\n\t\t\t\t\t\tnewCursorPosition\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t} else if ( ! newValue ) {\n\t\t\t\t\t// We have an update containing empty blocks and content.\n\t\t\t\t\t// Set to undefined instead of deleting the key. This is important\n\t\t\t\t\t// since we iterate over the Y.Map keys in getPostChangesFromCRDTDoc.\n\t\t\t\t\tymap.set( key, undefined );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tlet currentBlocks = ymap.get( key );\n\n\t\t\t\t// Initialize.\n\t\t\t\tif ( ! ( currentBlocks instanceof Y.Array ) ) {\n\t\t\t\t\tcurrentBlocks = new Y.Array< YBlock >();\n\t\t\t\t\tymap.set( key, currentBlocks );\n\t\t\t\t}\n\n\t\t\t\t// Merge blocks does not need `setValue` because it is operating on a\n\t\t\t\t// Yjs type that is already in the Y.Doc.\n\t\t\t\tmergeCrdtBlocks( currentBlocks, newValue, newCursorPosition );\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'content':\n\t\t\tcase 'excerpt':\n\t\t\tcase 'title': {\n\t\t\t\tconst currentValue = ymap.get( key );\n\t\t\t\tlet rawValue = getRawValue( newValue );\n\n\t\t\t\t// Copy logic from prePersistPostType to ensure that the \"Auto\n\t\t\t\t// Draft\" template title is not synced.\n\t\t\t\tif (\n\t\t\t\t\tkey === 'title' &&\n\t\t\t\t\t! currentValue?.toString() &&\n\t\t\t\t\t'Auto Draft' === rawValue\n\t\t\t\t) {\n\t\t\t\t\trawValue = '';\n\t\t\t\t}\n\n\t\t\t\tif ( currentValue instanceof Y.Text ) {\n\t\t\t\t\tmergeRichTextUpdate( currentValue, rawValue ?? '' );\n\t\t\t\t} else {\n\t\t\t\t\tconst newYText = new Y.Text( rawValue ?? '' );\n\t\t\t\t\tymap.set( key, newYText );\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// \"Meta\" is overloaded term; here, it refers to post meta.\n\t\t\tcase 'meta': {\n\t\t\t\tlet metaMap = ymap.get( 'meta' );\n\n\t\t\t\t// Initialize.\n\t\t\t\tif ( ! isYMap( metaMap ) ) {\n\t\t\t\t\tmetaMap = createYMap< YMapRecord >();\n\t\t\t\t\tymap.set( 'meta', metaMap );\n\t\t\t\t}\n\n\t\t\t\t// Iterate over each meta property in the new value and merge it if it\n\t\t\t\t// should be synced.\n\t\t\t\tObject.entries( newValue ?? {} ).forEach(\n\t\t\t\t\t( [ metaKey, metaValue ] ) => {\n\t\t\t\t\t\tif ( disallowedPostMetaKeys.has( metaKey ) ) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tupdateMapValue(\n\t\t\t\t\t\t\tmetaMap,\n\t\t\t\t\t\t\tmetaKey,\n\t\t\t\t\t\t\tmetaMap.get( metaKey ), // current value in CRDT\n\t\t\t\t\t\t\tmetaValue // new value from changes\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'slug': {\n\t\t\t\t// Do not sync an empty slug. This indicates that the post is using\n\t\t\t\t// the default auto-generated slug.\n\t\t\t\tif ( ! newValue ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tconst currentValue = ymap.get( key );\n\t\t\t\tupdateMapValue( ymap, key, currentValue, newValue );\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Add support for additional properties here.\n\n\t\t\tdefault: {\n\t\t\t\tconst currentValue = ymap.get( key );\n\t\t\t\tupdateMapValue( ymap, key, currentValue, newValue );\n\t\t\t}\n\t\t}\n\t} );\n\n\t// Process changes that we don't want to persist to the CRDT document.\n\tif ( changes.selection ) {\n\t\tconst selection = changes.selection;\n\t\t// Persist selection changes at the end of the current event loop.\n\t\t// This allows undo meta to be saved with the current selection before\n\t\t// it is overwritten by the new selection from Gutenberg.\n\t\t// Without this, selection history will already contain the latest\n\t\t// selection (after this change) when the undo stack is saved.\n\t\tsetTimeout( () => {\n\t\t\tupdateSelectionHistory( ydoc, selection );\n\t\t}, 0 );\n\t}\n}\n\n/**\n * Derive blocks from a raw content string and merge them into the post's\n * blocks Y.Array. Used when a caller dispatches a change with `blocks:\n * undefined` alongside new content, most notably the Code Editor's\n * per-keystroke dispatch.\n *\n * @param ymap The post's root Y.Map.\n * @param rawContent The raw HTML content to parse.\n * @param cursorPosition Cursor position derived from the change's selection,\n * used by mergeCrdtBlocks for rich-text cursor hints.\n */\nfunction mergeContentWithoutBlocks(\n\tymap: YMapWrap< YPostRecord >,\n\trawContent: string,\n\tcursorPosition: MergeCursorPosition\n): void {\n\tlet currentBlocks = ymap.get( 'blocks' );\n\n\tif ( ! ( currentBlocks instanceof Y.Array ) ) {\n\t\tcurrentBlocks = new Y.Array< YBlock >();\n\t\tymap.set( 'blocks', currentBlocks );\n\t}\n\n\tmergeCrdtBlocks(\n\t\tcurrentBlocks,\n\t\tparse( rawContent ) as Block[],\n\t\tcursorPosition,\n\t\t{ preserveClientIds: true }\n\t);\n}\n\n/**\n * Only returns a selection object if it describes a selection within a block, with\n * a cursor inside a RichText field associated with one of that block\u2019s attributes.\n *\n * @param selection Selection object which might represent a selection within a block,\n * within a RichText field associated with a particular attribute of\n * that block, or none at all.\n */\nfunction parseCursorSelection( selection?: WPSelection ): MergeCursorPosition {\n\tconst selectionStart = selection?.selectionStart;\n\n\treturn selectionStart?.clientId &&\n\t\tselectionStart.attributeKey &&\n\t\t'number' === typeof selectionStart.offset &&\n\t\tNumber.isInteger( selectionStart.offset )\n\t\t? {\n\t\t\t\tattributeKey: selectionStart.attributeKey,\n\t\t\t\tclientId: selectionStart.clientId,\n\t\t\t\toffset: asRichTextOffset( selectionStart.offset ),\n\t\t }\n\t\t: null;\n}\n\nfunction defaultGetChangesFromCRDTDoc( crdtDoc: CRDTDoc ): ObjectData {\n\treturn getRootMap( crdtDoc, CRDT_RECORD_MAP_KEY ).toJSON();\n}\n\n/**\n * Given a local Y.Doc that *may* contain changes from remote peers, compare\n * against the local record and determine if there are changes (edits) we want\n * to dispatch.\n *\n * @param {CRDTDoc} ydoc\n * @param {Post} editedRecord\n * @param {Set<string>} syncedProperties\n * @return {Partial<PostChanges>} The changes that should be applied to the local record.\n */\nexport function getPostChangesFromCRDTDoc(\n\tydoc: CRDTDoc,\n\teditedRecord: Post,\n\tsyncedProperties: Set< string >\n): PostChanges {\n\tconst ymap = getRootMap< YPostRecord >( ydoc, CRDT_RECORD_MAP_KEY );\n\n\tlet allowedMetaChanges: Post[ 'meta' ] = {};\n\n\tconst changes = Object.fromEntries(\n\t\tObject.entries( ymap.toJSON() ).filter( ( [ key, newValue ] ) => {\n\t\t\tif ( ! syncedProperties.has( key ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst currentValue = editedRecord[ key ];\n\n\t\t\tswitch ( key ) {\n\t\t\t\tcase 'blocks': {\n\t\t\t\t\t// When we are passed a persisted CRDT document, make a special\n\t\t\t\t\t// comparison of the content and blocks.\n\t\t\t\t\t//\n\t\t\t\t\t// When other fields (besides `blocks`) are mutated outside the block\n\t\t\t\t\t// editor, the change is caught by an equality check (see other cases\n\t\t\t\t\t// in this `switch` statement). As a transient property, `blocks`\n\t\t\t\t\t// cannot be directly mutated outside the block editor -- only\n\t\t\t\t\t// `content` can.\n\t\t\t\t\t//\n\t\t\t\t\t// Therefore, for this special comparison, we serialize the `blocks`\n\t\t\t\t\t// from the persisted CRDT document and compare that to the content\n\t\t\t\t\t// from the persisted record. If they differ, we know that the content\n\t\t\t\t\t// in the database has changed, and therefore the blocks have changed.\n\t\t\t\t\t//\n\t\t\t\t\t// We cannot directly compare the `blocks` from the CRDT document to\n\t\t\t\t\t// the `blocks` derived from the `content` in the persisted record,\n\t\t\t\t\t// because the latter will have different client IDs.\n\t\t\t\t\tif (\n\t\t\t\t\t\tydoc.meta?.get( CRDT_DOC_META_PERSISTENCE_KEY ) &&\n\t\t\t\t\t\teditedRecord.content\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst blocksJson = ymap.get( 'blocks' )?.toJSON() ?? [];\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t__unstableSerializeAndClean( blocksJson ).trim() !==\n\t\t\t\t\t\t\tgetRawValue( editedRecord.content )\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tcase 'date': {\n\t\t\t\t\t// Do not overwrite a \"floating\" date. Borrowing logic from the\n\t\t\t\t\t// isEditedPostDateFloating selector.\n\t\t\t\t\tconst currentDateIsFloating =\n\t\t\t\t\t\tnull === currentValue ||\n\t\t\t\t\t\teditedRecord.modified === currentValue;\n\n\t\t\t\t\tif ( currentDateIsFloating ) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn haveValuesChanged( currentValue, newValue );\n\t\t\t\t}\n\n\t\t\t\tcase 'meta': {\n\t\t\t\t\tconst currentMeta =\n\t\t\t\t\t\t( currentValue as PostChanges[ 'meta' ] ) ?? {};\n\n\t\t\t\t\tallowedMetaChanges = Object.fromEntries(\n\t\t\t\t\t\tObject.entries( newValue ?? {} ).filter(\n\t\t\t\t\t\t\t( [ metaKey ] ) => {\n\t\t\t\t\t\t\t\tif ( disallowedPostMetaKeys.has( metaKey ) ) {\n\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Ignore meta keys that are no longer registered\n\t\t\t\t\t\t\t\t// for this post (absent from the REST response).\n\t\t\t\t\t\t\t\t// Without this, orphaned CRDT meta would mark\n\t\t\t\t\t\t\t\t// the post permanently dirty.\n\t\t\t\t\t\t\t\treturn metaKey in currentMeta;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\n\t\t\t\t\t// Merge the allowed meta changes with the current meta values since\n\t\t\t\t\t// not all meta properties are synced.\n\t\t\t\t\tconst mergedValue = {\n\t\t\t\t\t\t...currentMeta,\n\t\t\t\t\t\t...allowedMetaChanges,\n\t\t\t\t\t};\n\n\t\t\t\t\treturn haveValuesChanged( currentValue, mergedValue );\n\t\t\t\t}\n\n\t\t\t\tcase 'status': {\n\t\t\t\t\t// Do not sync an invalid status.\n\t\t\t\t\tif ( 'auto-draft' === newValue ) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn haveValuesChanged( currentValue, newValue );\n\t\t\t\t}\n\n\t\t\t\tcase 'content':\n\t\t\t\tcase 'excerpt':\n\t\t\t\tcase 'title': {\n\t\t\t\t\treturn haveValuesChanged(\n\t\t\t\t\t\tgetRawValue( currentValue ),\n\t\t\t\t\t\tnewValue\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Add support for additional data types here.\n\n\t\t\t\tdefault: {\n\t\t\t\t\treturn haveValuesChanged( currentValue, newValue );\n\t\t\t\t}\n\t\t\t}\n\t\t} )\n\t);\n\n\t// Blocks extracted from the CRDT document have rich-text attributes as\n\t// plain strings (from Y.Text.toJSON()). Convert them back to RichTextData\n\t// so block edit components receive the same types as locally-created blocks.\n\tif ( changes.blocks ) {\n\t\tchanges.blocks = deserializeBlockAttributes(\n\t\t\tchanges.blocks as Block[]\n\t\t);\n\t}\n\n\t// When blocks changed but content didn't (the sender internally used a lazy\n\t// serializer function), inject a closure that captures the synced blocks\n\t// and serializes them on demand. Mirrors what useEntityBlockEditor does\n\t// locally. A fresh function on every persistent edit marks the entity\n\t// dirty (so the save button reactivates for peers), while serialization\n\t// stays lazy (only runs when getEditedPostContent reads it). The closure\n\t// captures `capturedBlocks` so the right content is returned even if the\n\t// caller later clears `record.blocks` (e.g. the Code Editor re-parsing\n\t// from content).\n\tif ( changes.blocks && ! changes.content ) {\n\t\tconst capturedBlocks = changes.blocks;\n\t\tchanges.content = () =>\n\t\t\t__unstableSerializeAndClean( capturedBlocks as WPBlock[] );\n\t}\n\n\t// Meta changes must be merged with the edited record since not all meta\n\t// properties are synced.\n\tif ( 'object' === typeof changes.meta ) {\n\t\tchanges.meta = {\n\t\t\t...editedRecord.meta,\n\t\t\t...allowedMetaChanges,\n\t\t};\n\t}\n\n\t// When remote content changes are detected, recalculate the local user's\n\t// selection using Y.RelativePosition to account for text shifts. The ydoc\n\t// has already been updated with remote content at this point, so converting\n\t// relative positions to absolute gives corrected offsets. Including the\n\t// selection in PostChanges ensures it dispatches atomically with content.\n\tconst selectionHistory = getSelectionHistory( ydoc );\n\tconst shiftedSelection = getShiftedSelection( ydoc, selectionHistory );\n\tif ( shiftedSelection ) {\n\t\tchanges.selection = {\n\t\t\t...shiftedSelection,\n\t\t\tinitialPosition: 0,\n\t\t};\n\t}\n\n\treturn changes;\n}\n\n/**\n * This default sync config can be used for entities that are flat maps of\n * primitive values and do not require custom logic to merge changes.\n */\nexport const defaultSyncConfig: SyncConfig = {\n\tapplyChangesToCRDTDoc: defaultApplyChangesToCRDTDoc,\n\tcreateAwareness: ( ydoc: CRDTDoc ) => new BaseAwareness( ydoc ),\n\tgetChangesFromCRDTDoc: defaultGetChangesFromCRDTDoc,\n};\n\n/**\n * This default collection sync config can be used to sync entity collections\n * (e.g., block comments) where we are not interested in merging changes at the\n * individual record level, but instead want to replace the entire collection\n * when changes are detected.\n */\nexport const defaultCollectionSyncConfig: SyncConfig = {\n\tapplyChangesToCRDTDoc: () => {},\n\tgetChangesFromCRDTDoc: () => ( {} ),\n\tshouldSync: ( _: ObjectType, objectId: ObjectID | null ) =>\n\t\tnull === objectId,\n};\n\n/**\n * Extract the raw string value from a property that may be a string or an object\n * with a `raw` property (`RenderedText`).\n *\n * @param {unknown} value The value to extract from.\n * @return {string|undefined} The raw string value, or undefined if it could not be determined.\n */\nfunction getRawValue( value?: unknown ): string | undefined {\n\t// Value may be a string property or a nested object with a `raw` property.\n\tif ( 'string' === typeof value ) {\n\t\treturn value;\n\t}\n\n\tif (\n\t\tvalue &&\n\t\t'object' === typeof value &&\n\t\t'raw' in value &&\n\t\t'string' === typeof value.raw\n\t) {\n\t\treturn value.raw;\n\t}\n\n\treturn undefined;\n}\n\nfunction haveValuesChanged< ValueType >(\n\tcurrentValue: ValueType | undefined,\n\tnewValue: ValueType | undefined\n): boolean {\n\treturn ! fastDeepEqual( currentValue, newValue );\n}\n\nfunction updateMapValue< T extends YMapRecord, K extends keyof T >(\n\tmap: YMapWrap< T >,\n\tkey: K,\n\tcurrentValue: T[ K ] | undefined,\n\tnewValue: T[ K ] | undefined\n): void {\n\tif ( undefined === newValue ) {\n\t\tmap.delete( key );\n\t\treturn;\n\t}\n\n\tif ( haveValuesChanged< T[ K ] >( currentValue, newValue ) ) {\n\t\tmap.set( key, newValue );\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["import_sync", "fastDeepEqual"]
7
7
  }
@@ -42,6 +42,7 @@ __export(utils_exports, {
42
42
  isNumericID: () => import_is_numeric_id.default,
43
43
  normalizeQueryForResolution: () => import_normalize_query_for_resolution.default,
44
44
  replaceAction: () => import_replace_action.default,
45
+ saveCRDTDoc: () => import_save_crdt_doc.saveCRDTDoc,
45
46
  setNestedValue: () => import_set_nested_value.default,
46
47
  withWeakMapCache: () => import_with_weak_map_cache.default
47
48
  });
@@ -58,6 +59,7 @@ var import_is_numeric_id = __toESM(require("./is-numeric-id.cjs"));
58
59
  var import_user_permissions = require("./user-permissions.cjs");
59
60
  var import_receive_intermediate_results = require("./receive-intermediate-results.cjs");
60
61
  var import_normalize_query_for_resolution = __toESM(require("./normalize-query-for-resolution.cjs"));
62
+ var import_save_crdt_doc = require("./save-crdt-doc.cjs");
61
63
  // Annotate the CommonJS export names for ESM import in node:
62
64
  0 && (module.exports = {
63
65
  ALLOWED_RESOURCE_ACTIONS,
@@ -72,6 +74,7 @@ var import_normalize_query_for_resolution = __toESM(require("./normalize-query-f
72
74
  isNumericID,
73
75
  normalizeQueryForResolution,
74
76
  replaceAction,
77
+ saveCRDTDoc,
75
78
  setNestedValue,
76
79
  withWeakMapCache
77
80
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/index.js"],
4
- "sourcesContent": ["export { default as conservativeMapItem } from './conservative-map-item';\nexport { default as getNormalizedCommaSeparable } from './get-normalized-comma-separable';\nexport { default as ifMatchingAction } from './if-matching-action';\nexport { default as forwardResolver } from './forward-resolver';\nexport { default as replaceAction } from './replace-action';\nexport { default as withWeakMapCache } from './with-weak-map-cache';\nexport { default as setNestedValue } from './set-nested-value';\nexport { default as getNestedValue } from './get-nested-value';\nexport { default as isNumericID } from './is-numeric-id';\nexport {\n\tgetUserPermissionCacheKey,\n\tgetUserPermissionsFromAllowHeader,\n\tALLOWED_RESOURCE_ACTIONS,\n} from './user-permissions';\nexport { RECEIVE_INTERMEDIATE_RESULTS } from './receive-intermediate-results';\nexport { default as normalizeQueryForResolution } from './normalize-query-for-resolution';\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAA+C;AAC/C,4CAAuD;AACvD,gCAA4C;AAC5C,8BAA2C;AAC3C,4BAAyC;AACzC,iCAA4C;AAC5C,8BAA0C;AAC1C,8BAA0C;AAC1C,2BAAuC;AACvC,8BAIO;AACP,0CAA6C;AAC7C,4CAAuD;",
4
+ "sourcesContent": ["export { default as conservativeMapItem } from './conservative-map-item';\nexport { default as getNormalizedCommaSeparable } from './get-normalized-comma-separable';\nexport { default as ifMatchingAction } from './if-matching-action';\nexport { default as forwardResolver } from './forward-resolver';\nexport { default as replaceAction } from './replace-action';\nexport { default as withWeakMapCache } from './with-weak-map-cache';\nexport { default as setNestedValue } from './set-nested-value';\nexport { default as getNestedValue } from './get-nested-value';\nexport { default as isNumericID } from './is-numeric-id';\nexport {\n\tgetUserPermissionCacheKey,\n\tgetUserPermissionsFromAllowHeader,\n\tALLOWED_RESOURCE_ACTIONS,\n} from './user-permissions';\nexport { RECEIVE_INTERMEDIATE_RESULTS } from './receive-intermediate-results';\nexport { default as normalizeQueryForResolution } from './normalize-query-for-resolution';\nexport { saveCRDTDoc } from './save-crdt-doc';\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAA+C;AAC/C,4CAAuD;AACvD,gCAA4C;AAC5C,8BAA2C;AAC3C,4BAAyC;AACzC,iCAA4C;AAC5C,8BAA0C;AAC1C,8BAA0C;AAC1C,2BAAuC;AACvC,8BAIO;AACP,0CAA6C;AAC7C,4CAAuD;AACvD,2BAA4B;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // packages/core-data/src/utils/save-crdt-doc.js
31
+ var save_crdt_doc_exports = {};
32
+ __export(save_crdt_doc_exports, {
33
+ saveCRDTDoc: () => saveCRDTDoc
34
+ });
35
+ module.exports = __toCommonJS(save_crdt_doc_exports);
36
+ var import_api_fetch = __toESM(require("@wordpress/api-fetch"));
37
+ var import_sync = require("../sync.cjs");
38
+ var SYNC_SAVE_API_PATH = "/wp-sync/v1/save";
39
+ var saveCRDTDocQueues = /* @__PURE__ */ new Map();
40
+ async function serializeAndSaveCRDTDoc(objectType, objectId, room) {
41
+ const serializedDoc = await (0, import_sync.getSyncManager)()?.createPersistedCRDTDoc(
42
+ objectType,
43
+ objectId
44
+ );
45
+ if (!serializedDoc) {
46
+ return;
47
+ }
48
+ await (0, import_api_fetch.default)({
49
+ path: SYNC_SAVE_API_PATH,
50
+ method: "POST",
51
+ data: {
52
+ room,
53
+ doc: serializedDoc
54
+ }
55
+ });
56
+ }
57
+ async function saveCRDTDoc(objectType, objectId) {
58
+ const room = `${objectType}:${objectId}`;
59
+ const previousSave = saveCRDTDocQueues.get(room) || Promise.resolve();
60
+ const currentSave = previousSave.catch(() => {
61
+ }).then(() => serializeAndSaveCRDTDoc(objectType, objectId, room));
62
+ saveCRDTDocQueues.set(room, currentSave);
63
+ try {
64
+ await currentSave;
65
+ } finally {
66
+ if (saveCRDTDocQueues.get(room) === currentSave) {
67
+ saveCRDTDocQueues.delete(room);
68
+ }
69
+ }
70
+ }
71
+ // Annotate the CommonJS export names for ESM import in node:
72
+ 0 && (module.exports = {
73
+ saveCRDTDoc
74
+ });
75
+ //# sourceMappingURL=save-crdt-doc.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/save-crdt-doc.js"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport apiFetch from '@wordpress/api-fetch';\n\n/**\n * Internal dependencies\n */\nimport { getSyncManager } from '../sync';\n\nconst SYNC_SAVE_API_PATH = '/wp-sync/v1/save';\nconst saveCRDTDocQueues = new Map();\n\nasync function serializeAndSaveCRDTDoc( objectType, objectId, room ) {\n\tconst serializedDoc = await getSyncManager()?.createPersistedCRDTDoc(\n\t\tobjectType,\n\t\tobjectId\n\t);\n\n\tif ( ! serializedDoc ) {\n\t\treturn;\n\t}\n\n\tawait apiFetch( {\n\t\tpath: SYNC_SAVE_API_PATH,\n\t\tmethod: 'POST',\n\t\tdata: {\n\t\t\troom,\n\t\t\tdoc: serializedDoc,\n\t\t},\n\t} );\n}\n\n/**\n * Persist the current CRDT document through the sync /save endpoint.\n *\n * @param {import('@wordpress/sync').ObjectType} objectType Object type.\n * @param {import('@wordpress/sync').ObjectID} objectId Object ID.\n */\nexport async function saveCRDTDoc( objectType, objectId ) {\n\tconst room = `${ objectType }:${ objectId }`;\n\n\t// Saves are chained per-room, which forms a queue.\n\t// Without a queue, two /save calls might fire close together with a risk\n\t// that the older serialized CRDT snapshot completes after the newer one and\n\t// overwrites it with stale data.\n\t// Wait for the prior request chain to complete before firing the next save.\n\tconst previousSave = saveCRDTDocQueues.get( room ) || Promise.resolve();\n\n\tconst currentSave = previousSave\n\t\t// A failed save should reject its caller, but not block later saves.\n\t\t.catch( () => {} )\n\t\t.then( () => serializeAndSaveCRDTDoc( objectType, objectId, room ) );\n\n\tsaveCRDTDocQueues.set( room, currentSave );\n\n\ttry {\n\t\tawait currentSave;\n\t} finally {\n\t\tif ( saveCRDTDocQueues.get( room ) === currentSave ) {\n\t\t\tsaveCRDTDocQueues.delete( room );\n\t\t}\n\t}\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,uBAAqB;AAKrB,kBAA+B;AAE/B,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB,oBAAI,IAAI;AAElC,eAAe,wBAAyB,YAAY,UAAU,MAAO;AACpE,QAAM,gBAAgB,UAAM,4BAAe,GAAG;AAAA,IAC7C;AAAA,IACA;AAAA,EACD;AAEA,MAAK,CAAE,eAAgB;AACtB;AAAA,EACD;AAEA,YAAM,iBAAAA,SAAU;AAAA,IACf,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM;AAAA,MACL;AAAA,MACA,KAAK;AAAA,IACN;AAAA,EACD,CAAE;AACH;AAQA,eAAsB,YAAa,YAAY,UAAW;AACzD,QAAM,OAAO,GAAI,UAAW,IAAK,QAAS;AAO1C,QAAM,eAAe,kBAAkB,IAAK,IAAK,KAAK,QAAQ,QAAQ;AAEtE,QAAM,cAAc,aAElB,MAAO,MAAM;AAAA,EAAC,CAAE,EAChB,KAAM,MAAM,wBAAyB,YAAY,UAAU,IAAK,CAAE;AAEpE,oBAAkB,IAAK,MAAM,WAAY;AAEzC,MAAI;AACH,UAAM;AAAA,EACP,UAAE;AACD,QAAK,kBAAkB,IAAK,IAAK,MAAM,aAAc;AACpD,wBAAkB,OAAQ,IAAK;AAAA,IAChC;AAAA,EACD;AACD;",
6
+ "names": ["apiFetch"]
7
+ }
@@ -1,7 +1,8 @@
1
1
  // packages/core-data/src/awareness/block-lookup.ts
2
- import { select } from "@wordpress/data";
2
+ import { useSelect } from "@wordpress/data";
3
3
  import { Y } from "@wordpress/sync";
4
4
  import { store as blockEditorStore } from "@wordpress/block-editor";
5
+ import { unlock } from "../lock-unlock.mjs";
5
6
  function getContainingBlockYMap(yType) {
6
7
  let current = yType;
7
8
  while (current) {
@@ -41,13 +42,10 @@ function getBlockPathInYdoc(yType) {
41
42
  }
42
43
  return path;
43
44
  }
44
- function resolveBlockClientIdByPath(path) {
45
+ function resolveBlockClientIdByPath(path, blocks) {
45
46
  if (path.length === 0) {
46
47
  return null;
47
48
  }
48
- const { getBlocks } = select(blockEditorStore);
49
- const postContentBlocks = getPostContentBlocks(getBlocks(), getBlocks);
50
- let blocks = postContentBlocks;
51
49
  for (let i = 0; i < path.length; i++) {
52
50
  const block = blocks[path[i]];
53
51
  if (!block) {
@@ -60,30 +58,19 @@ function resolveBlockClientIdByPath(path) {
60
58
  }
61
59
  return null;
62
60
  }
63
- function getPostContentBlocks(rootBlocks, getBlocks) {
64
- const postContentBlock = findBlockByName(rootBlocks, "core/post-content");
65
- if (postContentBlock) {
66
- return getBlocks(postContentBlock.clientId);
67
- }
68
- return rootBlocks;
69
- }
70
- function findBlockByName(blocks, name) {
71
- for (const block of blocks) {
72
- if (block.name === name) {
73
- return block;
74
- }
75
- if (block.innerBlocks?.length) {
76
- const found = findBlockByName(block.innerBlocks, name);
77
- if (found) {
78
- return found;
79
- }
80
- }
81
- }
82
- return null;
61
+ function usePostContentBlocks() {
62
+ return useSelect((select) => {
63
+ const { getBlocksByName, getClientIdsTree } = unlock(
64
+ select(blockEditorStore)
65
+ );
66
+ const [postContentClientId] = getBlocksByName("core/post-content");
67
+ return getClientIdsTree(postContentClientId ?? "");
68
+ }, []);
83
69
  }
84
70
  export {
85
71
  getBlockPathInYdoc,
86
72
  getContainingBlockYMap,
87
- resolveBlockClientIdByPath
73
+ resolveBlockClientIdByPath,
74
+ usePostContentBlocks
88
75
  };
89
76
  //# sourceMappingURL=block-lookup.mjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/awareness/block-lookup.ts"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { select } from '@wordpress/data';\nimport { Y } from '@wordpress/sync';\n// @ts-ignore No exported types for block editor store selectors.\nimport { store as blockEditorStore } from '@wordpress/block-editor';\n\n/**\n * Internal dependencies\n */\nimport type { AbsoluteBlockIndexPath } from '../types';\n\n/**\n * A block as represented in the block-editor store (from `getBlocks()`).\n *\n * This is a minimal interface covering only the fields used by RTC awareness.\n */\ninterface EditorStoreBlock {\n\tclientId: string;\n\tname: string;\n\tinnerBlocks: EditorStoreBlock[];\n}\n\n/**\n * Find the block Y.Map that contains a nested Yjs type.\n *\n * Rich-text attributes are often stored directly at attributes.content, but\n * blocks can also store rich text deeper inside object or array attributes.\n * Walk upward until we find the block map instead of assuming a fixed parent\n * depth.\n *\n * @param yType - The nested Yjs type to start from.\n * @return The containing block Y.Map, or null if no block ancestor exists.\n */\nexport function getContainingBlockYMap(\n\tyType: Y.AbstractType< any >\n): Y.Map< unknown > | null {\n\tlet current: Y.AbstractType< any > | null = yType;\n\n\twhile ( current ) {\n\t\tconst parent = current.parent;\n\n\t\tif (\n\t\t\tparent instanceof Y.Map &&\n\t\t\tparent.parent instanceof Y.Array &&\n\t\t\tparent.get( 'clientId' ) !== undefined &&\n\t\t\tparent.get( 'innerBlocks' ) instanceof Y.Array\n\t\t) {\n\t\t\treturn parent;\n\t\t}\n\n\t\tcurrent = parent instanceof Y.AbstractType ? parent : null;\n\t}\n\n\treturn null;\n}\n\n/**\n * Given a Y.Map within a Ydoc, traverse up the Yjs block tree to compute the\n * index path from the root.\n *\n * For example, the second inner block of the first root block returns [0, 1].\n *\n * @param yType - The Yjs block Y.Map to start from.\n * @return The index path from root, or null if traversal fails.\n */\nexport function getBlockPathInYdoc(\n\tyType: Y.Map< unknown >\n): AbsoluteBlockIndexPath | null {\n\tconst path: AbsoluteBlockIndexPath = [];\n\tlet current: Y.Map< unknown > = yType;\n\n\twhile ( current ) {\n\t\tconst parentArray = current.parent;\n\n\t\tif ( ! parentArray || ! ( parentArray instanceof Y.Array ) ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Find index of current block in its parent array.\n\t\tlet index = -1;\n\t\tfor ( let i = 0; i < parentArray.length; i++ ) {\n\t\t\tif ( parentArray.get( i ) === current ) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif ( index === -1 ) {\n\t\t\treturn null;\n\t\t}\n\n\t\tpath.unshift( index );\n\n\t\t// Walk up: is the parent array's parent a block Y.Map or the root?\n\t\tconst grandparent = parentArray.parent;\n\t\tif (\n\t\t\tgrandparent instanceof Y.Map &&\n\t\t\tgrandparent.get( 'clientId' ) !== undefined\n\t\t) {\n\t\t\tcurrent = grandparent; // It's a block, keep going.\n\t\t} else {\n\t\t\tbreak; // It's the root map, done.\n\t\t}\n\t}\n\n\treturn path;\n}\n\n/**\n * Navigate the block-editor store's block tree by an index path\n * and return the local block's clientId.\n *\n * In template mode, getBlocks() returns the full template tree, but Yjs\n * paths are relative to the post content. This method finds the\n * core/post-content block (if present) and uses its inner blocks as the\n * navigation root, so paths align with the Yjs document structure.\n *\n * @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].\n * @return The local block clientId, or null if the path is invalid.\n */\nexport function resolveBlockClientIdByPath(\n\tpath: AbsoluteBlockIndexPath\n): string | null {\n\tif ( path.length === 0 ) {\n\t\treturn null;\n\t}\n\n\tconst { getBlocks } = select( blockEditorStore );\n\tconst postContentBlocks = getPostContentBlocks( getBlocks(), getBlocks );\n\n\tlet blocks = postContentBlocks;\n\n\tfor ( let i = 0; i < path.length; i++ ) {\n\t\tconst block = blocks[ path[ i ] ];\n\t\tif ( ! block ) {\n\t\t\treturn null;\n\t\t}\n\t\tif ( i === path.length - 1 ) {\n\t\t\treturn block.clientId;\n\t\t}\n\t\tblocks = block.innerBlocks;\n\t}\n\treturn null;\n}\n\n/**\n * Find the post content blocks to use as the navigation root.\n *\n * In template mode, the block tree contains template parts wrapping a\n * core/post-content block. The Yjs document only stores the post content\n * blocks, so we need to find the core/post-content block and use\n * getBlocks(clientId) to retrieve its inner blocks from the store.\n *\n * We must use getBlocks(clientId) rather than reading .innerBlocks from\n * the block object because useBlockSync() injects post content as\n * controlled inner blocks \u2014 they exist in the store's block order map\n * but are not populated in the .innerBlocks property of the tree\n * returned by getBlocks().\n *\n * @param rootBlocks - The root-level blocks from getBlocks().\n * @param getBlocks - The getBlocks selector.\n * @return The blocks that correspond to the Yjs document root.\n */\nfunction getPostContentBlocks(\n\trootBlocks: EditorStoreBlock[],\n\tgetBlocks: ( rootClientId?: string ) => EditorStoreBlock[]\n): EditorStoreBlock[] {\n\tconst postContentBlock = findBlockByName( rootBlocks, 'core/post-content' );\n\tif ( postContentBlock ) {\n\t\t// Use getBlocks(clientId) to read controlled inner blocks from\n\t\t// the store, since postContentBlock.innerBlocks is empty.\n\t\treturn getBlocks( postContentBlock.clientId );\n\t}\n\n\treturn rootBlocks;\n}\n\n/**\n * Recursively search the block tree for a block with a given name.\n *\n * @param blocks - The blocks to search.\n * @param name - The block name to find.\n * @return The first matching block, or null if not found.\n */\nfunction findBlockByName(\n\tblocks: EditorStoreBlock[],\n\tname: string\n): EditorStoreBlock | null {\n\tfor ( const block of blocks ) {\n\t\tif ( block.name === name ) {\n\t\t\treturn block;\n\t\t}\n\t\tif ( block.innerBlocks?.length ) {\n\t\t\tconst found = findBlockByName( block.innerBlocks, name );\n\t\t\tif ( found ) {\n\t\t\t\treturn found;\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n"],
5
- "mappings": ";AAGA,SAAS,cAAc;AACvB,SAAS,SAAS;AAElB,SAAS,SAAS,wBAAwB;AA6BnC,SAAS,uBACf,OAC0B;AAC1B,MAAI,UAAwC;AAE5C,SAAQ,SAAU;AACjB,UAAM,SAAS,QAAQ;AAEvB,QACC,kBAAkB,EAAE,OACpB,OAAO,kBAAkB,EAAE,SAC3B,OAAO,IAAK,UAAW,MAAM,UAC7B,OAAO,IAAK,aAAc,aAAa,EAAE,OACxC;AACD,aAAO;AAAA,IACR;AAEA,cAAU,kBAAkB,EAAE,eAAe,SAAS;AAAA,EACvD;AAEA,SAAO;AACR;AAWO,SAAS,mBACf,OACgC;AAChC,QAAM,OAA+B,CAAC;AACtC,MAAI,UAA4B;AAEhC,SAAQ,SAAU;AACjB,UAAM,cAAc,QAAQ;AAE5B,QAAK,CAAE,eAAe,EAAI,uBAAuB,EAAE,QAAU;AAC5D,aAAO;AAAA,IACR;AAGA,QAAI,QAAQ;AACZ,aAAU,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAM;AAC9C,UAAK,YAAY,IAAK,CAAE,MAAM,SAAU;AACvC,gBAAQ;AACR;AAAA,MACD;AAAA,IACD;AAEA,QAAK,UAAU,IAAK;AACnB,aAAO;AAAA,IACR;AAEA,SAAK,QAAS,KAAM;AAGpB,UAAM,cAAc,YAAY;AAChC,QACC,uBAAuB,EAAE,OACzB,YAAY,IAAK,UAAW,MAAM,QACjC;AACD,gBAAU;AAAA,IACX,OAAO;AACN;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAcO,SAAS,2BACf,MACgB;AAChB,MAAK,KAAK,WAAW,GAAI;AACxB,WAAO;AAAA,EACR;AAEA,QAAM,EAAE,UAAU,IAAI,OAAQ,gBAAiB;AAC/C,QAAM,oBAAoB,qBAAsB,UAAU,GAAG,SAAU;AAEvE,MAAI,SAAS;AAEb,WAAU,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAM;AACvC,UAAM,QAAQ,OAAQ,KAAM,CAAE,CAAE;AAChC,QAAK,CAAE,OAAQ;AACd,aAAO;AAAA,IACR;AACA,QAAK,MAAM,KAAK,SAAS,GAAI;AAC5B,aAAO,MAAM;AAAA,IACd;AACA,aAAS,MAAM;AAAA,EAChB;AACA,SAAO;AACR;AAoBA,SAAS,qBACR,YACA,WACqB;AACrB,QAAM,mBAAmB,gBAAiB,YAAY,mBAAoB;AAC1E,MAAK,kBAAmB;AAGvB,WAAO,UAAW,iBAAiB,QAAS;AAAA,EAC7C;AAEA,SAAO;AACR;AASA,SAAS,gBACR,QACA,MAC0B;AAC1B,aAAY,SAAS,QAAS;AAC7B,QAAK,MAAM,SAAS,MAAO;AAC1B,aAAO;AAAA,IACR;AACA,QAAK,MAAM,aAAa,QAAS;AAChC,YAAM,QAAQ,gBAAiB,MAAM,aAAa,IAAK;AACvD,UAAK,OAAQ;AACZ,eAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;",
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useSelect } from '@wordpress/data';\nimport { Y } from '@wordpress/sync';\n// @ts-ignore No exported types for block editor store selectors.\nimport { store as blockEditorStore } from '@wordpress/block-editor';\n\n/**\n * Internal dependencies\n */\nimport type { AbsoluteBlockIndexPath } from '../types';\nimport { unlock } from '../lock-unlock';\n\n/**\n * A block as represented in the block-editor store's client ID tree.\n *\n * This is a minimal interface covering only the fields used by RTC awareness.\n */\nexport type EditorStoreBlock = {\n\tclientId: string;\n\tinnerBlocks: EditorStoreBlock[];\n};\n\n/**\n * Find the block Y.Map that contains a nested Yjs type.\n *\n * Rich-text attributes are often stored directly at attributes.content, but\n * blocks can also store rich text deeper inside object or array attributes.\n * Walk upward until we find the block map instead of assuming a fixed parent\n * depth.\n *\n * @param yType - The nested Yjs type to start from.\n * @return The containing block Y.Map, or null if no block ancestor exists.\n */\nexport function getContainingBlockYMap(\n\tyType: Y.AbstractType< any >\n): Y.Map< unknown > | null {\n\tlet current: Y.AbstractType< any > | null = yType;\n\n\twhile ( current ) {\n\t\tconst parent = current.parent;\n\n\t\tif (\n\t\t\tparent instanceof Y.Map &&\n\t\t\tparent.parent instanceof Y.Array &&\n\t\t\tparent.get( 'clientId' ) !== undefined &&\n\t\t\tparent.get( 'innerBlocks' ) instanceof Y.Array\n\t\t) {\n\t\t\treturn parent;\n\t\t}\n\n\t\tcurrent = parent instanceof Y.AbstractType ? parent : null;\n\t}\n\n\treturn null;\n}\n\n/**\n * Given a Y.Map within a Ydoc, traverse up the Yjs block tree to compute the\n * index path from the root.\n *\n * For example, the second inner block of the first root block returns [0, 1].\n *\n * @param yType - The Yjs block Y.Map to start from.\n * @return The index path from root, or null if traversal fails.\n */\nexport function getBlockPathInYdoc(\n\tyType: Y.Map< unknown >\n): AbsoluteBlockIndexPath | null {\n\tconst path: AbsoluteBlockIndexPath = [];\n\tlet current: Y.Map< unknown > = yType;\n\n\twhile ( current ) {\n\t\tconst parentArray = current.parent;\n\n\t\tif ( ! parentArray || ! ( parentArray instanceof Y.Array ) ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Find index of current block in its parent array.\n\t\tlet index = -1;\n\t\tfor ( let i = 0; i < parentArray.length; i++ ) {\n\t\t\tif ( parentArray.get( i ) === current ) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif ( index === -1 ) {\n\t\t\treturn null;\n\t\t}\n\n\t\tpath.unshift( index );\n\n\t\t// Walk up: is the parent array's parent a block Y.Map or the root?\n\t\tconst grandparent = parentArray.parent;\n\t\tif (\n\t\t\tgrandparent instanceof Y.Map &&\n\t\t\tgrandparent.get( 'clientId' ) !== undefined\n\t\t) {\n\t\t\tcurrent = grandparent; // It's a block, keep going.\n\t\t} else {\n\t\t\tbreak; // It's the root map, done.\n\t\t}\n\t}\n\n\treturn path;\n}\n\n/**\n * Navigate the block-editor store's block tree by an index path\n * and return the local block's clientId.\n *\n * @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].\n * @param blocks - The tree of block-editor store post contentblocks.\n * @return The local block clientId, or null if the path is invalid.\n */\nexport function resolveBlockClientIdByPath(\n\tpath: AbsoluteBlockIndexPath,\n\tblocks: EditorStoreBlock[]\n): string | null {\n\tif ( path.length === 0 ) {\n\t\treturn null;\n\t}\n\n\tfor ( let i = 0; i < path.length; i++ ) {\n\t\tconst block = blocks[ path[ i ] ];\n\t\tif ( ! block ) {\n\t\t\treturn null;\n\t\t}\n\t\tif ( i === path.length - 1 ) {\n\t\t\treturn block.clientId;\n\t\t}\n\t\tblocks = block.innerBlocks;\n\t}\n\treturn null;\n}\n\n/**\n * Find the post content blocks to use as the navigation root.\n *\n * In template mode, the block tree contains template parts wrapping a\n * core/post-content block. The Yjs document only stores the post content\n * blocks, so we need to find the core/post-content block and use\n * getClientIdsTree(clientId) to retrieve its inner blocks from the store.\n *\n * Uses the private getClientIdsTree selector which depends only on\n * state.blocks.order, avoiding unnecessary re-renders when block\n * attributes change (which would happen with getBlocks()).\n *\n * @return The blocks that correspond to the Yjs document root.\n */\nexport function usePostContentBlocks(): EditorStoreBlock[] {\n\treturn useSelect( ( select ) => {\n\t\tconst { getBlocksByName, getClientIdsTree } = unlock(\n\t\t\tselect( blockEditorStore )\n\t\t);\n\t\tconst [ postContentClientId ] = getBlocksByName( 'core/post-content' );\n\t\treturn getClientIdsTree( postContentClientId ?? '' );\n\t}, [] );\n}\n"],
5
+ "mappings": ";AAGA,SAAS,iBAAiB;AAC1B,SAAS,SAAS;AAElB,SAAS,SAAS,wBAAwB;AAM1C,SAAS,cAAc;AAuBhB,SAAS,uBACf,OAC0B;AAC1B,MAAI,UAAwC;AAE5C,SAAQ,SAAU;AACjB,UAAM,SAAS,QAAQ;AAEvB,QACC,kBAAkB,EAAE,OACpB,OAAO,kBAAkB,EAAE,SAC3B,OAAO,IAAK,UAAW,MAAM,UAC7B,OAAO,IAAK,aAAc,aAAa,EAAE,OACxC;AACD,aAAO;AAAA,IACR;AAEA,cAAU,kBAAkB,EAAE,eAAe,SAAS;AAAA,EACvD;AAEA,SAAO;AACR;AAWO,SAAS,mBACf,OACgC;AAChC,QAAM,OAA+B,CAAC;AACtC,MAAI,UAA4B;AAEhC,SAAQ,SAAU;AACjB,UAAM,cAAc,QAAQ;AAE5B,QAAK,CAAE,eAAe,EAAI,uBAAuB,EAAE,QAAU;AAC5D,aAAO;AAAA,IACR;AAGA,QAAI,QAAQ;AACZ,aAAU,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAM;AAC9C,UAAK,YAAY,IAAK,CAAE,MAAM,SAAU;AACvC,gBAAQ;AACR;AAAA,MACD;AAAA,IACD;AAEA,QAAK,UAAU,IAAK;AACnB,aAAO;AAAA,IACR;AAEA,SAAK,QAAS,KAAM;AAGpB,UAAM,cAAc,YAAY;AAChC,QACC,uBAAuB,EAAE,OACzB,YAAY,IAAK,UAAW,MAAM,QACjC;AACD,gBAAU;AAAA,IACX,OAAO;AACN;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAUO,SAAS,2BACf,MACA,QACgB;AAChB,MAAK,KAAK,WAAW,GAAI;AACxB,WAAO;AAAA,EACR;AAEA,WAAU,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAM;AACvC,UAAM,QAAQ,OAAQ,KAAM,CAAE,CAAE;AAChC,QAAK,CAAE,OAAQ;AACd,aAAO;AAAA,IACR;AACA,QAAK,MAAM,KAAK,SAAS,GAAI;AAC5B,aAAO,MAAM;AAAA,IACd;AACA,aAAS,MAAM;AAAA,EAChB;AACA,SAAO;AACR;AAgBO,SAAS,uBAA2C;AAC1D,SAAO,UAAW,CAAE,WAAY;AAC/B,UAAM,EAAE,iBAAiB,iBAAiB,IAAI;AAAA,MAC7C,OAAQ,gBAAiB;AAAA,IAC1B;AACA,UAAM,CAAE,mBAAoB,IAAI,gBAAiB,mBAAoB;AACrE,WAAO,iBAAkB,uBAAuB,EAAG;AAAA,EACpD,GAAG,CAAC,CAAE;AACP;",
6
6
  "names": []
7
7
  }
@@ -151,9 +151,10 @@ var PostEditorAwareness = class extends BaseAwarenessState {
151
151
  * clientIds (e.g. in "Show Template" mode where blocks are cloned).
152
152
  *
153
153
  * @param selection - The selection state.
154
+ * @param blocks - The tree of block-editor store post content blocks.
154
155
  * @return The rich-text offset and block client ID, or nulls if not resolvable.
155
156
  */
156
- convertSelectionStateToAbsolute(selection) {
157
+ convertSelectionStateToAbsolute(selection, blocks) {
157
158
  if (selection.type === SelectionType.None) {
158
159
  return {
159
160
  richTextOffset: null,
@@ -172,7 +173,7 @@ var PostEditorAwareness = class extends BaseAwarenessState {
172
173
  const block = parentArray.get(absolutePos.index);
173
174
  if (block instanceof Y.Map) {
174
175
  const path2 = getBlockPathInYdoc(block);
175
- localClientId2 = path2 ? resolveBlockClientIdByPath(path2) : null;
176
+ localClientId2 = path2 ? resolveBlockClientIdByPath(path2, blocks) : null;
176
177
  }
177
178
  }
178
179
  return {
@@ -195,7 +196,7 @@ var PostEditorAwareness = class extends BaseAwarenessState {
195
196
  }
196
197
  const yType = getContainingBlockYMap(absolutePosition.type);
197
198
  const path = yType ? getBlockPathInYdoc(yType) : null;
198
- const localClientId = path ? resolveBlockClientIdByPath(path) : null;
199
+ const localClientId = path ? resolveBlockClientIdByPath(path, blocks) : null;
199
200
  return {
200
201
  richTextOffset: htmlIndexToRichTextOffset(
201
202
  absolutePosition.type.toString(),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/awareness/post-editor-awareness.ts"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { dispatch, select, subscribe } from '@wordpress/data';\nimport { Y } from '@wordpress/sync';\n// @ts-ignore No exported types for block editor store selectors.\nimport { store as blockEditorStore } from '@wordpress/block-editor';\n\n/**\n * Internal dependencies\n */\nimport { BaseAwarenessState, baseEqualityFieldChecks } from './base-awareness';\nimport {\n\tgetBlockPathInYdoc,\n\tgetContainingBlockYMap,\n\tresolveBlockClientIdByPath,\n} from './block-lookup';\nimport {\n\tAWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS,\n\tLOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS,\n} from './config';\nimport { STORE_NAME as coreStore } from '../name';\nimport {\n\tasHtmlStringIndex,\n\thtmlIndexToRichTextOffset,\n} from '../utils/crdt-utils';\nimport {\n\tareSelectionsStatesEqual,\n\tgetSelectionState,\n\tSelectionType,\n\tSelectionDirection,\n} from '../utils/crdt-user-selections';\n\nimport type {\n\tResolvedSelection,\n\tSelectionState,\n\tWPBlockSelection,\n} from '../types';\nimport type { YBlocks } from '../utils/crdt-blocks';\nimport type {\n\tDebugCollaboratorData,\n\tEditorState,\n\tPostEditorState,\n\tSerializableYItem,\n\tYDocDebugData,\n} from './types';\n\nexport class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {\n\tprotected equalityFieldChecks = {\n\t\t...baseEqualityFieldChecks,\n\t\teditorState: this.areEditorStatesEqual,\n\t};\n\n\tpublic constructor(\n\t\tdoc: Y.Doc,\n\t\tprivate kind: string,\n\t\tprivate name: string,\n\t\tprivate postId: number\n\t) {\n\t\tsuper( doc );\n\t}\n\n\tprotected onSetUp(): void {\n\t\tsuper.onSetUp();\n\n\t\tthis.subscribeToCollaboratorSelectionChanges();\n\t}\n\n\t/**\n\t * Subscribe to collaborator selection changes and update the selection state.\n\t */\n\tprivate subscribeToCollaboratorSelectionChanges(): void {\n\t\tconst {\n\t\t\tgetSelectionStart,\n\t\t\tgetSelectionEnd,\n\t\t\tgetSelectedBlocksInitialCaretPosition,\n\t\t} = select( blockEditorStore );\n\n\t\t// Keep track of the current selection in the outer scope so we can compare\n\t\t// in the subscription.\n\t\tlet selectionStart = getSelectionStart();\n\t\tlet selectionEnd = getSelectionEnd();\n\t\tlet localCursorTimeout: NodeJS.Timeout | null = null;\n\n\t\t// During rapid selection changes (e.g. undo restoring content and\n\t\t// selection), the debounce discards intermediate events. If we use the\n\t\t// last intermediate state instead of the overall change it can produce\n\t\t// the wrong direction.\n\t\t// Use selectionBeforeDebounce to capture the selection state from\n\t\t// before the debounce window so that direction is computed across the\n\t\t// full window when it fires.\n\t\tlet selectionBeforeDebounce: {\n\t\t\tstart: WPBlockSelection;\n\t\t\tend: WPBlockSelection;\n\t\t} | null = null;\n\n\t\tsubscribe( () => {\n\t\t\tconst newSelectionStart = getSelectionStart();\n\t\t\tconst newSelectionEnd = getSelectionEnd();\n\n\t\t\tif (\n\t\t\t\tnewSelectionStart === selectionStart &&\n\t\t\t\tnewSelectionEnd === selectionEnd\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// On the first change of a debounce window, snapshot the state\n\t\t\t// we're moving away from.\n\t\t\tif ( ! selectionBeforeDebounce ) {\n\t\t\t\tselectionBeforeDebounce = {\n\t\t\t\t\tstart: selectionStart,\n\t\t\t\t\tend: selectionEnd,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tselectionStart = newSelectionStart;\n\t\t\tselectionEnd = newSelectionEnd;\n\n\t\t\t// Typically selection position is only persisted after typing in a block, which\n\t\t\t// can cause selection position to be reset by other users making block updates.\n\t\t\t// Ensure we update the controlled selection right away, persisting our cursor position locally.\n\t\t\tconst initialPosition = getSelectedBlocksInitialCaretPosition();\n\t\t\tvoid this.updateSelectionInEntityRecord(\n\t\t\t\tselectionStart,\n\t\t\t\tselectionEnd,\n\t\t\t\tinitialPosition\n\t\t\t);\n\n\t\t\t// We receive two selection changes in quick succession\n\t\t\t// from local selection events:\n\t\t\t// { clientId: \"123...\", attributeKey: \"content\", offset: undefined }\n\t\t\t// { clientId: \"123...\", attributeKey: \"content\", offset: 554 }\n\t\t\t// Add a short debounce to avoid sending the first selection change.\n\t\t\tif ( localCursorTimeout ) {\n\t\t\t\tclearTimeout( localCursorTimeout );\n\t\t\t}\n\n\t\t\tlocalCursorTimeout = setTimeout( () => {\n\t\t\t\t// Compute direction across the full debounce window.\n\t\t\t\tconst selectionStateOptions: {\n\t\t\t\t\tselectionDirection?: SelectionDirection;\n\t\t\t\t} = {};\n\n\t\t\t\tif ( selectionBeforeDebounce ) {\n\t\t\t\t\tselectionStateOptions.selectionDirection =\n\t\t\t\t\t\tdetectSelectionDirection(\n\t\t\t\t\t\t\tselectionBeforeDebounce.start,\n\t\t\t\t\t\t\tselectionBeforeDebounce.end,\n\t\t\t\t\t\t\tselectionStart,\n\t\t\t\t\t\t\tselectionEnd\n\t\t\t\t\t\t);\n\n\t\t\t\t\t// Reset debounced selection state.\n\t\t\t\t\tselectionBeforeDebounce = null;\n\t\t\t\t}\n\n\t\t\t\tconst selectionState = getSelectionState(\n\t\t\t\t\tselectionStart,\n\t\t\t\t\tselectionEnd,\n\t\t\t\t\tthis.doc,\n\t\t\t\t\tselectionStateOptions\n\t\t\t\t);\n\n\t\t\t\tthis.setThrottledLocalStateField(\n\t\t\t\t\t'editorState',\n\t\t\t\t\t{ selection: selectionState },\n\t\t\t\t\tAWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS\n\t\t\t\t);\n\t\t\t}, LOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS );\n\t\t} );\n\t}\n\n\t/**\n\t * Update the entity record with the current collaborator's selection.\n\t *\n\t * @param selectionStart - The start position of the selection.\n\t * @param selectionEnd - The end position of the selection.\n\t * @param initialPosition - The initial position of the selection.\n\t */\n\tprivate async updateSelectionInEntityRecord(\n\t\tselectionStart: WPBlockSelection,\n\t\tselectionEnd: WPBlockSelection,\n\t\tinitialPosition: number | null\n\t): Promise< void > {\n\t\t// Send an entityRecord `selection` update if we have a selection.\n\t\t//\n\t\t// Normally WordPress updates the `selection` property of the post when changes are made to blocks.\n\t\t// In a multi-user setup, block changes can occur from other users. When an entity is updated from another\n\t\t// user's changes, useBlockSync() in Gutenberg will reset the user's selection to the last saved selection.\n\t\t//\n\t\t// Manually adding an edit for each movement ensures that other user's changes to the document will\n\t\t// not cause the local user's selection to reset to the last local change location.\n\t\tconst edits = {\n\t\t\tselection: { selectionStart, selectionEnd, initialPosition },\n\t\t};\n\n\t\tconst options = {\n\t\t\tundoIgnore: true,\n\t\t};\n\n\t\t// @ts-ignore Types are not provided when using store name instead of store instance.\n\t\tdispatch( coreStore ).editEntityRecord(\n\t\t\tthis.kind,\n\t\t\tthis.name,\n\t\t\tthis.postId,\n\t\t\tedits,\n\t\t\toptions\n\t\t);\n\t}\n\n\t/**\n\t * Check if two editor states are equal.\n\t *\n\t * @param state1 - The first editor state.\n\t * @param state2 - The second editor state.\n\t * @return True if the editor states are equal, false otherwise.\n\t */\n\tprivate areEditorStatesEqual(\n\t\tstate1?: EditorState,\n\t\tstate2?: EditorState\n\t): boolean {\n\t\tif ( ! state1 || ! state2 ) {\n\t\t\treturn state1 === state2;\n\t\t}\n\n\t\tif ( ! state1.selection || ! state2.selection ) {\n\t\t\treturn state1.selection === state2.selection;\n\t\t}\n\n\t\treturn areSelectionsStatesEqual( state1.selection, state2.selection );\n\t}\n\n\t/**\n\t * Resolve a selection state to a text index and block client ID.\n\t *\n\t * For text-based selections, navigates up from the resolved Y.Text via\n\t * AbstractType.parent to find the containing block, then resolves the\n\t * local clientId via the block's tree path.\n\t * For WholeBlock selections, resolves the block's relative position and\n\t * then finds the local clientId via tree path.\n\t *\n\t * Tree-path resolution is used instead of reading the clientId directly\n\t * from the Yjs block because the local block-editor store may use different\n\t * clientIds (e.g. in \"Show Template\" mode where blocks are cloned).\n\t *\n\t * @param selection - The selection state.\n\t * @return The rich-text offset and block client ID, or nulls if not resolvable.\n\t */\n\tpublic convertSelectionStateToAbsolute(\n\t\tselection: SelectionState\n\t): ResolvedSelection {\n\t\tif ( selection.type === SelectionType.None ) {\n\t\t\treturn {\n\t\t\t\trichTextOffset: null,\n\t\t\t\tlocalClientId: null,\n\t\t\t\tattributeKey: null,\n\t\t\t};\n\t\t}\n\n\t\tif ( selection.type === SelectionType.WholeBlock ) {\n\t\t\tconst absolutePos = Y.createAbsolutePositionFromRelativePosition(\n\t\t\t\tselection.blockPosition,\n\t\t\t\tthis.doc\n\t\t\t);\n\n\t\t\tlet localClientId: string | null = null;\n\n\t\t\tif ( absolutePos && absolutePos.type instanceof Y.Array ) {\n\t\t\t\tconst parentArray = absolutePos.type as YBlocks;\n\t\t\t\tconst block = parentArray.get( absolutePos.index );\n\n\t\t\t\tif ( block instanceof Y.Map ) {\n\t\t\t\t\tconst path = getBlockPathInYdoc( block );\n\t\t\t\t\tlocalClientId = path\n\t\t\t\t\t\t? resolveBlockClientIdByPath( path )\n\t\t\t\t\t\t: null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\trichTextOffset: null,\n\t\t\t\tlocalClientId,\n\t\t\t\tattributeKey: null,\n\t\t\t};\n\t\t}\n\n\t\t// Text-based selections: resolve cursor position and navigate up.\n\t\tconst cursorPos =\n\t\t\t'cursorPosition' in selection\n\t\t\t\t? selection.cursorPosition\n\t\t\t\t: selection.cursorStartPosition;\n\n\t\tconst absolutePosition = Y.createAbsolutePositionFromRelativePosition(\n\t\t\tcursorPos.relativePosition,\n\t\t\tthis.doc\n\t\t);\n\n\t\tif ( ! absolutePosition ) {\n\t\t\treturn {\n\t\t\t\trichTextOffset: null,\n\t\t\t\tlocalClientId: null,\n\t\t\t\tattributeKey: null,\n\t\t\t};\n\t\t}\n\n\t\tconst yType = getContainingBlockYMap( absolutePosition.type );\n\t\tconst path = yType ? getBlockPathInYdoc( yType ) : null;\n\t\tconst localClientId = path ? resolveBlockClientIdByPath( path ) : null;\n\n\t\treturn {\n\t\t\trichTextOffset: htmlIndexToRichTextOffset(\n\t\t\t\tabsolutePosition.type.toString(),\n\t\t\t\tasHtmlStringIndex( absolutePosition.index )\n\t\t\t),\n\t\t\tlocalClientId,\n\t\t\tattributeKey: cursorPos.attributeKey ?? null,\n\t\t};\n\t}\n\n\t/**\n\t * Type guard to check if a struct is a Y.Item (not Y.GC)\n\t * @param struct - The struct to check.\n\t * @return True if the struct is a Y.Item, false otherwise.\n\t */\n\tprivate isYItem( struct: Y.Item | Y.GC ): struct is Y.Item {\n\t\treturn 'content' in struct;\n\t}\n\n\t/**\n\t * Get data for debugging, using the awareness state.\n\t *\n\t * @return {YDocDebugData} The debug data.\n\t */\n\tpublic getDebugData(): YDocDebugData {\n\t\tconst ydoc = this.doc;\n\n\t\t// Manually extract doc data to avoid deprecated toJSON method\n\t\tconst docData: Record< string, unknown > = Object.fromEntries(\n\t\t\tArray.from( ydoc.share, ( [ key, value ] ) => [\n\t\t\t\tkey,\n\t\t\t\tvalue.toJSON(),\n\t\t\t] )\n\t\t);\n\n\t\t// Build collaboratorMap from awareness store (all collaborators seen this session)\n\t\tconst collaboratorMapData = new Map< string, DebugCollaboratorData >(\n\t\t\tArray.from( this.getSeenStates().entries() ).map(\n\t\t\t\t( [ clientId, collaboratorState ] ) => [\n\t\t\t\t\tString( clientId ),\n\t\t\t\t\t{\n\t\t\t\t\t\tname: collaboratorState.collaboratorInfo.name,\n\t\t\t\t\t\twpUserId: collaboratorState.collaboratorInfo.id,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t)\n\t\t);\n\n\t\t// Serialize Yjs client items to avoid deep nesting\n\t\tconst serializableClientItems: Record<\n\t\t\tnumber,\n\t\t\tArray< SerializableYItem >\n\t\t> = {};\n\n\t\tydoc.store.clients.forEach( ( structs, clientId ) => {\n\t\t\t// Filter for Y.Item only (skip Y.GC garbage collection structs)\n\t\t\tconst items = structs.filter( this.isYItem );\n\n\t\t\tserializableClientItems[ clientId ] = items.map( ( item ) => {\n\t\t\t\tconst { left, right, ...rest } = item;\n\n\t\t\t\treturn {\n\t\t\t\t\t...rest,\n\t\t\t\t\tleft: left\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tid: left.id,\n\t\t\t\t\t\t\t\tlength: left.length,\n\t\t\t\t\t\t\t\torigin: left.origin,\n\t\t\t\t\t\t\t\tcontent: left.content,\n\t\t\t\t\t\t }\n\t\t\t\t\t\t: null,\n\t\t\t\t\tright: right\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tid: right.id,\n\t\t\t\t\t\t\t\tlength: right.length,\n\t\t\t\t\t\t\t\torigin: right.origin,\n\t\t\t\t\t\t\t\tcontent: right.content,\n\t\t\t\t\t\t }\n\t\t\t\t\t\t: null,\n\t\t\t\t};\n\t\t\t} );\n\t\t} );\n\n\t\treturn {\n\t\t\tdoc: docData,\n\t\t\tclients: serializableClientItems,\n\t\t\tcollaboratorMap: Object.fromEntries( collaboratorMapData ),\n\t\t};\n\t}\n}\n\n/**\n * Detect the direction of a selection change by comparing old and new edges.\n *\n * When the user extends a selection backward (e.g. Shift+Left), the\n * selectionStart edge moves while selectionEnd stays fixed, so the caret\n * is at the start. The reverse is true for forward extension.\n *\n * @param prevStart - The previous selectionStart.\n * @param prevEnd - The previous selectionEnd.\n * @param newStart - The new selectionStart.\n * @param newEnd - The new selectionEnd.\n * @return The detected direction, defaulting to Forward when indeterminate.\n */\nfunction detectSelectionDirection(\n\tprevStart: WPBlockSelection,\n\tprevEnd: WPBlockSelection,\n\tnewStart: WPBlockSelection,\n\tnewEnd: WPBlockSelection\n): SelectionDirection {\n\tconst startMoved = ! areBlockSelectionsEqual( prevStart, newStart );\n\tconst endMoved = ! areBlockSelectionsEqual( prevEnd, newEnd );\n\n\tif ( startMoved && ! endMoved ) {\n\t\treturn SelectionDirection.Backward;\n\t}\n\n\treturn SelectionDirection.Forward;\n}\n\n/**\n * Compare two WPBlockSelection objects by value.\n *\n * @param a - First selection.\n * @param b - Second selection.\n * @return True if all fields are equal.\n */\nfunction areBlockSelectionsEqual(\n\ta: WPBlockSelection,\n\tb: WPBlockSelection\n): boolean {\n\treturn (\n\t\ta.clientId === b.clientId &&\n\t\ta.attributeKey === b.attributeKey &&\n\t\ta.offset === b.offset\n\t);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { dispatch, select, subscribe } from '@wordpress/data';\nimport { Y } from '@wordpress/sync';\n// @ts-ignore No exported types for block editor store selectors.\nimport { store as blockEditorStore } from '@wordpress/block-editor';\n\n/**\n * Internal dependencies\n */\nimport { BaseAwarenessState, baseEqualityFieldChecks } from './base-awareness';\nimport {\n\tgetBlockPathInYdoc,\n\tgetContainingBlockYMap,\n\tresolveBlockClientIdByPath,\n} from './block-lookup';\nimport {\n\tAWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS,\n\tLOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS,\n} from './config';\nimport { STORE_NAME as coreStore } from '../name';\nimport {\n\tasHtmlStringIndex,\n\thtmlIndexToRichTextOffset,\n} from '../utils/crdt-utils';\nimport {\n\tareSelectionsStatesEqual,\n\tgetSelectionState,\n\tSelectionType,\n\tSelectionDirection,\n} from '../utils/crdt-user-selections';\n\nimport type {\n\tResolvedSelection,\n\tSelectionState,\n\tWPBlockSelection,\n} from '../types';\nimport type { YBlocks } from '../utils/crdt-blocks';\nimport type { EditorStoreBlock } from './block-lookup';\nimport type {\n\tDebugCollaboratorData,\n\tEditorState,\n\tPostEditorState,\n\tSerializableYItem,\n\tYDocDebugData,\n} from './types';\n\nexport class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {\n\tprotected equalityFieldChecks = {\n\t\t...baseEqualityFieldChecks,\n\t\teditorState: this.areEditorStatesEqual,\n\t};\n\n\tpublic constructor(\n\t\tdoc: Y.Doc,\n\t\tprivate kind: string,\n\t\tprivate name: string,\n\t\tprivate postId: number\n\t) {\n\t\tsuper( doc );\n\t}\n\n\tprotected onSetUp(): void {\n\t\tsuper.onSetUp();\n\n\t\tthis.subscribeToCollaboratorSelectionChanges();\n\t}\n\n\t/**\n\t * Subscribe to collaborator selection changes and update the selection state.\n\t */\n\tprivate subscribeToCollaboratorSelectionChanges(): void {\n\t\tconst {\n\t\t\tgetSelectionStart,\n\t\t\tgetSelectionEnd,\n\t\t\tgetSelectedBlocksInitialCaretPosition,\n\t\t} = select( blockEditorStore );\n\n\t\t// Keep track of the current selection in the outer scope so we can compare\n\t\t// in the subscription.\n\t\tlet selectionStart = getSelectionStart();\n\t\tlet selectionEnd = getSelectionEnd();\n\t\tlet localCursorTimeout: NodeJS.Timeout | null = null;\n\n\t\t// During rapid selection changes (e.g. undo restoring content and\n\t\t// selection), the debounce discards intermediate events. If we use the\n\t\t// last intermediate state instead of the overall change it can produce\n\t\t// the wrong direction.\n\t\t// Use selectionBeforeDebounce to capture the selection state from\n\t\t// before the debounce window so that direction is computed across the\n\t\t// full window when it fires.\n\t\tlet selectionBeforeDebounce: {\n\t\t\tstart: WPBlockSelection;\n\t\t\tend: WPBlockSelection;\n\t\t} | null = null;\n\n\t\tsubscribe( () => {\n\t\t\tconst newSelectionStart = getSelectionStart();\n\t\t\tconst newSelectionEnd = getSelectionEnd();\n\n\t\t\tif (\n\t\t\t\tnewSelectionStart === selectionStart &&\n\t\t\t\tnewSelectionEnd === selectionEnd\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// On the first change of a debounce window, snapshot the state\n\t\t\t// we're moving away from.\n\t\t\tif ( ! selectionBeforeDebounce ) {\n\t\t\t\tselectionBeforeDebounce = {\n\t\t\t\t\tstart: selectionStart,\n\t\t\t\t\tend: selectionEnd,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tselectionStart = newSelectionStart;\n\t\t\tselectionEnd = newSelectionEnd;\n\n\t\t\t// Typically selection position is only persisted after typing in a block, which\n\t\t\t// can cause selection position to be reset by other users making block updates.\n\t\t\t// Ensure we update the controlled selection right away, persisting our cursor position locally.\n\t\t\tconst initialPosition = getSelectedBlocksInitialCaretPosition();\n\t\t\tvoid this.updateSelectionInEntityRecord(\n\t\t\t\tselectionStart,\n\t\t\t\tselectionEnd,\n\t\t\t\tinitialPosition\n\t\t\t);\n\n\t\t\t// We receive two selection changes in quick succession\n\t\t\t// from local selection events:\n\t\t\t// { clientId: \"123...\", attributeKey: \"content\", offset: undefined }\n\t\t\t// { clientId: \"123...\", attributeKey: \"content\", offset: 554 }\n\t\t\t// Add a short debounce to avoid sending the first selection change.\n\t\t\tif ( localCursorTimeout ) {\n\t\t\t\tclearTimeout( localCursorTimeout );\n\t\t\t}\n\n\t\t\tlocalCursorTimeout = setTimeout( () => {\n\t\t\t\t// Compute direction across the full debounce window.\n\t\t\t\tconst selectionStateOptions: {\n\t\t\t\t\tselectionDirection?: SelectionDirection;\n\t\t\t\t} = {};\n\n\t\t\t\tif ( selectionBeforeDebounce ) {\n\t\t\t\t\tselectionStateOptions.selectionDirection =\n\t\t\t\t\t\tdetectSelectionDirection(\n\t\t\t\t\t\t\tselectionBeforeDebounce.start,\n\t\t\t\t\t\t\tselectionBeforeDebounce.end,\n\t\t\t\t\t\t\tselectionStart,\n\t\t\t\t\t\t\tselectionEnd\n\t\t\t\t\t\t);\n\n\t\t\t\t\t// Reset debounced selection state.\n\t\t\t\t\tselectionBeforeDebounce = null;\n\t\t\t\t}\n\n\t\t\t\tconst selectionState = getSelectionState(\n\t\t\t\t\tselectionStart,\n\t\t\t\t\tselectionEnd,\n\t\t\t\t\tthis.doc,\n\t\t\t\t\tselectionStateOptions\n\t\t\t\t);\n\n\t\t\t\tthis.setThrottledLocalStateField(\n\t\t\t\t\t'editorState',\n\t\t\t\t\t{ selection: selectionState },\n\t\t\t\t\tAWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS\n\t\t\t\t);\n\t\t\t}, LOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS );\n\t\t} );\n\t}\n\n\t/**\n\t * Update the entity record with the current collaborator's selection.\n\t *\n\t * @param selectionStart - The start position of the selection.\n\t * @param selectionEnd - The end position of the selection.\n\t * @param initialPosition - The initial position of the selection.\n\t */\n\tprivate async updateSelectionInEntityRecord(\n\t\tselectionStart: WPBlockSelection,\n\t\tselectionEnd: WPBlockSelection,\n\t\tinitialPosition: number | null\n\t): Promise< void > {\n\t\t// Send an entityRecord `selection` update if we have a selection.\n\t\t//\n\t\t// Normally WordPress updates the `selection` property of the post when changes are made to blocks.\n\t\t// In a multi-user setup, block changes can occur from other users. When an entity is updated from another\n\t\t// user's changes, useBlockSync() in Gutenberg will reset the user's selection to the last saved selection.\n\t\t//\n\t\t// Manually adding an edit for each movement ensures that other user's changes to the document will\n\t\t// not cause the local user's selection to reset to the last local change location.\n\t\tconst edits = {\n\t\t\tselection: { selectionStart, selectionEnd, initialPosition },\n\t\t};\n\n\t\tconst options = {\n\t\t\tundoIgnore: true,\n\t\t};\n\n\t\t// @ts-ignore Types are not provided when using store name instead of store instance.\n\t\tdispatch( coreStore ).editEntityRecord(\n\t\t\tthis.kind,\n\t\t\tthis.name,\n\t\t\tthis.postId,\n\t\t\tedits,\n\t\t\toptions\n\t\t);\n\t}\n\n\t/**\n\t * Check if two editor states are equal.\n\t *\n\t * @param state1 - The first editor state.\n\t * @param state2 - The second editor state.\n\t * @return True if the editor states are equal, false otherwise.\n\t */\n\tprivate areEditorStatesEqual(\n\t\tstate1?: EditorState,\n\t\tstate2?: EditorState\n\t): boolean {\n\t\tif ( ! state1 || ! state2 ) {\n\t\t\treturn state1 === state2;\n\t\t}\n\n\t\tif ( ! state1.selection || ! state2.selection ) {\n\t\t\treturn state1.selection === state2.selection;\n\t\t}\n\n\t\treturn areSelectionsStatesEqual( state1.selection, state2.selection );\n\t}\n\n\t/**\n\t * Resolve a selection state to a text index and block client ID.\n\t *\n\t * For text-based selections, navigates up from the resolved Y.Text via\n\t * AbstractType.parent to find the containing block, then resolves the\n\t * local clientId via the block's tree path.\n\t * For WholeBlock selections, resolves the block's relative position and\n\t * then finds the local clientId via tree path.\n\t *\n\t * Tree-path resolution is used instead of reading the clientId directly\n\t * from the Yjs block because the local block-editor store may use different\n\t * clientIds (e.g. in \"Show Template\" mode where blocks are cloned).\n\t *\n\t * @param selection - The selection state.\n\t * @param blocks - The tree of block-editor store post content blocks.\n\t * @return The rich-text offset and block client ID, or nulls if not resolvable.\n\t */\n\tpublic convertSelectionStateToAbsolute(\n\t\tselection: SelectionState,\n\t\tblocks: EditorStoreBlock[]\n\t): ResolvedSelection {\n\t\tif ( selection.type === SelectionType.None ) {\n\t\t\treturn {\n\t\t\t\trichTextOffset: null,\n\t\t\t\tlocalClientId: null,\n\t\t\t\tattributeKey: null,\n\t\t\t};\n\t\t}\n\n\t\tif ( selection.type === SelectionType.WholeBlock ) {\n\t\t\tconst absolutePos = Y.createAbsolutePositionFromRelativePosition(\n\t\t\t\tselection.blockPosition,\n\t\t\t\tthis.doc\n\t\t\t);\n\n\t\t\tlet localClientId: string | null = null;\n\n\t\t\tif ( absolutePos && absolutePos.type instanceof Y.Array ) {\n\t\t\t\tconst parentArray = absolutePos.type as YBlocks;\n\t\t\t\tconst block = parentArray.get( absolutePos.index );\n\n\t\t\t\tif ( block instanceof Y.Map ) {\n\t\t\t\t\tconst path = getBlockPathInYdoc( block );\n\t\t\t\t\tlocalClientId = path\n\t\t\t\t\t\t? resolveBlockClientIdByPath( path, blocks )\n\t\t\t\t\t\t: null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\trichTextOffset: null,\n\t\t\t\tlocalClientId,\n\t\t\t\tattributeKey: null,\n\t\t\t};\n\t\t}\n\n\t\t// Text-based selections: resolve cursor position and navigate up.\n\t\tconst cursorPos =\n\t\t\t'cursorPosition' in selection\n\t\t\t\t? selection.cursorPosition\n\t\t\t\t: selection.cursorStartPosition;\n\n\t\tconst absolutePosition = Y.createAbsolutePositionFromRelativePosition(\n\t\t\tcursorPos.relativePosition,\n\t\t\tthis.doc\n\t\t);\n\n\t\tif ( ! absolutePosition ) {\n\t\t\treturn {\n\t\t\t\trichTextOffset: null,\n\t\t\t\tlocalClientId: null,\n\t\t\t\tattributeKey: null,\n\t\t\t};\n\t\t}\n\n\t\tconst yType = getContainingBlockYMap( absolutePosition.type );\n\t\tconst path = yType ? getBlockPathInYdoc( yType ) : null;\n\t\tconst localClientId = path\n\t\t\t? resolveBlockClientIdByPath( path, blocks )\n\t\t\t: null;\n\n\t\treturn {\n\t\t\trichTextOffset: htmlIndexToRichTextOffset(\n\t\t\t\tabsolutePosition.type.toString(),\n\t\t\t\tasHtmlStringIndex( absolutePosition.index )\n\t\t\t),\n\t\t\tlocalClientId,\n\t\t\tattributeKey: cursorPos.attributeKey ?? null,\n\t\t};\n\t}\n\n\t/**\n\t * Type guard to check if a struct is a Y.Item (not Y.GC)\n\t * @param struct - The struct to check.\n\t * @return True if the struct is a Y.Item, false otherwise.\n\t */\n\tprivate isYItem( struct: Y.Item | Y.GC ): struct is Y.Item {\n\t\treturn 'content' in struct;\n\t}\n\n\t/**\n\t * Get data for debugging, using the awareness state.\n\t *\n\t * @return {YDocDebugData} The debug data.\n\t */\n\tpublic getDebugData(): YDocDebugData {\n\t\tconst ydoc = this.doc;\n\n\t\t// Manually extract doc data to avoid deprecated toJSON method\n\t\tconst docData: Record< string, unknown > = Object.fromEntries(\n\t\t\tArray.from( ydoc.share, ( [ key, value ] ) => [\n\t\t\t\tkey,\n\t\t\t\tvalue.toJSON(),\n\t\t\t] )\n\t\t);\n\n\t\t// Build collaboratorMap from awareness store (all collaborators seen this session)\n\t\tconst collaboratorMapData = new Map< string, DebugCollaboratorData >(\n\t\t\tArray.from( this.getSeenStates().entries() ).map(\n\t\t\t\t( [ clientId, collaboratorState ] ) => [\n\t\t\t\t\tString( clientId ),\n\t\t\t\t\t{\n\t\t\t\t\t\tname: collaboratorState.collaboratorInfo.name,\n\t\t\t\t\t\twpUserId: collaboratorState.collaboratorInfo.id,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t)\n\t\t);\n\n\t\t// Serialize Yjs client items to avoid deep nesting\n\t\tconst serializableClientItems: Record<\n\t\t\tnumber,\n\t\t\tArray< SerializableYItem >\n\t\t> = {};\n\n\t\tydoc.store.clients.forEach( ( structs, clientId ) => {\n\t\t\t// Filter for Y.Item only (skip Y.GC garbage collection structs)\n\t\t\tconst items = structs.filter( this.isYItem );\n\n\t\t\tserializableClientItems[ clientId ] = items.map( ( item ) => {\n\t\t\t\tconst { left, right, ...rest } = item;\n\n\t\t\t\treturn {\n\t\t\t\t\t...rest,\n\t\t\t\t\tleft: left\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tid: left.id,\n\t\t\t\t\t\t\t\tlength: left.length,\n\t\t\t\t\t\t\t\torigin: left.origin,\n\t\t\t\t\t\t\t\tcontent: left.content,\n\t\t\t\t\t\t }\n\t\t\t\t\t\t: null,\n\t\t\t\t\tright: right\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tid: right.id,\n\t\t\t\t\t\t\t\tlength: right.length,\n\t\t\t\t\t\t\t\torigin: right.origin,\n\t\t\t\t\t\t\t\tcontent: right.content,\n\t\t\t\t\t\t }\n\t\t\t\t\t\t: null,\n\t\t\t\t};\n\t\t\t} );\n\t\t} );\n\n\t\treturn {\n\t\t\tdoc: docData,\n\t\t\tclients: serializableClientItems,\n\t\t\tcollaboratorMap: Object.fromEntries( collaboratorMapData ),\n\t\t};\n\t}\n}\n\n/**\n * Detect the direction of a selection change by comparing old and new edges.\n *\n * When the user extends a selection backward (e.g. Shift+Left), the\n * selectionStart edge moves while selectionEnd stays fixed, so the caret\n * is at the start. The reverse is true for forward extension.\n *\n * @param prevStart - The previous selectionStart.\n * @param prevEnd - The previous selectionEnd.\n * @param newStart - The new selectionStart.\n * @param newEnd - The new selectionEnd.\n * @return The detected direction, defaulting to Forward when indeterminate.\n */\nfunction detectSelectionDirection(\n\tprevStart: WPBlockSelection,\n\tprevEnd: WPBlockSelection,\n\tnewStart: WPBlockSelection,\n\tnewEnd: WPBlockSelection\n): SelectionDirection {\n\tconst startMoved = ! areBlockSelectionsEqual( prevStart, newStart );\n\tconst endMoved = ! areBlockSelectionsEqual( prevEnd, newEnd );\n\n\tif ( startMoved && ! endMoved ) {\n\t\treturn SelectionDirection.Backward;\n\t}\n\n\treturn SelectionDirection.Forward;\n}\n\n/**\n * Compare two WPBlockSelection objects by value.\n *\n * @param a - First selection.\n * @param b - Second selection.\n * @return True if all fields are equal.\n */\nfunction areBlockSelectionsEqual(\n\ta: WPBlockSelection,\n\tb: WPBlockSelection\n): boolean {\n\treturn (\n\t\ta.clientId === b.clientId &&\n\t\ta.attributeKey === b.attributeKey &&\n\t\ta.offset === b.offset\n\t);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["localClientId", "path"]
7
7
  }
@@ -31,7 +31,7 @@ var rootEntitiesConfig = [
31
31
  baseURL: "/",
32
32
  baseURLParams: {
33
33
  // Please also change the preload path when changing this.
34
- // @see lib/compat/wordpress-7.0/preload.php
34
+ // @see lib/compat/wordpress-7.1/preload.php
35
35
  _fields: [
36
36
  "description",
37
37
  "gmt_offset",
@@ -267,7 +267,7 @@ var prePersistPostType = async (persistedRecord, edits, name, isTemplate) => {
267
267
  if (persistedRecord) {
268
268
  const objectType = `postType/${name}`;
269
269
  const objectId = persistedRecord.id;
270
- const serializedDoc = await getSyncManager()?.createPersistedCRDTDoc(
270
+ const serializedDoc = getSyncManager()?.createPersistedCRDTDoc(
271
271
  objectType,
272
272
  objectId
273
273
  );
@@ -330,6 +330,8 @@ async function loadPostTypeEntities() {
330
330
  revisionKey: isTemplate && !window?.__experimentalTemplateActivate ? "wp_id" : DEFAULT_ENTITY_KEY
331
331
  };
332
332
  entity.syncConfig = {
333
+ // Save a CRDT document with this entity
334
+ supportsPersistence: true,
333
335
  /**
334
336
  * Apply changes from the local editor to the local CRDT document so
335
337
  * that those changes can be synced to other peers (via the provider).