@wordpress/core-data 7.45.0 → 7.45.1-next.v.202605131006.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/actions.cjs +8 -6
- package/build/actions.cjs.map +2 -2
- package/build/awareness/post-editor-awareness.cjs +1 -1
- package/build/awareness/post-editor-awareness.cjs.map +2 -2
- package/build/resolvers.cjs +2 -1
- package/build/resolvers.cjs.map +2 -2
- package/build/types.cjs.map +2 -2
- package/build/utils/block-selection-history.cjs +4 -1
- package/build/utils/block-selection-history.cjs.map +2 -2
- package/build/utils/crdt-blocks.cjs +157 -89
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt-selection.cjs +1 -1
- package/build/utils/crdt-selection.cjs.map +2 -2
- package/build/utils/crdt-user-selections.cjs +4 -1
- package/build/utils/crdt-user-selections.cjs.map +2 -2
- package/build/utils/crdt-utils.cjs +18 -6
- package/build/utils/crdt-utils.cjs.map +2 -2
- package/build/utils/crdt.cjs +12 -2
- package/build/utils/crdt.cjs.map +2 -2
- package/build-module/actions.mjs +8 -6
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/post-editor-awareness.mjs +5 -2
- package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
- package/build-module/resolvers.mjs +2 -1
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/types.mjs.map +2 -2
- package/build-module/utils/block-selection-history.mjs +5 -1
- package/build-module/utils/block-selection-history.mjs.map +2 -2
- package/build-module/utils/crdt-blocks.mjs +162 -90
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt-selection.mjs +2 -1
- package/build-module/utils/crdt-selection.mjs.map +2 -2
- package/build-module/utils/crdt-user-selections.mjs +9 -2
- package/build-module/utils/crdt-user-selections.mjs.map +2 -2
- package/build-module/utils/crdt-utils.mjs +16 -6
- package/build-module/utils/crdt-utils.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +13 -2
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-types/actions.d.ts +177 -64
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/awareness-state.d.ts.map +1 -1
- package/build-types/awareness/base-awareness.d.ts +0 -3
- package/build-types/awareness/base-awareness.d.ts.map +1 -1
- package/build-types/awareness/post-editor-awareness.d.ts +1 -8
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
- package/build-types/awareness/typed-awareness.d.ts.map +1 -1
- package/build-types/batch/create-batch.d.ts +1 -1
- package/build-types/batch/create-batch.d.ts.map +1 -1
- package/build-types/batch/default-processor.d.ts.map +1 -1
- package/build-types/batch/index.d.ts +2 -2
- package/build-types/batch/index.d.ts.map +1 -1
- package/build-types/entities.d.ts +114 -87
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/entity-context.d.ts +1 -1
- package/build-types/entity-context.d.ts.map +1 -1
- package/build-types/entity-provider.d.ts +2 -2
- package/build-types/entity-provider.d.ts.map +1 -1
- package/build-types/entity-types/attachment.d.ts.map +1 -1
- package/build-types/entity-types/base-entity-records.d.ts.map +1 -1
- package/build-types/entity-types/base.d.ts.map +1 -1
- package/build-types/entity-types/comment.d.ts.map +1 -1
- package/build-types/entity-types/font-collection.d.ts.map +1 -1
- package/build-types/entity-types/font-family.d.ts.map +1 -1
- package/build-types/entity-types/global-styles-revision.d.ts.map +1 -1
- package/build-types/entity-types/icon.d.ts.map +1 -1
- package/build-types/entity-types/menu-location.d.ts.map +1 -1
- package/build-types/entity-types/nav-menu-item.d.ts.map +1 -1
- package/build-types/entity-types/nav-menu.d.ts.map +1 -1
- package/build-types/entity-types/page.d.ts.map +1 -1
- package/build-types/entity-types/plugin.d.ts.map +1 -1
- package/build-types/entity-types/post-revision.d.ts.map +1 -1
- package/build-types/entity-types/post-status.d.ts.map +1 -1
- package/build-types/entity-types/post.d.ts.map +1 -1
- package/build-types/entity-types/settings.d.ts.map +1 -1
- package/build-types/entity-types/sidebar.d.ts.map +1 -1
- package/build-types/entity-types/taxonomy.d.ts.map +1 -1
- package/build-types/entity-types/term.d.ts.map +1 -1
- package/build-types/entity-types/theme.d.ts.map +1 -1
- package/build-types/entity-types/type.d.ts.map +1 -1
- package/build-types/entity-types/user.d.ts.map +1 -1
- package/build-types/entity-types/widget-type.d.ts.map +1 -1
- package/build-types/entity-types/widget.d.ts.map +1 -1
- package/build-types/entity-types/wp-template-part.d.ts.map +1 -1
- package/build-types/entity-types/wp-template.d.ts.map +1 -1
- package/build-types/fetch/__experimental-fetch-url-data.d.ts +2 -5
- package/build-types/fetch/__experimental-fetch-url-data.d.ts.map +1 -1
- package/build-types/fetch/index.d.ts +3 -3
- package/build-types/fetch/index.d.ts.map +1 -1
- package/build-types/footnotes/get-footnotes-order.d.ts.map +1 -1
- package/build-types/footnotes/get-rich-text-values-cached.d.ts.map +1 -1
- package/build-types/footnotes/index.d.ts +1 -1
- package/build-types/footnotes/index.d.ts.map +1 -1
- package/build-types/hooks/use-entity-block-editor.d.ts +1 -1
- package/build-types/hooks/use-entity-block-editor.d.ts.map +1 -1
- package/build-types/hooks/use-entity-id.d.ts.map +1 -1
- package/build-types/hooks/use-entity-prop.d.ts.map +1 -1
- package/build-types/hooks/use-resource-permissions.d.ts.map +1 -1
- package/build-types/index.d.ts +155 -153
- package/build-types/index.d.ts.map +1 -1
- package/build-types/locks/actions.d.ts +1 -1
- package/build-types/locks/actions.d.ts.map +1 -1
- package/build-types/locks/engine.d.ts +1 -1
- package/build-types/locks/engine.d.ts.map +1 -1
- package/build-types/locks/reducer.d.ts.map +1 -1
- package/build-types/locks/selectors.d.ts +2 -2
- package/build-types/locks/selectors.d.ts.map +1 -1
- package/build-types/locks/utils.d.ts +5 -5
- package/build-types/locks/utils.d.ts.map +1 -1
- package/build-types/name.d.ts +1 -1
- package/build-types/name.d.ts.map +1 -1
- package/build-types/private-actions.d.ts +45 -29
- package/build-types/private-actions.d.ts.map +1 -1
- package/build-types/private-apis.d.ts +1 -1
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/queried-data/actions.d.ts +3 -3
- package/build-types/queried-data/actions.d.ts.map +1 -1
- package/build-types/queried-data/get-query-parts.d.ts +10 -34
- package/build-types/queried-data/get-query-parts.d.ts.map +1 -1
- package/build-types/queried-data/index.d.ts +3 -3
- package/build-types/queried-data/index.d.ts.map +1 -1
- package/build-types/queried-data/reducer.d.ts +7 -23
- package/build-types/queried-data/reducer.d.ts.map +1 -1
- package/build-types/queried-data/selectors.d.ts +3 -3
- package/build-types/queried-data/selectors.d.ts.map +1 -1
- package/build-types/reducer.d.ts +40 -32
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts +130 -47
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts +1 -1
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/types.d.ts +61 -6
- package/build-types/types.d.ts.map +1 -1
- package/build-types/utils/block-selection-history.d.ts.map +1 -1
- package/build-types/utils/conservative-map-item.d.ts.map +1 -1
- package/build-types/utils/crdt-blocks.d.ts +19 -9
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt-selection.d.ts.map +1 -1
- package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
- package/build-types/utils/crdt-utils.d.ts +35 -2
- package/build-types/utils/crdt-utils.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/build-types/utils/forward-resolver.d.ts +2 -2
- package/build-types/utils/forward-resolver.d.ts.map +1 -1
- package/build-types/utils/get-nested-value.d.ts.map +1 -1
- package/build-types/utils/get-normalized-comma-separable.d.ts +1 -1
- package/build-types/utils/get-normalized-comma-separable.d.ts.map +1 -1
- package/build-types/utils/if-matching-action.d.ts +3 -3
- package/build-types/utils/if-matching-action.d.ts.map +1 -1
- package/build-types/utils/index.d.ts +12 -12
- package/build-types/utils/index.d.ts.map +1 -1
- package/build-types/utils/is-numeric-id.d.ts.map +1 -1
- package/build-types/utils/log-entity-deprecation.d.ts +1 -1
- package/build-types/utils/log-entity-deprecation.d.ts.map +1 -1
- package/build-types/utils/normalize-query-for-resolution.d.ts.map +1 -1
- package/build-types/utils/receive-intermediate-results.d.ts +1 -1
- package/build-types/utils/receive-intermediate-results.d.ts.map +1 -1
- package/build-types/utils/replace-action.d.ts +3 -3
- package/build-types/utils/replace-action.d.ts.map +1 -1
- package/build-types/utils/set-nested-value.d.ts.map +1 -1
- package/build-types/utils/user-permissions.d.ts +3 -3
- package/build-types/utils/user-permissions.d.ts.map +1 -1
- package/build-types/utils/with-weak-map-cache.d.ts +1 -1
- package/build-types/utils/with-weak-map-cache.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/actions.js +7 -9
- package/src/awareness/post-editor-awareness.ts +5 -2
- package/src/resolvers.js +2 -1
- package/src/test/actions.js +58 -0
- package/src/test/resolvers.js +115 -2
- package/src/test/rtc-rich-text-offset-space.test.js +204 -0
- package/src/types.ts +63 -6
- package/src/utils/block-selection-history.ts +5 -1
- package/src/utils/crdt-blocks.ts +316 -116
- package/src/utils/crdt-selection.ts +2 -1
- package/src/utils/crdt-user-selections.ts +9 -2
- package/src/utils/crdt-utils.ts +53 -10
- package/src/utils/crdt.ts +30 -4
- package/src/utils/test/crdt-blocks.ts +74 -18
- package/src/utils/test/crdt-utils.ts +18 -2
- package/src/utils/test/rtc-rich-text-cursor-scope.test.js +267 -0
- package/src/utils/test/rtc-rich-text-offset-space.test.js +469 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/crdt-selection.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { dispatch, select } from '@wordpress/data';\n// @ts-expect-error No exported types.\nimport { store as blockEditorStore } from '@wordpress/block-editor';\nimport { isUnmodifiedBlock } from '@wordpress/blocks';\nimport { type CRDTDoc, Y } from '@wordpress/sync';\n\n/**\n * Internal dependencies\n */\nimport {\n\tcreateBlockSelectionHistory,\n\tYSelectionType,\n\ttype BlockSelectionHistory,\n\ttype YFullSelection,\n\ttype YSelection,\n} from './block-selection-history';\nimport {\n\tfindBlockByClientIdInDoc,\n\thtmlIndexToRichTextOffset,\n} from './crdt-utils';\nimport type { WPBlockSelection, WPSelection } from '../types';\n\n// WeakMap to store BlockSelectionHistory instances per Y.Doc\nconst selectionHistoryMap = new WeakMap< CRDTDoc, BlockSelectionHistory >();\n\n/**\n * Get or create a BlockSelectionHistory instance for a given Y.Doc.\n *\n * @param ydoc The Y.Doc to get the selection history for\n * @return The BlockSelectionHistory instance\n */\nfunction getBlockSelectionHistory( ydoc: CRDTDoc ): BlockSelectionHistory {\n\tlet history = selectionHistoryMap.get( ydoc );\n\n\tif ( ! history ) {\n\t\thistory = createBlockSelectionHistory( ydoc );\n\t\tselectionHistoryMap.set( ydoc, history );\n\t}\n\n\treturn history;\n}\n\nexport function getSelectionHistory( ydoc: CRDTDoc ): YFullSelection[] {\n\treturn getBlockSelectionHistory( ydoc ).getSelectionHistory();\n}\n\nexport function updateSelectionHistory(\n\tydoc: CRDTDoc,\n\twpSelection: WPSelection\n): void {\n\treturn getBlockSelectionHistory( ydoc ).updateSelection( wpSelection );\n}\n\n/**\n * Convert a YSelection to a WPBlockSelection.\n * @param ySelection The YSelection (relative) to convert\n * @param ydoc The Y.Doc to convert the selection to a block selection for\n * @return The converted WPBlockSelection, or null if the conversion fails\n */\nfunction convertYSelectionToBlockSelection(\n\tySelection: YSelection,\n\tydoc: Y.Doc\n): WPBlockSelection | null {\n\tif ( ySelection.type === YSelectionType.RelativeSelection ) {\n\t\tconst { relativePosition, attributeKey, clientId } = ySelection;\n\n\t\tconst absolutePosition = Y.createAbsolutePositionFromRelativePosition(\n\t\t\trelativePosition,\n\t\t\tydoc\n\t\t);\n\n\t\tif ( absolutePosition ) {\n\t\t\treturn {\n\t\t\t\tclientId,\n\t\t\t\tattributeKey,\n\t\t\t\toffset: htmlIndexToRichTextOffset(\n\t\t\t\t\tabsolutePosition.type.toString(),\n\t\t\t\t\
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAAiC;AAEjC,0BAA0C;AAC1C,oBAAkC;AAClC,kBAAgC;AAKhC,qCAMO;AACP,
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { dispatch, select } from '@wordpress/data';\n// @ts-expect-error No exported types.\nimport { store as blockEditorStore } from '@wordpress/block-editor';\nimport { isUnmodifiedBlock } from '@wordpress/blocks';\nimport { type CRDTDoc, Y } from '@wordpress/sync';\n\n/**\n * Internal dependencies\n */\nimport {\n\tcreateBlockSelectionHistory,\n\tYSelectionType,\n\ttype BlockSelectionHistory,\n\ttype YFullSelection,\n\ttype YSelection,\n} from './block-selection-history';\nimport {\n\tasHtmlStringIndex,\n\tfindBlockByClientIdInDoc,\n\thtmlIndexToRichTextOffset,\n} from './crdt-utils';\nimport type { WPBlockSelection, WPSelection } from '../types';\n\n// WeakMap to store BlockSelectionHistory instances per Y.Doc\nconst selectionHistoryMap = new WeakMap< CRDTDoc, BlockSelectionHistory >();\n\n/**\n * Get or create a BlockSelectionHistory instance for a given Y.Doc.\n *\n * @param ydoc The Y.Doc to get the selection history for\n * @return The BlockSelectionHistory instance\n */\nfunction getBlockSelectionHistory( ydoc: CRDTDoc ): BlockSelectionHistory {\n\tlet history = selectionHistoryMap.get( ydoc );\n\n\tif ( ! history ) {\n\t\thistory = createBlockSelectionHistory( ydoc );\n\t\tselectionHistoryMap.set( ydoc, history );\n\t}\n\n\treturn history;\n}\n\nexport function getSelectionHistory( ydoc: CRDTDoc ): YFullSelection[] {\n\treturn getBlockSelectionHistory( ydoc ).getSelectionHistory();\n}\n\nexport function updateSelectionHistory(\n\tydoc: CRDTDoc,\n\twpSelection: WPSelection\n): void {\n\treturn getBlockSelectionHistory( ydoc ).updateSelection( wpSelection );\n}\n\n/**\n * Convert a YSelection to a WPBlockSelection.\n * @param ySelection The YSelection (relative) to convert\n * @param ydoc The Y.Doc to convert the selection to a block selection for\n * @return The converted WPBlockSelection, or null if the conversion fails\n */\nfunction convertYSelectionToBlockSelection(\n\tySelection: YSelection,\n\tydoc: Y.Doc\n): WPBlockSelection | null {\n\tif ( ySelection.type === YSelectionType.RelativeSelection ) {\n\t\tconst { relativePosition, attributeKey, clientId } = ySelection;\n\n\t\tconst absolutePosition = Y.createAbsolutePositionFromRelativePosition(\n\t\t\trelativePosition,\n\t\t\tydoc\n\t\t);\n\n\t\tif ( absolutePosition ) {\n\t\t\treturn {\n\t\t\t\tclientId,\n\t\t\t\tattributeKey,\n\t\t\t\toffset: htmlIndexToRichTextOffset(\n\t\t\t\t\tabsolutePosition.type.toString(),\n\t\t\t\t\tasHtmlStringIndex( absolutePosition.index )\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\t} else if ( ySelection.type === YSelectionType.BlockSelection ) {\n\t\treturn {\n\t\t\tclientId: ySelection.clientId,\n\t\t\tattributeKey: undefined,\n\t\t\toffset: undefined,\n\t\t};\n\t}\n\n\treturn null;\n}\n\n/**\n * Convert a YFullSelection to a WPSelection by resolving relative positions\n * and verifying the blocks exist in the document.\n * @param yFullSelection The YFullSelection to convert\n * @param ydoc The Y.Doc to resolve positions against\n * @return The converted WPSelection, or null if the conversion fails\n */\nfunction convertYFullSelectionToWPSelection(\n\tyFullSelection: YFullSelection,\n\tydoc: Y.Doc\n): WPSelection | null {\n\tconst { start, end } = yFullSelection;\n\tconst startBlock = findBlockByClientIdInDoc( start.clientId, ydoc );\n\tconst endBlock = findBlockByClientIdInDoc( end.clientId, ydoc );\n\n\tif ( ! startBlock || ! endBlock ) {\n\t\treturn null;\n\t}\n\n\tconst startBlockSelection = convertYSelectionToBlockSelection(\n\t\tstart,\n\t\tydoc\n\t);\n\tconst endBlockSelection = convertYSelectionToBlockSelection( end, ydoc );\n\n\tif ( startBlockSelection === null || endBlockSelection === null ) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tselectionStart: startBlockSelection,\n\t\tselectionEnd: endBlockSelection,\n\t};\n}\n\n/**\n * Given a Y.Doc and a selection history, find the most recent selection\n * that exists in the document. Skip any selections that are not in the document.\n * @param ydoc The Y.Doc to find the selection in\n * @param selectionHistory The selection history to check\n * @return The most recent selection that exists in the document, or null if no selection exists.\n */\nfunction findSelectionFromHistory(\n\tydoc: Y.Doc,\n\tselectionHistory: YFullSelection[]\n): WPSelection | null {\n\tfor ( const positionToTry of selectionHistory ) {\n\t\tconst result = convertYFullSelectionToWPSelection(\n\t\t\tpositionToTry,\n\t\t\tydoc\n\t\t);\n\t\tif ( result !== null ) {\n\t\t\treturn result;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Restore the selection to the most recent selection in history that is\n * available in the document.\n * @param selectionHistory The selection history to restore\n * @param ydoc The Y.Doc where blocks are stored\n */\nexport function restoreSelection(\n\tselectionHistory: YFullSelection[],\n\tydoc: Y.Doc\n): void {\n\t// Find the most recent selection in history that is available in\n\t// the document.\n\tconst selectionToRestore = findSelectionFromHistory(\n\t\tydoc,\n\t\tselectionHistory\n\t);\n\n\tif ( selectionToRestore === null ) {\n\t\t// Case 1: No blocks in history are available for restoration.\n\t\t// Do nothing.\n\t\treturn;\n\t}\n\n\tconst { getBlock } = select( blockEditorStore );\n\tconst { resetSelection } = dispatch( blockEditorStore );\n\tconst { selectionStart, selectionEnd } = selectionToRestore;\n\tconst isSelectionInSameBlock =\n\t\tselectionStart.clientId === selectionEnd.clientId;\n\n\tif ( isSelectionInSameBlock ) {\n\t\t// Case 2: After content is restored, the selection is available\n\t\t// within the same block\n\n\t\tconst block = getBlock( selectionStart.clientId );\n\t\tconst isBlockEmpty = block && isUnmodifiedBlock( block );\n\t\tconst isBeginningOfEmptyBlock =\n\t\t\t0 === selectionStart.offset &&\n\t\t\t0 === selectionEnd.offset &&\n\t\t\tisBlockEmpty &&\n\t\t\t! selectionStart.attributeKey &&\n\t\t\t! selectionEnd.attributeKey;\n\n\t\tif ( isBeginningOfEmptyBlock ) {\n\t\t\t// Case 2a: When the content in a block has been removed after an\n\t\t\t// undo, WordPress will set the selection to the block's client ID\n\t\t\t// with an undefined startOffset and endOffset.\n\t\t\t//\n\t\t\t// To match the default behavior and tests, exclude the selection\n\t\t\t// offset when resetting to position 0.\n\t\t\tconst selectionStartWithoutOffset = {\n\t\t\t\tclientId: selectionStart.clientId,\n\t\t\t};\n\t\t\tconst selectionEndWithoutOffset = {\n\t\t\t\tclientId: selectionEnd.clientId,\n\t\t\t};\n\n\t\t\tresetSelection(\n\t\t\t\tselectionStartWithoutOffset,\n\t\t\t\tselectionEndWithoutOffset,\n\t\t\t\t0\n\t\t\t);\n\t\t} else {\n\t\t\t// Case 2b: Otherwise, reset including the saved selection offset.\n\t\t\tresetSelection( selectionStart, selectionEnd, 0 );\n\t\t}\n\t} else {\n\t\t// Case 3: A multi-block selection was made. resetSelection() can only\n\t\t// restore selections within the same block.\n\t\t// When a multi-block selection is made, selectionEnd represents\n\t\t// where the user's cursor ended.\n\t\tresetSelection( selectionEnd, selectionEnd, 0 );\n\t}\n}\n\n/**\n * If the latest selection has been shifted by remote edits, resolve and return\n * it as a WPSelection. Returns null when the history is empty or neither\n * endpoint has moved.\n *\n * @param ydoc The Y.Doc to resolve positions against\n * @param selectionHistory The selection history to check\n * @return The shifted WPSelection, or null if nothing moved.\n */\nexport function getShiftedSelection(\n\tydoc: Y.Doc,\n\tselectionHistory: YFullSelection[]\n): WPSelection | null {\n\tif ( selectionHistory.length === 0 ) {\n\t\treturn null;\n\t}\n\n\tconst { start, end } = selectionHistory[ 0 ];\n\n\t// Block-level selections have no offset that can shift.\n\tif (\n\t\tstart.type === YSelectionType.BlockSelection ||\n\t\tend.type === YSelectionType.BlockSelection\n\t) {\n\t\treturn null;\n\t}\n\n\tconst selectionStart = convertYSelectionToBlockSelection( start, ydoc );\n\tconst selectionEnd = convertYSelectionToBlockSelection( end, ydoc );\n\n\tif ( ! selectionStart || ! selectionEnd ) {\n\t\treturn null;\n\t}\n\n\t// Only dispatch if at least one endpoint actually moved.\n\tconst startShifted = selectionStart.offset !== start.offset;\n\tconst endShifted = selectionEnd.offset !== end.offset;\n\n\tif ( ! startShifted && ! endShifted ) {\n\t\treturn null;\n\t}\n\n\treturn { selectionStart, selectionEnd };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["blockEditorStore"]
|
|
7
7
|
}
|
|
@@ -113,7 +113,10 @@ function getCursorPosition(selection, blocks) {
|
|
|
113
113
|
}
|
|
114
114
|
const relativePosition = import_sync.Y.createRelativePositionFromTypeIndex(
|
|
115
115
|
currentYText,
|
|
116
|
-
(0, import_crdt_utils.richTextOffsetToHtmlIndex)(
|
|
116
|
+
(0, import_crdt_utils.richTextOffsetToHtmlIndex)(
|
|
117
|
+
currentYText.toString(),
|
|
118
|
+
(0, import_crdt_utils.asRichTextOffset)(selection.offset)
|
|
119
|
+
)
|
|
117
120
|
);
|
|
118
121
|
return {
|
|
119
122
|
relativePosition,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/crdt-user-selections.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 { CRDT_RECORD_MAP_KEY } from '../sync';\nimport type { YPostRecord } from './crdt';\nimport type { YBlock, YBlocks } from './crdt-blocks';\nimport { getRootMap, richTextOffsetToHtmlIndex } from './crdt-utils';\nimport type {\n\tAbsoluteBlockIndexPath,\n\tWPBlockSelection,\n\tSelectionState,\n\tSelectionNone,\n\tSelectionCursor,\n\tSelectionInOneBlock,\n\tSelectionInMultipleBlocks,\n\tSelectionWholeBlock,\n\tSelectionDirection,\n\tCursorPosition,\n} from '../types';\n\n/**\n * The type of selection.\n */\nexport enum SelectionType {\n\tNone = 'none',\n\tCursor = 'cursor',\n\tSelectionInOneBlock = 'selection-in-one-block',\n\tSelectionInMultipleBlocks = 'selection-in-multiple-blocks',\n\tWholeBlock = 'whole-block',\n}\n\n/**\n * Converts WordPress block editor selection to a SelectionState.\n *\n * Uses getBlockPathForLocalClientId to locate blocks in the Yjs document by\n * their tree position (index path) rather than clientId, since clientIds may\n * differ between the block-editor store and the Yjs document (e.g. in \"Show\n * Template\" mode).\n *\n * @param selectionStart - The start position of the selection\n * @param selectionEnd - The end position of the selection\n * @param yDoc - The Yjs document\n * @param options - Optional parameters\n * @param options.selectionDirection - The direction of the selection (forward or backward)\n * @return The SelectionState\n */\nexport function getSelectionState(\n\tselectionStart: WPBlockSelection,\n\tselectionEnd: WPBlockSelection,\n\tyDoc: Y.Doc,\n\toptions?: { selectionDirection?: SelectionDirection }\n): SelectionState {\n\tconst { selectionDirection } = options ?? {};\n\tconst ymap = getRootMap< YPostRecord >( yDoc, CRDT_RECORD_MAP_KEY );\n\tconst yBlocks = ymap.get( 'blocks' );\n\n\tconst isSelectionEmpty = Object.keys( selectionStart ).length === 0;\n\tconst noSelection: SelectionNone = {\n\t\ttype: SelectionType.None,\n\t};\n\n\tif ( isSelectionEmpty || ! yBlocks ) {\n\t\t// Case 1: No selection, or no blocks in the document.\n\t\treturn noSelection;\n\t}\n\n\t// When the page initially loads, selectionStart can contain an empty object `{}`.\n\tconst isSelectionInOneBlock =\n\t\tselectionStart.clientId === selectionEnd.clientId;\n\tconst isCursorOnly =\n\t\tisSelectionInOneBlock && selectionStart.offset === selectionEnd.offset;\n\tconst isSelectionAWholeBlock =\n\t\tisSelectionInOneBlock &&\n\t\tselectionStart.offset === undefined &&\n\t\tselectionEnd.offset === undefined;\n\n\tif ( isSelectionAWholeBlock ) {\n\t\t// Case 2: A whole block is selected.\n\t\tconst path = getBlockPathForLocalClientId( selectionStart.clientId );\n\t\tconst blockPosition = path\n\t\t\t? createRelativePositionForBlockPath( path, yBlocks )\n\t\t\t: null;\n\n\t\tif ( ! blockPosition ) {\n\t\t\treturn noSelection;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: SelectionType.WholeBlock,\n\t\t\tblockPosition,\n\t\t};\n\t} else if ( isCursorOnly ) {\n\t\t// Case 3: Cursor only, no text selected\n\t\tconst cursorPosition = getCursorPosition( selectionStart, yBlocks );\n\n\t\tif ( ! cursorPosition ) {\n\t\t\t// If we can't find the cursor position in block text, treat it as a non-selection.\n\t\t\treturn noSelection;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: SelectionType.Cursor,\n\t\t\tcursorPosition,\n\t\t};\n\t} else if ( isSelectionInOneBlock ) {\n\t\t// Case 4: Selection in a single block\n\t\tconst cursorStartPosition = getCursorPosition(\n\t\t\tselectionStart,\n\t\t\tyBlocks\n\t\t);\n\t\tconst cursorEndPosition = getCursorPosition( selectionEnd, yBlocks );\n\n\t\tif ( ! cursorStartPosition || ! cursorEndPosition ) {\n\t\t\t// If we can't find the cursor positions in block text, treat it as a non-selection.\n\t\t\treturn noSelection;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: SelectionType.SelectionInOneBlock,\n\t\t\tcursorStartPosition,\n\t\t\tcursorEndPosition,\n\t\t\tselectionDirection,\n\t\t};\n\t}\n\n\t// Case 5: Selection in multiple blocks\n\tconst cursorStartPosition = getCursorPosition( selectionStart, yBlocks );\n\tconst cursorEndPosition = getCursorPosition( selectionEnd, yBlocks );\n\tif ( ! cursorStartPosition || ! cursorEndPosition ) {\n\t\t// If we can't find the cursor positions in block text, treat it as a non-selection.\n\t\treturn noSelection;\n\t}\n\n\treturn {\n\t\ttype: SelectionType.SelectionInMultipleBlocks,\n\t\tcursorStartPosition,\n\t\tcursorEndPosition,\n\t\tselectionDirection,\n\t};\n}\n\n/**\n * Get the cursor position from a selection.\n *\n * @param selection - The selection.\n * @param blocks - The blocks to search through.\n * @return The cursor position, or null if not found.\n */\nfunction getCursorPosition(\n\tselection: WPBlockSelection,\n\tblocks: YBlocks\n): CursorPosition | null {\n\tconst path = getBlockPathForLocalClientId( selection.clientId );\n\tconst block = path ? findBlockByPath( path, blocks ) : null;\n\tif (\n\t\t! block ||\n\t\t! selection.attributeKey ||\n\t\tundefined === selection.offset\n\t) {\n\t\treturn null;\n\t}\n\n\tconst attributes = block.get( 'attributes' );\n\tconst currentYText = attributes?.get( selection.attributeKey );\n\n\t// If the attribute is not a Y.Text, return null.\n\tif ( ! ( currentYText instanceof Y.Text ) ) {\n\t\treturn null;\n\t}\n\n\tconst relativePosition = Y.createRelativePositionFromTypeIndex(\n\t\tcurrentYText,\n\t\trichTextOffsetToHtmlIndex( currentYText.toString(), selection.offset )\n\t);\n\n\treturn {\n\t\trelativePosition,\n\t\tabsoluteOffset: selection.offset,\n\t};\n}\n\n/**\n * Resolves a local block-editor clientId to its index path relative to the\n * post content blocks. This allows finding the corresponding block in the Yjs\n * document even when clientIds differ (e.g. in \"Show Template\" mode where\n * blocks are cloned).\n *\n * In template mode, the block tree includes template parts and wrapper blocks\n * around a core/post-content block. The Yjs document only contains the post\n * content blocks, so we stop the upward walk when the parent is\n * core/post-content (its inner blocks correspond to the Yjs root blocks).\n *\n * @param clientId - The local block-editor clientId to resolve.\n * @return The index path from root, or null if not resolvable.\n */\nexport function getBlockPathForLocalClientId(\n\tclientId: string\n): AbsoluteBlockIndexPath | null {\n\tconst { getBlockIndex, getBlockRootClientId, getBlockName } =\n\t\tselect( blockEditorStore );\n\n\tconst path: AbsoluteBlockIndexPath = [];\n\tlet current: string | null = clientId;\n\twhile ( current ) {\n\t\tconst index = getBlockIndex( current );\n\t\tif ( index === -1 ) {\n\t\t\treturn null;\n\t\t}\n\t\tpath.unshift( index );\n\t\tconst parent = getBlockRootClientId( current );\n\t\tif ( ! parent ) {\n\t\t\tbreak;\n\t\t}\n\t\t// If the parent is core/post-content, stop here \u2014 the Yjs doc\n\t\t// root blocks correspond to post-content's inner blocks.\n\t\tconst parentName = getBlockName( parent );\n\t\tif ( parentName === 'core/post-content' ) {\n\t\t\tbreak;\n\t\t}\n\t\tcurrent = parent;\n\t}\n\treturn path.length > 0 ? path : null;\n}\n\n/**\n * Find a block by navigating a tree index path in the Yjs block hierarchy.\n *\n * @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].\n * @param blocks - The root-level Yjs blocks array.\n * @return The block Y.Map if found, null otherwise.\n */\nfunction findBlockByPath(\n\tpath: AbsoluteBlockIndexPath,\n\tblocks: YBlocks\n): YBlock | null {\n\tlet currentBlocks = blocks;\n\tfor ( let i = 0; i < path.length; i++ ) {\n\t\tif ( path[ i ] >= currentBlocks.length ) {\n\t\t\treturn null;\n\t\t}\n\t\tconst block = currentBlocks.get( 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;\n\t\t}\n\t\tcurrentBlocks =\n\t\t\tblock.get( 'innerBlocks' ) ?? ( new Y.Array() as YBlocks );\n\t}\n\treturn null;\n}\n\n/**\n * Create a Y.RelativePosition for a block by navigating a tree index path.\n *\n * @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].\n * @param blocks - The root-level Yjs blocks array.\n * @return A Y.RelativePosition for the block, or null if the path is invalid.\n */\nfunction createRelativePositionForBlockPath(\n\tpath: AbsoluteBlockIndexPath,\n\tblocks: YBlocks\n): Y.RelativePosition | null {\n\tlet currentBlocks = blocks;\n\tfor ( let i = 0; i < path.length; i++ ) {\n\t\tif ( path[ i ] >= currentBlocks.length ) {\n\t\t\treturn null;\n\t\t}\n\t\tif ( i === path.length - 1 ) {\n\t\t\treturn Y.createRelativePositionFromTypeIndex(\n\t\t\t\tcurrentBlocks,\n\t\t\t\tpath[ i ]\n\t\t\t);\n\t\t}\n\t\tconst block = currentBlocks.get( path[ i ] );\n\t\tcurrentBlocks =\n\t\t\tblock?.get( 'innerBlocks' ) ?? ( new Y.Array() as YBlocks );\n\t}\n\treturn null;\n}\n\n/**\n * Check if two selection states are equal.\n *\n * @param selection1 - The first selection state.\n * @param selection2 - The second selection state.\n * @return True if the selection states are equal, false otherwise.\n */\nexport function areSelectionsStatesEqual(\n\tselection1: SelectionState,\n\tselection2: SelectionState\n): boolean {\n\tif ( selection1.type !== selection2.type ) {\n\t\treturn false;\n\t}\n\n\tswitch ( selection1.type ) {\n\t\tcase SelectionType.None:\n\t\t\treturn true;\n\n\t\tcase SelectionType.Cursor:\n\t\t\treturn areCursorPositionsEqual(\n\t\t\t\tselection1.cursorPosition,\n\t\t\t\t( selection2 as SelectionCursor ).cursorPosition\n\t\t\t);\n\n\t\tcase SelectionType.SelectionInOneBlock:\n\t\t\treturn (\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorStartPosition,\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).cursorStartPosition\n\t\t\t\t) &&\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorEndPosition,\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).cursorEndPosition\n\t\t\t\t) &&\n\t\t\t\tselection1.selectionDirection ===\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).selectionDirection\n\t\t\t);\n\n\t\tcase SelectionType.SelectionInMultipleBlocks:\n\t\t\treturn (\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorStartPosition,\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks )\n\t\t\t\t\t\t.cursorStartPosition\n\t\t\t\t) &&\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorEndPosition,\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks )\n\t\t\t\t\t\t.cursorEndPosition\n\t\t\t\t) &&\n\t\t\t\tselection1.selectionDirection ===\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks )\n\t\t\t\t\t\t.selectionDirection\n\t\t\t);\n\t\tcase SelectionType.WholeBlock:\n\t\t\treturn Y.compareRelativePositions(\n\t\t\t\tselection1.blockPosition,\n\t\t\t\t( selection2 as SelectionWholeBlock ).blockPosition\n\t\t\t);\n\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Check if two cursor positions are equal.\n *\n * @param cursorPosition1 - The first cursor position.\n * @param cursorPosition2 - The second cursor position.\n * @return True if the cursor positions are equal, false otherwise.\n */\nfunction areCursorPositionsEqual(\n\tcursorPosition1: CursorPosition,\n\tcursorPosition2: CursorPosition\n): boolean {\n\tconst isRelativePositionEqual = Y.compareRelativePositions(\n\t\tcursorPosition1.relativePosition,\n\t\tcursorPosition2.relativePosition\n\t);\n\n\t// Ensure a change in calculated absolute offset results in a treating the cursor as modified.\n\t// This is necessary because Y.Text relative positions can remain the same after text changes.\n\tconst isAbsoluteOffsetEqual =\n\t\tcursorPosition1.absoluteOffset === cursorPosition2.absoluteOffset;\n\n\treturn isRelativePositionEqual && isAbsoluteOffsetEqual;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAAuB;AACvB,kBAAkB;AAElB,0BAA0C;AAK1C,IAAAA,eAAoC;AAGpC,
|
|
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 { CRDT_RECORD_MAP_KEY } from '../sync';\nimport type { YPostRecord } from './crdt';\nimport type { YBlock, YBlocks } from './crdt-blocks';\nimport {\n\tasRichTextOffset,\n\tgetRootMap,\n\trichTextOffsetToHtmlIndex,\n} from './crdt-utils';\nimport type {\n\tAbsoluteBlockIndexPath,\n\tWPBlockSelection,\n\tSelectionState,\n\tSelectionNone,\n\tSelectionCursor,\n\tSelectionInOneBlock,\n\tSelectionInMultipleBlocks,\n\tSelectionWholeBlock,\n\tSelectionDirection,\n\tCursorPosition,\n} from '../types';\n\n/**\n * The type of selection.\n */\nexport enum SelectionType {\n\tNone = 'none',\n\tCursor = 'cursor',\n\tSelectionInOneBlock = 'selection-in-one-block',\n\tSelectionInMultipleBlocks = 'selection-in-multiple-blocks',\n\tWholeBlock = 'whole-block',\n}\n\n/**\n * Converts WordPress block editor selection to a SelectionState.\n *\n * Uses getBlockPathForLocalClientId to locate blocks in the Yjs document by\n * their tree position (index path) rather than clientId, since clientIds may\n * differ between the block-editor store and the Yjs document (e.g. in \"Show\n * Template\" mode).\n *\n * @param selectionStart - The start position of the selection\n * @param selectionEnd - The end position of the selection\n * @param yDoc - The Yjs document\n * @param options - Optional parameters\n * @param options.selectionDirection - The direction of the selection (forward or backward)\n * @return The SelectionState\n */\nexport function getSelectionState(\n\tselectionStart: WPBlockSelection,\n\tselectionEnd: WPBlockSelection,\n\tyDoc: Y.Doc,\n\toptions?: { selectionDirection?: SelectionDirection }\n): SelectionState {\n\tconst { selectionDirection } = options ?? {};\n\tconst ymap = getRootMap< YPostRecord >( yDoc, CRDT_RECORD_MAP_KEY );\n\tconst yBlocks = ymap.get( 'blocks' );\n\n\tconst isSelectionEmpty = Object.keys( selectionStart ).length === 0;\n\tconst noSelection: SelectionNone = {\n\t\ttype: SelectionType.None,\n\t};\n\n\tif ( isSelectionEmpty || ! yBlocks ) {\n\t\t// Case 1: No selection, or no blocks in the document.\n\t\treturn noSelection;\n\t}\n\n\t// When the page initially loads, selectionStart can contain an empty object `{}`.\n\tconst isSelectionInOneBlock =\n\t\tselectionStart.clientId === selectionEnd.clientId;\n\tconst isCursorOnly =\n\t\tisSelectionInOneBlock && selectionStart.offset === selectionEnd.offset;\n\tconst isSelectionAWholeBlock =\n\t\tisSelectionInOneBlock &&\n\t\tselectionStart.offset === undefined &&\n\t\tselectionEnd.offset === undefined;\n\n\tif ( isSelectionAWholeBlock ) {\n\t\t// Case 2: A whole block is selected.\n\t\tconst path = getBlockPathForLocalClientId( selectionStart.clientId );\n\t\tconst blockPosition = path\n\t\t\t? createRelativePositionForBlockPath( path, yBlocks )\n\t\t\t: null;\n\n\t\tif ( ! blockPosition ) {\n\t\t\treturn noSelection;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: SelectionType.WholeBlock,\n\t\t\tblockPosition,\n\t\t};\n\t} else if ( isCursorOnly ) {\n\t\t// Case 3: Cursor only, no text selected\n\t\tconst cursorPosition = getCursorPosition( selectionStart, yBlocks );\n\n\t\tif ( ! cursorPosition ) {\n\t\t\t// If we can't find the cursor position in block text, treat it as a non-selection.\n\t\t\treturn noSelection;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: SelectionType.Cursor,\n\t\t\tcursorPosition,\n\t\t};\n\t} else if ( isSelectionInOneBlock ) {\n\t\t// Case 4: Selection in a single block\n\t\tconst cursorStartPosition = getCursorPosition(\n\t\t\tselectionStart,\n\t\t\tyBlocks\n\t\t);\n\t\tconst cursorEndPosition = getCursorPosition( selectionEnd, yBlocks );\n\n\t\tif ( ! cursorStartPosition || ! cursorEndPosition ) {\n\t\t\t// If we can't find the cursor positions in block text, treat it as a non-selection.\n\t\t\treturn noSelection;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: SelectionType.SelectionInOneBlock,\n\t\t\tcursorStartPosition,\n\t\t\tcursorEndPosition,\n\t\t\tselectionDirection,\n\t\t};\n\t}\n\n\t// Case 5: Selection in multiple blocks\n\tconst cursorStartPosition = getCursorPosition( selectionStart, yBlocks );\n\tconst cursorEndPosition = getCursorPosition( selectionEnd, yBlocks );\n\tif ( ! cursorStartPosition || ! cursorEndPosition ) {\n\t\t// If we can't find the cursor positions in block text, treat it as a non-selection.\n\t\treturn noSelection;\n\t}\n\n\treturn {\n\t\ttype: SelectionType.SelectionInMultipleBlocks,\n\t\tcursorStartPosition,\n\t\tcursorEndPosition,\n\t\tselectionDirection,\n\t};\n}\n\n/**\n * Get the cursor position from a selection.\n *\n * @param selection - The selection.\n * @param blocks - The blocks to search through.\n * @return The cursor position, or null if not found.\n */\nfunction getCursorPosition(\n\tselection: WPBlockSelection,\n\tblocks: YBlocks\n): CursorPosition | null {\n\tconst path = getBlockPathForLocalClientId( selection.clientId );\n\tconst block = path ? findBlockByPath( path, blocks ) : null;\n\tif (\n\t\t! block ||\n\t\t! selection.attributeKey ||\n\t\tundefined === selection.offset\n\t) {\n\t\treturn null;\n\t}\n\n\tconst attributes = block.get( 'attributes' );\n\tconst currentYText = attributes?.get( selection.attributeKey );\n\n\t// If the attribute is not a Y.Text, return null.\n\tif ( ! ( currentYText instanceof Y.Text ) ) {\n\t\treturn null;\n\t}\n\n\tconst relativePosition = Y.createRelativePositionFromTypeIndex(\n\t\tcurrentYText,\n\t\trichTextOffsetToHtmlIndex(\n\t\t\tcurrentYText.toString(),\n\t\t\tasRichTextOffset( selection.offset )\n\t\t)\n\t);\n\n\treturn {\n\t\trelativePosition,\n\t\tabsoluteOffset: selection.offset,\n\t};\n}\n\n/**\n * Resolves a local block-editor clientId to its index path relative to the\n * post content blocks. This allows finding the corresponding block in the Yjs\n * document even when clientIds differ (e.g. in \"Show Template\" mode where\n * blocks are cloned).\n *\n * In template mode, the block tree includes template parts and wrapper blocks\n * around a core/post-content block. The Yjs document only contains the post\n * content blocks, so we stop the upward walk when the parent is\n * core/post-content (its inner blocks correspond to the Yjs root blocks).\n *\n * @param clientId - The local block-editor clientId to resolve.\n * @return The index path from root, or null if not resolvable.\n */\nexport function getBlockPathForLocalClientId(\n\tclientId: string\n): AbsoluteBlockIndexPath | null {\n\tconst { getBlockIndex, getBlockRootClientId, getBlockName } =\n\t\tselect( blockEditorStore );\n\n\tconst path: AbsoluteBlockIndexPath = [];\n\tlet current: string | null = clientId;\n\twhile ( current ) {\n\t\tconst index = getBlockIndex( current );\n\t\tif ( index === -1 ) {\n\t\t\treturn null;\n\t\t}\n\t\tpath.unshift( index );\n\t\tconst parent = getBlockRootClientId( current );\n\t\tif ( ! parent ) {\n\t\t\tbreak;\n\t\t}\n\t\t// If the parent is core/post-content, stop here \u2014 the Yjs doc\n\t\t// root blocks correspond to post-content's inner blocks.\n\t\tconst parentName = getBlockName( parent );\n\t\tif ( parentName === 'core/post-content' ) {\n\t\t\tbreak;\n\t\t}\n\t\tcurrent = parent;\n\t}\n\treturn path.length > 0 ? path : null;\n}\n\n/**\n * Find a block by navigating a tree index path in the Yjs block hierarchy.\n *\n * @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].\n * @param blocks - The root-level Yjs blocks array.\n * @return The block Y.Map if found, null otherwise.\n */\nfunction findBlockByPath(\n\tpath: AbsoluteBlockIndexPath,\n\tblocks: YBlocks\n): YBlock | null {\n\tlet currentBlocks = blocks;\n\tfor ( let i = 0; i < path.length; i++ ) {\n\t\tif ( path[ i ] >= currentBlocks.length ) {\n\t\t\treturn null;\n\t\t}\n\t\tconst block = currentBlocks.get( 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;\n\t\t}\n\t\tcurrentBlocks =\n\t\t\tblock.get( 'innerBlocks' ) ?? ( new Y.Array() as YBlocks );\n\t}\n\treturn null;\n}\n\n/**\n * Create a Y.RelativePosition for a block by navigating a tree index path.\n *\n * @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].\n * @param blocks - The root-level Yjs blocks array.\n * @return A Y.RelativePosition for the block, or null if the path is invalid.\n */\nfunction createRelativePositionForBlockPath(\n\tpath: AbsoluteBlockIndexPath,\n\tblocks: YBlocks\n): Y.RelativePosition | null {\n\tlet currentBlocks = blocks;\n\tfor ( let i = 0; i < path.length; i++ ) {\n\t\tif ( path[ i ] >= currentBlocks.length ) {\n\t\t\treturn null;\n\t\t}\n\t\tif ( i === path.length - 1 ) {\n\t\t\treturn Y.createRelativePositionFromTypeIndex(\n\t\t\t\tcurrentBlocks,\n\t\t\t\tpath[ i ]\n\t\t\t);\n\t\t}\n\t\tconst block = currentBlocks.get( path[ i ] );\n\t\tcurrentBlocks =\n\t\t\tblock?.get( 'innerBlocks' ) ?? ( new Y.Array() as YBlocks );\n\t}\n\treturn null;\n}\n\n/**\n * Check if two selection states are equal.\n *\n * @param selection1 - The first selection state.\n * @param selection2 - The second selection state.\n * @return True if the selection states are equal, false otherwise.\n */\nexport function areSelectionsStatesEqual(\n\tselection1: SelectionState,\n\tselection2: SelectionState\n): boolean {\n\tif ( selection1.type !== selection2.type ) {\n\t\treturn false;\n\t}\n\n\tswitch ( selection1.type ) {\n\t\tcase SelectionType.None:\n\t\t\treturn true;\n\n\t\tcase SelectionType.Cursor:\n\t\t\treturn areCursorPositionsEqual(\n\t\t\t\tselection1.cursorPosition,\n\t\t\t\t( selection2 as SelectionCursor ).cursorPosition\n\t\t\t);\n\n\t\tcase SelectionType.SelectionInOneBlock:\n\t\t\treturn (\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorStartPosition,\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).cursorStartPosition\n\t\t\t\t) &&\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorEndPosition,\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).cursorEndPosition\n\t\t\t\t) &&\n\t\t\t\tselection1.selectionDirection ===\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).selectionDirection\n\t\t\t);\n\n\t\tcase SelectionType.SelectionInMultipleBlocks:\n\t\t\treturn (\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorStartPosition,\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks )\n\t\t\t\t\t\t.cursorStartPosition\n\t\t\t\t) &&\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorEndPosition,\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks )\n\t\t\t\t\t\t.cursorEndPosition\n\t\t\t\t) &&\n\t\t\t\tselection1.selectionDirection ===\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks )\n\t\t\t\t\t\t.selectionDirection\n\t\t\t);\n\t\tcase SelectionType.WholeBlock:\n\t\t\treturn Y.compareRelativePositions(\n\t\t\t\tselection1.blockPosition,\n\t\t\t\t( selection2 as SelectionWholeBlock ).blockPosition\n\t\t\t);\n\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Check if two cursor positions are equal.\n *\n * @param cursorPosition1 - The first cursor position.\n * @param cursorPosition2 - The second cursor position.\n * @return True if the cursor positions are equal, false otherwise.\n */\nfunction areCursorPositionsEqual(\n\tcursorPosition1: CursorPosition,\n\tcursorPosition2: CursorPosition\n): boolean {\n\tconst isRelativePositionEqual = Y.compareRelativePositions(\n\t\tcursorPosition1.relativePosition,\n\t\tcursorPosition2.relativePosition\n\t);\n\n\t// Ensure a change in calculated absolute offset results in a treating the cursor as modified.\n\t// This is necessary because Y.Text relative positions can remain the same after text changes.\n\tconst isAbsoluteOffsetEqual =\n\t\tcursorPosition1.absoluteOffset === cursorPosition2.absoluteOffset;\n\n\treturn isRelativePositionEqual && isAbsoluteOffsetEqual;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_sync", "SelectionType", "cursorStartPosition", "cursorEndPosition", "blockEditorStore"]
|
|
7
7
|
}
|
|
@@ -20,6 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// packages/core-data/src/utils/crdt-utils.ts
|
|
21
21
|
var crdt_utils_exports = {};
|
|
22
22
|
__export(crdt_utils_exports, {
|
|
23
|
+
asHtmlStringIndex: () => asHtmlStringIndex,
|
|
24
|
+
asRichTextOffset: () => asRichTextOffset,
|
|
23
25
|
createYMap: () => createYMap,
|
|
24
26
|
findBlockByClientIdInDoc: () => findBlockByClientIdInDoc,
|
|
25
27
|
getRootMap: () => getRootMap,
|
|
@@ -40,6 +42,12 @@ function createYMap(partial = {}) {
|
|
|
40
42
|
function isYMap(value) {
|
|
41
43
|
return value instanceof import_sync.Y.Map;
|
|
42
44
|
}
|
|
45
|
+
function asRichTextOffset(offset) {
|
|
46
|
+
return offset;
|
|
47
|
+
}
|
|
48
|
+
function asHtmlStringIndex(index) {
|
|
49
|
+
return index;
|
|
50
|
+
}
|
|
43
51
|
function findBlockByClientIdInDoc(blockId, ydoc) {
|
|
44
52
|
const ymap = getRootMap(ydoc, import_sync2.CRDT_RECORD_MAP_KEY);
|
|
45
53
|
const blocks = ymap.get("blocks");
|
|
@@ -61,24 +69,24 @@ function pickMarker(text) {
|
|
|
61
69
|
}
|
|
62
70
|
function htmlIndexToRichTextOffset(html, htmlIndex) {
|
|
63
71
|
if (!html.includes("<") && !html.includes("&")) {
|
|
64
|
-
return htmlIndex;
|
|
72
|
+
return asRichTextOffset(htmlIndex);
|
|
65
73
|
}
|
|
66
74
|
const marker = pickMarker(html);
|
|
67
75
|
if (!marker) {
|
|
68
|
-
return htmlIndex;
|
|
76
|
+
return asRichTextOffset(htmlIndex);
|
|
69
77
|
}
|
|
70
78
|
const withMarker = html.slice(0, htmlIndex) + marker + html.slice(htmlIndex);
|
|
71
79
|
const value = (0, import_rich_text.create)({ html: withMarker });
|
|
72
80
|
const markerPos = value.text.indexOf(marker);
|
|
73
|
-
return markerPos === -1 ? htmlIndex : markerPos;
|
|
81
|
+
return asRichTextOffset(markerPos === -1 ? htmlIndex : markerPos);
|
|
74
82
|
}
|
|
75
83
|
function richTextOffsetToHtmlIndex(html, richTextOffset) {
|
|
76
84
|
if (!html.includes("<") && !html.includes("&")) {
|
|
77
|
-
return richTextOffset;
|
|
85
|
+
return asHtmlStringIndex(richTextOffset);
|
|
78
86
|
}
|
|
79
87
|
const marker = pickMarker(html);
|
|
80
88
|
if (!marker) {
|
|
81
|
-
return richTextOffset;
|
|
89
|
+
return asHtmlStringIndex(richTextOffset);
|
|
82
90
|
}
|
|
83
91
|
const value = (0, import_rich_text.create)({ html });
|
|
84
92
|
const markerValue = (0, import_rich_text.create)({ text: marker });
|
|
@@ -93,7 +101,9 @@ function richTextOffsetToHtmlIndex(html, richTextOffset) {
|
|
|
93
101
|
);
|
|
94
102
|
const htmlWithMarker = (0, import_rich_text.toHTMLString)({ value: withMarker });
|
|
95
103
|
const markerIndex = htmlWithMarker.indexOf(marker);
|
|
96
|
-
return
|
|
104
|
+
return asHtmlStringIndex(
|
|
105
|
+
markerIndex === -1 ? richTextOffset : markerIndex
|
|
106
|
+
);
|
|
97
107
|
}
|
|
98
108
|
function findBlockByClientIdInBlocks(blockId, blocks) {
|
|
99
109
|
for (const block of blocks) {
|
|
@@ -115,6 +125,8 @@ function findBlockByClientIdInBlocks(blockId, blocks) {
|
|
|
115
125
|
}
|
|
116
126
|
// Annotate the CommonJS export names for ESM import in node:
|
|
117
127
|
0 && (module.exports = {
|
|
128
|
+
asHtmlStringIndex,
|
|
129
|
+
asRichTextOffset,
|
|
118
130
|
createYMap,
|
|
119
131
|
findBlockByClientIdInDoc,
|
|
120
132
|
getRootMap,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/crdt-utils.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { Y } from '@wordpress/sync';\nimport { create, insert, toHTMLString } from '@wordpress/rich-text';\n\n/**\n * Internal dependencies\n */\nimport type { YBlock, YBlocks } from './crdt-blocks';\nimport type { YPostRecord } from './crdt';\nimport { CRDT_RECORD_MAP_KEY } from '../sync';\n\n/**\n * A YMapRecord represents the shape of the data stored in a Y.Map.\n */\nexport type YMapRecord = Record< string, unknown >;\n\n/**\n * A wrapper around Y.Map to provide type safety. The generic type accepted by\n * Y.Map represents the union of possible values of the map, which are varied in\n * many cases. This type is accurate, but its non-specificity requires aggressive\n * type narrowing or type casting / destruction with `as`.\n *\n * This type provides type enhancements so that the correct value type can be\n * inferred based on the provided key. It is just a type wrap / overlay, and\n * does not change the runtime behavior of Y.Map.\n *\n * This interface cannot extend Y.Map directly due to the limitations of\n * TypeScript's structural typing. One negative consequence of this is that\n * `instanceof` checks against Y.Map continue to work at runtime but will blur\n * the type at compile time. To navigate this, use the `isYMap` function below.\n */\nexport interface YMapWrap< T extends YMapRecord > extends Y.AbstractType< T > {\n\tdelete: < K extends keyof T >( key: K ) => void;\n\tforEach: (\n\t\tcallback: (\n\t\t\tvalue: T[ keyof T ],\n\t\t\tkey: keyof T,\n\t\t\tmap: YMapWrap< T >\n\t\t) => void\n\t) => void;\n\thas: < K extends keyof T >( key: K ) => boolean;\n\tget: < K extends keyof T >( key: K ) => T[ K ] | undefined;\n\tset: < K extends keyof T >( key: K, value: T[ K ] ) => void;\n\ttoJSON: () => T;\n\t// add types for other Y.Map methods as needed\n}\n\n/**\n * Get or create a root-level Map for the given Y.Doc. Use this instead of\n * doc.getMap() for additional type safety.\n *\n * @param doc Y.Doc\n * @param key Map key\n */\nexport function getRootMap< T extends YMapRecord >(\n\tdoc: Y.Doc,\n\tkey: string\n): YMapWrap< T > {\n\treturn doc.getMap< T >( key ) as unknown as YMapWrap< T >;\n}\n\n/**\n * Create a new Y.Map (provided with YMapWrap type), optionally initialized with\n * data. Use this instead of `new Y.Map()` for additional type safety.\n *\n * @param partial Partial data to initialize the map with.\n */\nexport function createYMap< T extends YMapRecord >(\n\tpartial: Partial< T > = {}\n): YMapWrap< T > {\n\treturn new Y.Map( Object.entries( partial ) ) as unknown as YMapWrap< T >;\n}\n\n/**\n * Type guard to check if a value is a Y.Map without losing type information.\n *\n * @param value Value to check.\n */\nexport function isYMap< T extends YMapRecord >(\n\tvalue: YMapWrap< T > | undefined\n): value is YMapWrap< T > {\n\treturn value instanceof Y.Map;\n}\n\n/**\n * Given a block ID and a Y.Doc, find the block in the document.\n *\n * @param blockId The block ID to find\n * @param ydoc The Y.Doc to find the block in\n * @return The block, or null if the block is not found\n */\nexport function findBlockByClientIdInDoc(\n\tblockId: string,\n\tydoc: Y.Doc\n): YBlock | null {\n\tconst ymap = getRootMap< YPostRecord >( ydoc, CRDT_RECORD_MAP_KEY );\n\tconst blocks = ymap.get( 'blocks' );\n\n\tif ( ! ( blocks instanceof Y.Array ) ) {\n\t\treturn null;\n\t}\n\n\treturn findBlockByClientIdInBlocks( blockId, blocks );\n}\n\n// Marker for insertion.\nconst MARKER_START = 0xe000;\n\n/**\n * Pick a marker character that does not appear in `text`. Returns the marker\n * or `null` if all candidates are present (extremely unlikely in practice).\n *\n * @param text The string to check for existing marker characters.\n */\nfunction pickMarker( text: string ): string | null {\n\tconst tryCount = 0x10;\n\n\t// Scan the unicode private use area for the first code point not present\n\t// in the text.\n\tfor ( let code = MARKER_START; code < MARKER_START + tryCount; code++ ) {\n\t\tconst candidate = String.fromCharCode( code );\n\n\t\tif ( ! text.includes( candidate ) ) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Convert an HTML character index (counting tag characters) to a rich-text\n * offset (counting only text characters). Used on read paths where Y.Text\n * resolves to an HTML index but the block editor expects a text offset.\n *\n * @param html The full HTML string from Y.Text.\n * @param htmlIndex The HTML character index.\n * @return The corresponding rich-text offset.\n */\nexport function htmlIndexToRichTextOffset(\n\thtml: string,\n\thtmlIndex:
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAAkB;AAClB,uBAA6C;AAO7C,IAAAA,eAAoC;AA6C7B,SAAS,WACf,KACA,KACgB;AAChB,SAAO,IAAI,OAAa,GAAI;AAC7B;AAQO,SAAS,WACf,UAAwB,CAAC,GACT;AAChB,SAAO,IAAI,cAAE,IAAK,OAAO,QAAS,OAAQ,CAAE;AAC7C;AAOO,SAAS,OACf,OACyB;AACzB,SAAO,iBAAiB,cAAE;AAC3B;AASO,SAAS,yBACf,SACA,MACgB;AAChB,QAAM,OAAO,WAA2B,MAAM,gCAAoB;AAClE,QAAM,SAAS,KAAK,IAAK,QAAS;AAElC,MAAK,EAAI,kBAAkB,cAAE,QAAU;AACtC,WAAO;AAAA,EACR;AAEA,SAAO,4BAA6B,SAAS,MAAO;AACrD;AAGA,IAAM,eAAe;AAQrB,SAAS,WAAY,MAA8B;AAClD,QAAM,WAAW;AAIjB,WAAU,OAAO,cAAc,OAAO,eAAe,UAAU,QAAS;AACvE,UAAM,YAAY,OAAO,aAAc,IAAK;AAE5C,QAAK,CAAE,KAAK,SAAU,SAAU,GAAI;AACnC,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO;AACR;AAWO,SAAS,0BACf,MACA,
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { Y } from '@wordpress/sync';\nimport { create, insert, toHTMLString } from '@wordpress/rich-text';\n\n/**\n * Internal dependencies\n */\nimport type { YBlock, YBlocks } from './crdt-blocks';\nimport type { YPostRecord } from './crdt';\nimport { CRDT_RECORD_MAP_KEY } from '../sync';\n\n/**\n * A YMapRecord represents the shape of the data stored in a Y.Map.\n */\nexport type YMapRecord = Record< string, unknown >;\n\n/**\n * A wrapper around Y.Map to provide type safety. The generic type accepted by\n * Y.Map represents the union of possible values of the map, which are varied in\n * many cases. This type is accurate, but its non-specificity requires aggressive\n * type narrowing or type casting / destruction with `as`.\n *\n * This type provides type enhancements so that the correct value type can be\n * inferred based on the provided key. It is just a type wrap / overlay, and\n * does not change the runtime behavior of Y.Map.\n *\n * This interface cannot extend Y.Map directly due to the limitations of\n * TypeScript's structural typing. One negative consequence of this is that\n * `instanceof` checks against Y.Map continue to work at runtime but will blur\n * the type at compile time. To navigate this, use the `isYMap` function below.\n */\nexport interface YMapWrap< T extends YMapRecord > extends Y.AbstractType< T > {\n\tdelete: < K extends keyof T >( key: K ) => void;\n\tforEach: (\n\t\tcallback: (\n\t\t\tvalue: T[ keyof T ],\n\t\t\tkey: keyof T,\n\t\t\tmap: YMapWrap< T >\n\t\t) => void\n\t) => void;\n\thas: < K extends keyof T >( key: K ) => boolean;\n\tget: < K extends keyof T >( key: K ) => T[ K ] | undefined;\n\tset: < K extends keyof T >( key: K, value: T[ K ] ) => void;\n\ttoJSON: () => T;\n\t// add types for other Y.Map methods as needed\n}\n\n/**\n * Get or create a root-level Map for the given Y.Doc. Use this instead of\n * doc.getMap() for additional type safety.\n *\n * @param doc Y.Doc\n * @param key Map key\n */\nexport function getRootMap< T extends YMapRecord >(\n\tdoc: Y.Doc,\n\tkey: string\n): YMapWrap< T > {\n\treturn doc.getMap< T >( key ) as unknown as YMapWrap< T >;\n}\n\n/**\n * Create a new Y.Map (provided with YMapWrap type), optionally initialized with\n * data. Use this instead of `new Y.Map()` for additional type safety.\n *\n * @param partial Partial data to initialize the map with.\n */\nexport function createYMap< T extends YMapRecord >(\n\tpartial: Partial< T > = {}\n): YMapWrap< T > {\n\treturn new Y.Map( Object.entries( partial ) ) as unknown as YMapWrap< T >;\n}\n\n/**\n * Type guard to check if a value is a Y.Map without losing type information.\n *\n * @param value Value to check.\n */\nexport function isYMap< T extends YMapRecord >(\n\tvalue: YMapWrap< T > | undefined\n): value is YMapWrap< T > {\n\treturn value instanceof Y.Map;\n}\n\ndeclare const richTextOffsetBrand: unique symbol;\ndeclare const htmlStringIndexBrand: unique symbol;\n\n/**\n * Branded type to prevent confusion between HTML string indices and RichText offsets.\n *\n * @see asRichTextOffset()\n */\nexport type RichTextOffset = number & {\n\treadonly [ richTextOffsetBrand ]: 'RichTextOffset';\n};\n\n/**\n * Branded type to prevent confusion between HTML string indices and RichText offsets.\n *\n * @see asHtmlStringIndex()\n */\nexport type HtmlStringIndex = number & {\n\treadonly [ htmlStringIndexBrand ]: 'HtmlStringIndex';\n};\n\n/**\n * Brand a number as an offset into a RichText\u2019s text content.\n *\n * @param offset The rich-text offset to brand.\n * @return The branded rich-text offset.\n */\nexport function asRichTextOffset( offset: number ): RichTextOffset {\n\treturn offset as RichTextOffset;\n}\n\n/**\n * Brand a number as a string index into serialized HTML.\n *\n * @param index The HTML string index to brand.\n * @return The branded HTML string index.\n */\nexport function asHtmlStringIndex( index: number ): HtmlStringIndex {\n\treturn index as HtmlStringIndex;\n}\n\n/**\n * Given a block ID and a Y.Doc, find the block in the document.\n *\n * @param blockId The block ID to find\n * @param ydoc The Y.Doc to find the block in\n * @return The block, or null if the block is not found\n */\nexport function findBlockByClientIdInDoc(\n\tblockId: string,\n\tydoc: Y.Doc\n): YBlock | null {\n\tconst ymap = getRootMap< YPostRecord >( ydoc, CRDT_RECORD_MAP_KEY );\n\tconst blocks = ymap.get( 'blocks' );\n\n\tif ( ! ( blocks instanceof Y.Array ) ) {\n\t\treturn null;\n\t}\n\n\treturn findBlockByClientIdInBlocks( blockId, blocks );\n}\n\n// Marker for insertion.\nconst MARKER_START = 0xe000;\n\n/**\n * Pick a marker character that does not appear in `text`. Returns the marker\n * or `null` if all candidates are present (extremely unlikely in practice).\n *\n * @param text The string to check for existing marker characters.\n */\nfunction pickMarker( text: string ): string | null {\n\tconst tryCount = 0x10;\n\n\t// Scan the unicode private use area for the first code point not present\n\t// in the text.\n\tfor ( let code = MARKER_START; code < MARKER_START + tryCount; code++ ) {\n\t\tconst candidate = String.fromCharCode( code );\n\n\t\tif ( ! text.includes( candidate ) ) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Convert an HTML character index (counting tag characters) to a rich-text\n * offset (counting only text characters). Used on read paths where Y.Text\n * resolves to an HTML index but the block editor expects a text offset.\n *\n * @param html The full HTML string from Y.Text.\n * @param htmlIndex The HTML character index.\n * @return The corresponding rich-text offset.\n */\nexport function htmlIndexToRichTextOffset(\n\thtml: string,\n\thtmlIndex: HtmlStringIndex\n): RichTextOffset {\n\tif ( ! html.includes( '<' ) && ! html.includes( '&' ) ) {\n\t\treturn asRichTextOffset( htmlIndex );\n\t}\n\n\tconst marker = pickMarker( html );\n\tif ( ! marker ) {\n\t\treturn asRichTextOffset( htmlIndex );\n\t}\n\n\t// Insert marker and let create() do the parsing.\n\tconst withMarker =\n\t\thtml.slice( 0, htmlIndex ) + marker + html.slice( htmlIndex );\n\tconst value = create( { html: withMarker } );\n\tconst markerPos = value.text.indexOf( marker );\n\n\treturn asRichTextOffset( markerPos === -1 ? htmlIndex : markerPos );\n}\n\n/**\n * Convert a rich-text offset (counting only text characters) to an HTML\n * character index (counting tag characters). Used on write paths where the\n * block editor provides a text offset but Y.Text expects an HTML index.\n *\n * @param html The full HTML string from Y.Text.\n * @param richTextOffset The rich-text text offset.\n * @return The corresponding HTML character index.\n */\nexport function richTextOffsetToHtmlIndex(\n\thtml: string,\n\trichTextOffset: RichTextOffset\n): HtmlStringIndex {\n\tif ( ! html.includes( '<' ) && ! html.includes( '&' ) ) {\n\t\treturn asHtmlStringIndex( richTextOffset );\n\t}\n\n\tconst marker = pickMarker( html );\n\tif ( ! marker ) {\n\t\treturn asHtmlStringIndex( richTextOffset );\n\t}\n\n\tconst value = create( { html } );\n\tconst markerValue = create( { text: marker } );\n\t// The marker must inherit the formatting at the insertion point so that\n\t// toHTMLString does not split surrounding tags (e.g. <strong>) around it.\n\tif ( value.formats[ richTextOffset ] ) {\n\t\tmarkerValue.formats[ 0 ] = value.formats[ richTextOffset ];\n\t}\n\n\tconst withMarker = insert(\n\t\tvalue,\n\t\tmarkerValue,\n\t\trichTextOffset,\n\t\trichTextOffset\n\t);\n\n\tconst htmlWithMarker = toHTMLString( { value: withMarker } );\n\tconst markerIndex = htmlWithMarker.indexOf( marker );\n\treturn asHtmlStringIndex(\n\t\tmarkerIndex === -1 ? richTextOffset : markerIndex\n\t);\n}\n\nfunction findBlockByClientIdInBlocks(\n\tblockId: string,\n\tblocks: YBlocks\n): YBlock | null {\n\tfor ( const block of blocks ) {\n\t\tif ( block.get( 'clientId' ) === blockId ) {\n\t\t\treturn block;\n\t\t}\n\n\t\tconst innerBlocks = block.get( 'innerBlocks' );\n\n\t\tif ( innerBlocks && innerBlocks.length > 0 ) {\n\t\t\tconst innerBlock = findBlockByClientIdInBlocks(\n\t\t\t\tblockId,\n\t\t\t\tinnerBlocks\n\t\t\t);\n\n\t\t\tif ( innerBlock ) {\n\t\t\t\treturn innerBlock;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_sync"]
|
|
7
7
|
}
|
package/build/utils/crdt.cjs
CHANGED
|
@@ -85,8 +85,10 @@ function applyPostChangesToCRDTDoc(ydoc, changes, syncedProperties) {
|
|
|
85
85
|
currentBlocks = new import_sync.Y.Array();
|
|
86
86
|
ymap.set(key, currentBlocks);
|
|
87
87
|
}
|
|
88
|
-
const
|
|
89
|
-
|
|
88
|
+
const newCursorPosition = parseCursorSelection(
|
|
89
|
+
changes.selection
|
|
90
|
+
);
|
|
91
|
+
(0, import_crdt_blocks.mergeCrdtBlocks)(currentBlocks, newValue, newCursorPosition);
|
|
90
92
|
break;
|
|
91
93
|
}
|
|
92
94
|
case "content":
|
|
@@ -151,6 +153,14 @@ function applyPostChangesToCRDTDoc(ydoc, changes, syncedProperties) {
|
|
|
151
153
|
}, 0);
|
|
152
154
|
}
|
|
153
155
|
}
|
|
156
|
+
function parseCursorSelection(selection) {
|
|
157
|
+
const selectionStart = selection?.selectionStart;
|
|
158
|
+
return selectionStart?.clientId && selectionStart.attributeKey && "number" === typeof selectionStart.offset && Number.isInteger(selectionStart.offset) ? {
|
|
159
|
+
attributeKey: selectionStart.attributeKey,
|
|
160
|
+
clientId: selectionStart.clientId,
|
|
161
|
+
offset: (0, import_crdt_utils.asRichTextOffset)(selectionStart.offset)
|
|
162
|
+
} : null;
|
|
163
|
+
}
|
|
154
164
|
function defaultGetChangesFromCRDTDoc(crdtDoc) {
|
|
155
165
|
return (0, import_crdt_utils.getRootMap)(crdtDoc, import_sync2.CRDT_RECORD_MAP_KEY).toJSON();
|
|
156
166
|
}
|
package/build/utils/crdt.cjs.map
CHANGED
|
@@ -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 { __unstableSerializeAndClean } 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\tdeserializeBlockAttributes,\n\tmergeCrdtBlocks,\n\tmergeRichTextUpdate,\n\ttype Block,\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\tcreateYMap,\n\tgetRootMap,\n\tisYMap,\n\ttype YMapRecord,\n\ttype YMapWrap,\n} from './crdt-utils';\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;\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.\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// Blocks are undefined when they need to be re-parsed from content.\n\t\t\t\tif ( ! newValue ) {\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// Block changes from typing are bundled with a 'selection' update.\n\t\t\t\t// Pass the resulting cursor position to the mergeCrdtBlocks function.\n\t\t\t\tconst cursorPosition =\n\t\t\t\t\tchanges.selection?.selectionStart?.offset ?? null;\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, cursorPosition );\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\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// 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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,iBAA0B;AAK1B,oBAA4C;AAC5C,kBAOO;AAKP,4BAA8B;AAC9B,
|
|
4
|
+
"sourcesContent": ["/**\n * External dependencies\n */\nimport fastDeepEqual from 'fast-deep-equal/es6/index.js';\n\n/**\n * WordPress dependencies\n */\nimport { __unstableSerializeAndClean } 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// Changes that can be applied to a post entity record.\nexport type PostChanges = Partial< Post > & {\n\tblocks?: Block[];\n\tcontent?: Post[ 'content' ] | string;\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.\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// Blocks are undefined when they need to be re-parsed from content.\n\t\t\t\tif ( ! newValue ) {\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// Block changes from typing are bundled with a 'selection' update.\n\t\t\t\t// Pass the resulting cursor position to the mergeCrdtBlocks function.\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// 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 * 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// 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
|
}
|
package/build-module/actions.mjs
CHANGED
|
@@ -292,11 +292,13 @@ var redo = () => ({ select, dispatch }) => {
|
|
|
292
292
|
var __unstableCreateUndoLevel = () => ({ select }) => {
|
|
293
293
|
select.getUndoManager().addRecord();
|
|
294
294
|
};
|
|
295
|
-
var saveEntityRecord = (kind, name, record, {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
295
|
+
var saveEntityRecord = (kind, name, record, options = {}) => async ({ select, resolveSelect, dispatch }) => {
|
|
296
|
+
const {
|
|
297
|
+
isAutosave = false,
|
|
298
|
+
__unstableFetch = apiFetch,
|
|
299
|
+
__unstableSkipSyncUpdate = false,
|
|
300
|
+
throwOnError = false
|
|
301
|
+
} = options;
|
|
300
302
|
logEntityDeprecation(kind, name, "saveEntityRecord");
|
|
301
303
|
const configs = await resolveSelect.getEntitiesConfig(kind);
|
|
302
304
|
const entityConfig = configs.find(
|
|
@@ -440,7 +442,7 @@ var saveEntityRecord = (kind, name, record, {
|
|
|
440
442
|
getSyncManager()?.update(
|
|
441
443
|
`${kind}/${name}`,
|
|
442
444
|
recordId,
|
|
443
|
-
updatedRecord,
|
|
445
|
+
__unstableSkipSyncUpdate ? {} : updatedRecord,
|
|
444
446
|
LOCAL_UNDO_IGNORED_ORIGIN,
|
|
445
447
|
{ isSave: true }
|
|
446
448
|
);
|