@wordpress/editor 14.45.2-next.v.202605131032.0 → 14.47.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/CHANGELOG.md +12 -1
- package/build/components/collab-sidebar/add-note.cjs +6 -0
- package/build/components/collab-sidebar/add-note.cjs.map +2 -2
- package/build/components/collab-sidebar/hooks.cjs +36 -24
- package/build/components/collab-sidebar/hooks.cjs.map +2 -2
- package/build/components/collab-sidebar/index.cjs +19 -10
- package/build/components/collab-sidebar/index.cjs.map +2 -2
- package/build/components/collab-sidebar/note-byline.cjs +16 -9
- package/build/components/collab-sidebar/note-byline.cjs.map +2 -2
- package/build/components/collab-sidebar/notes.cjs +20 -11
- package/build/components/collab-sidebar/notes.cjs.map +2 -2
- package/build/components/collab-sidebar/utils.cjs +42 -2
- package/build/components/collab-sidebar/utils.cjs.map +2 -2
- package/build/components/collaborators-overlay/compute-selection.cjs +39 -15
- package/build/components/collaborators-overlay/compute-selection.cjs.map +3 -3
- package/build/components/collaborators-overlay/use-block-highlighting.cjs +10 -2
- package/build/components/collaborators-overlay/use-block-highlighting.cjs.map +3 -3
- package/build/components/collaborators-overlay/use-render-cursors.cjs +15 -7
- package/build/components/collaborators-overlay/use-render-cursors.cjs.map +3 -3
- package/build/components/collaborators-presence/avatar/component.cjs +5 -1
- package/build/components/collaborators-presence/avatar/component.cjs.map +3 -3
- package/build/components/collaborators-presence/use-collaborator-notifications.cjs +6 -3
- package/build/components/collaborators-presence/use-collaborator-notifications.cjs.map +2 -2
- package/build/components/editor-interface/index.cjs +17 -16
- package/build/components/editor-interface/index.cjs.map +3 -3
- package/build/components/editor-notices/index.cjs +6 -1
- package/build/components/editor-notices/index.cjs.map +3 -3
- package/build/components/global-styles/hooks.cjs +12 -1
- package/build/components/global-styles/hooks.cjs.map +2 -2
- package/build/components/header/index.cjs +11 -6
- package/build/components/header/index.cjs.map +2 -2
- package/build/components/media/media-editor-modal.cjs +14 -1
- package/build/components/media/media-editor-modal.cjs.map +2 -2
- package/build/components/post-card-panel/index.cjs +7 -1
- package/build/components/post-card-panel/index.cjs.map +3 -3
- package/build/components/post-last-revision/index.cjs +28 -8
- package/build/components/post-last-revision/index.cjs.map +2 -2
- package/build/components/post-locked-modal/index.cjs +9 -6
- package/build/components/post-locked-modal/index.cjs.map +2 -2
- package/build/components/post-publish-button/label.cjs +0 -7
- package/build/components/post-publish-button/label.cjs.map +2 -2
- package/build/components/post-publish-panel/prepublish.cjs +8 -1
- package/build/components/post-publish-panel/prepublish.cjs.map +3 -3
- package/build/components/post-revisions-panel/index.cjs +7 -0
- package/build/components/post-revisions-panel/index.cjs.map +2 -2
- package/build/components/post-revisions-preview/diff-markers.cjs +21 -13
- package/build/components/post-revisions-preview/diff-markers.cjs.map +2 -2
- package/build/components/post-revisions-preview/revisions-canvas.cjs +7 -1
- package/build/components/post-revisions-preview/revisions-canvas.cjs.map +2 -2
- package/build/components/post-taxonomies/check.cjs +1 -2
- package/build/components/post-taxonomies/check.cjs.map +2 -2
- package/build/components/post-taxonomies/index.cjs +1 -2
- package/build/components/post-taxonomies/index.cjs.map +2 -2
- package/build/components/preferences-modal/index.cjs +1 -1
- package/build/components/preferences-modal/index.cjs.map +2 -2
- package/build/components/preview-dropdown/index.cjs.map +3 -3
- package/build/components/provider/index.cjs +5 -21
- package/build/components/provider/index.cjs.map +3 -3
- package/build/components/provider/use-block-editor-settings.cjs +16 -9
- package/build/components/provider/use-block-editor-settings.cjs.map +3 -3
- package/build/components/resizable-editor/resize-handle.cjs +24 -16
- package/build/components/resizable-editor/resize-handle.cjs.map +2 -2
- package/build/components/sidebar/header.cjs +16 -21
- package/build/components/sidebar/header.cjs.map +3 -3
- package/build/components/sidebar/index.cjs +2 -6
- package/build/components/sidebar/index.cjs.map +2 -2
- package/build/components/start-page-options/index.cjs +14 -4
- package/build/components/start-page-options/index.cjs.map +2 -2
- package/build/components/start-template-options/index.cjs +8 -6
- package/build/components/start-template-options/index.cjs.map +2 -2
- package/build/components/styles-canvas/style-book.cjs +59 -2
- package/build/components/styles-canvas/style-book.cjs.map +2 -2
- package/build/components/sync-connection-error-modal/index.cjs +10 -7
- package/build/components/sync-connection-error-modal/index.cjs.map +3 -3
- package/build/components/template-actions-panel/block-theme-content.cjs +21 -12
- package/build/components/template-actions-panel/block-theme-content.cjs.map +2 -2
- package/build/dataviews/store/private-actions.cjs.map +2 -2
- package/build/store/private-actions.cjs +17 -1
- package/build/store/private-actions.cjs.map +3 -3
- package/build/store/private-selectors.cjs +18 -0
- package/build/store/private-selectors.cjs.map +2 -2
- package/build/store/selectors.cjs +0 -17
- package/build/store/selectors.cjs.map +2 -2
- package/build/{components/media → utils/media-delete}/index.cjs +12 -13
- package/build/utils/media-delete/index.cjs.map +7 -0
- package/build/utils/media-finalize/index.cjs +3 -1
- package/build/utils/media-finalize/index.cjs.map +2 -2
- package/build/utils/sync-error-messages.cjs +9 -0
- package/build/utils/sync-error-messages.cjs.map +2 -2
- package/build-module/components/collab-sidebar/add-note.mjs +6 -0
- package/build-module/components/collab-sidebar/add-note.mjs.map +2 -2
- package/build-module/components/collab-sidebar/hooks.mjs +42 -25
- package/build-module/components/collab-sidebar/hooks.mjs.map +2 -2
- package/build-module/components/collab-sidebar/index.mjs +19 -10
- package/build-module/components/collab-sidebar/index.mjs.map +2 -2
- package/build-module/components/collab-sidebar/note-byline.mjs +17 -10
- package/build-module/components/collab-sidebar/note-byline.mjs.map +2 -2
- package/build-module/components/collab-sidebar/notes.mjs +26 -13
- package/build-module/components/collab-sidebar/notes.mjs.map +2 -2
- package/build-module/components/collab-sidebar/utils.mjs +38 -2
- package/build-module/components/collab-sidebar/utils.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/compute-selection.mjs +36 -12
- package/build-module/components/collaborators-overlay/compute-selection.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/use-block-highlighting.mjs +10 -3
- package/build-module/components/collaborators-overlay/use-block-highlighting.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/use-render-cursors.mjs +11 -6
- package/build-module/components/collaborators-overlay/use-render-cursors.mjs.map +2 -2
- package/build-module/components/collaborators-presence/avatar/component.mjs +7 -3
- package/build-module/components/collaborators-presence/avatar/component.mjs.map +2 -2
- package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs +6 -3
- package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs.map +2 -2
- package/build-module/components/editor-interface/index.mjs +22 -17
- package/build-module/components/editor-interface/index.mjs.map +2 -2
- package/build-module/components/editor-notices/index.mjs +6 -1
- package/build-module/components/editor-notices/index.mjs.map +2 -2
- package/build-module/components/global-styles/hooks.mjs +12 -1
- package/build-module/components/global-styles/hooks.mjs.map +2 -2
- package/build-module/components/header/index.mjs +11 -6
- package/build-module/components/header/index.mjs.map +2 -2
- package/build-module/components/media/media-editor-modal.mjs +14 -1
- package/build-module/components/media/media-editor-modal.mjs.map +2 -2
- package/build-module/components/post-card-panel/index.mjs +8 -2
- package/build-module/components/post-card-panel/index.mjs.map +2 -2
- package/build-module/components/post-last-revision/index.mjs +29 -9
- package/build-module/components/post-last-revision/index.mjs.map +2 -2
- package/build-module/components/post-locked-modal/index.mjs +9 -6
- package/build-module/components/post-locked-modal/index.mjs.map +2 -2
- package/build-module/components/post-publish-button/label.mjs +0 -7
- package/build-module/components/post-publish-button/label.mjs.map +2 -2
- package/build-module/components/post-publish-panel/prepublish.mjs +9 -2
- package/build-module/components/post-publish-panel/prepublish.mjs.map +2 -2
- package/build-module/components/post-revisions-panel/index.mjs +7 -0
- package/build-module/components/post-revisions-panel/index.mjs.map +2 -2
- package/build-module/components/post-revisions-preview/diff-markers.mjs +22 -14
- package/build-module/components/post-revisions-preview/diff-markers.mjs.map +2 -2
- package/build-module/components/post-revisions-preview/revisions-canvas.mjs +7 -1
- package/build-module/components/post-revisions-preview/revisions-canvas.mjs.map +2 -2
- package/build-module/components/post-taxonomies/check.mjs +1 -2
- package/build-module/components/post-taxonomies/check.mjs.map +2 -2
- package/build-module/components/post-taxonomies/index.mjs +1 -2
- package/build-module/components/post-taxonomies/index.mjs.map +2 -2
- package/build-module/components/preferences-modal/index.mjs +1 -1
- package/build-module/components/preferences-modal/index.mjs.map +2 -2
- package/build-module/components/preview-dropdown/index.mjs +2 -2
- package/build-module/components/preview-dropdown/index.mjs.map +2 -2
- package/build-module/components/provider/index.mjs +6 -22
- package/build-module/components/provider/index.mjs.map +2 -2
- package/build-module/components/provider/use-block-editor-settings.mjs +16 -9
- package/build-module/components/provider/use-block-editor-settings.mjs.map +2 -2
- package/build-module/components/resizable-editor/resize-handle.mjs +26 -18
- package/build-module/components/resizable-editor/resize-handle.mjs.map +2 -2
- package/build-module/components/sidebar/header.mjs +11 -16
- package/build-module/components/sidebar/header.mjs.map +2 -2
- package/build-module/components/sidebar/index.mjs +2 -7
- package/build-module/components/sidebar/index.mjs.map +2 -2
- package/build-module/components/start-page-options/index.mjs +14 -4
- package/build-module/components/start-page-options/index.mjs.map +2 -2
- package/build-module/components/start-template-options/index.mjs +8 -6
- package/build-module/components/start-template-options/index.mjs.map +2 -2
- package/build-module/components/styles-canvas/style-book.mjs +60 -3
- package/build-module/components/styles-canvas/style-book.mjs.map +2 -2
- package/build-module/components/sync-connection-error-modal/index.mjs +14 -8
- package/build-module/components/sync-connection-error-modal/index.mjs.map +2 -2
- package/build-module/components/template-actions-panel/block-theme-content.mjs +21 -13
- package/build-module/components/template-actions-panel/block-theme-content.mjs.map +2 -2
- package/build-module/dataviews/store/private-actions.mjs.map +2 -2
- package/build-module/store/private-actions.mjs +16 -1
- package/build-module/store/private-actions.mjs.map +2 -2
- package/build-module/store/private-selectors.mjs +18 -0
- package/build-module/store/private-selectors.mjs.map +2 -2
- package/build-module/store/selectors.mjs +0 -16
- package/build-module/store/selectors.mjs.map +2 -2
- package/build-module/utils/media-delete/index.mjs +12 -0
- package/build-module/utils/media-delete/index.mjs.map +7 -0
- package/build-module/utils/media-finalize/index.mjs +3 -1
- package/build-module/utils/media-finalize/index.mjs.map +2 -2
- package/build-module/utils/sync-error-messages.mjs +8 -0
- package/build-module/utils/sync-error-messages.mjs.map +2 -2
- package/build-style/style-rtl.css +462 -408
- package/build-style/style.css +462 -408
- package/build-types/components/collab-sidebar/add-note.d.ts.map +1 -1
- package/build-types/components/collab-sidebar/hooks.d.ts.map +1 -1
- package/build-types/components/collab-sidebar/index.d.ts.map +1 -1
- package/build-types/components/collab-sidebar/note-byline.d.ts +3 -0
- package/build-types/components/collab-sidebar/note-byline.d.ts.map +1 -1
- package/build-types/components/collab-sidebar/notes.d.ts.map +1 -1
- package/build-types/components/collab-sidebar/utils.d.ts +33 -0
- package/build-types/components/collab-sidebar/utils.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/compute-selection.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts +3 -0
- package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/use-render-cursors.d.ts.map +1 -1
- package/build-types/components/collaborators-presence/avatar/component.d.ts.map +1 -1
- package/build-types/components/collaborators-presence/use-collaborator-notifications.d.ts.map +1 -1
- package/build-types/components/editor-interface/index.d.ts.map +1 -1
- package/build-types/components/editor-notices/index.d.ts.map +1 -1
- package/build-types/components/global-styles/hooks.d.ts.map +1 -1
- package/build-types/components/media/media-editor-modal.d.ts +6 -2
- package/build-types/components/media/media-editor-modal.d.ts.map +1 -1
- package/build-types/components/post-card-panel/index.d.ts.map +1 -1
- package/build-types/components/post-last-revision/index.d.ts.map +1 -1
- package/build-types/components/post-locked-modal/index.d.ts +1 -6
- package/build-types/components/post-locked-modal/index.d.ts.map +1 -1
- package/build-types/components/post-publish-button/label.d.ts.map +1 -1
- package/build-types/components/post-publish-panel/prepublish.d.ts.map +1 -1
- package/build-types/components/post-revisions-panel/index.d.ts +1 -1
- package/build-types/components/post-revisions-panel/index.d.ts.map +1 -1
- package/build-types/components/post-revisions-preview/diff-markers.d.ts.map +1 -1
- package/build-types/components/post-revisions-preview/revisions-canvas.d.ts.map +1 -1
- package/build-types/components/post-taxonomies/check.d.ts.map +1 -1
- package/build-types/components/post-taxonomies/flat-term-selector.d.ts +1 -6
- package/build-types/components/post-taxonomies/hierarchical-term-selector.d.ts +1 -6
- package/build-types/components/post-taxonomies/index.d.ts.map +1 -1
- package/build-types/components/provider/index.d.ts.map +1 -1
- package/build-types/components/provider/use-block-editor-settings.d.ts.map +1 -1
- package/build-types/components/resizable-editor/resize-handle.d.ts.map +1 -1
- package/build-types/components/sidebar/index.d.ts.map +1 -1
- package/build-types/components/start-page-options/index.d.ts.map +1 -1
- package/build-types/components/start-template-options/index.d.ts.map +1 -1
- package/build-types/components/styles-canvas/style-book.d.ts.map +1 -1
- package/build-types/components/sync-connection-error-modal/index.d.ts.map +1 -1
- package/build-types/components/template-actions-panel/block-theme-content.d.ts.map +1 -1
- package/build-types/dataviews/store/private-actions.d.ts +0 -1
- package/build-types/dataviews/store/private-actions.d.ts.map +1 -1
- package/build-types/store/private-actions.d.ts +15 -0
- package/build-types/store/private-actions.d.ts.map +1 -1
- package/build-types/store/private-selectors.d.ts +10 -0
- package/build-types/store/private-selectors.d.ts.map +1 -1
- package/build-types/store/selectors.d.ts +0 -10
- package/build-types/store/selectors.d.ts.map +1 -1
- package/build-types/utils/get-template-part-icon.d.ts.map +1 -1
- package/build-types/utils/media-delete/index.d.ts +2 -0
- package/build-types/utils/media-delete/index.d.ts.map +1 -0
- package/build-types/utils/media-finalize/index.d.ts +1 -1
- package/build-types/utils/media-finalize/index.d.ts.map +1 -1
- package/build-types/utils/sync-error-messages.d.ts +1 -0
- package/build-types/utils/sync-error-messages.d.ts.map +1 -1
- package/package.json +48 -48
- package/src/components/collab-sidebar/add-note.js +9 -0
- package/src/components/collab-sidebar/hooks.js +53 -29
- package/src/components/collab-sidebar/index.js +28 -14
- package/src/components/collab-sidebar/note-byline.js +15 -10
- package/src/components/collab-sidebar/notes.js +36 -14
- package/src/components/collab-sidebar/test/utils.js +375 -1
- package/src/components/collab-sidebar/utils.js +70 -1
- package/src/components/collaborators-overlay/compute-selection.ts +67 -19
- package/src/components/collaborators-overlay/use-block-highlighting.ts +14 -1
- package/src/components/collaborators-overlay/use-render-cursors.ts +15 -4
- package/src/components/collaborators-presence/avatar/component.tsx +10 -3
- package/src/components/collaborators-presence/avatar/test/index.tsx +50 -18
- package/src/components/collaborators-presence/styles/collaborators-presence.scss +4 -1
- package/src/components/collaborators-presence/test/use-collaborator-notifications.ts +2 -1
- package/src/components/collaborators-presence/use-collaborator-notifications.ts +6 -4
- package/src/components/editor-help/help-topic-row.native.js +2 -2
- package/src/components/editor-interface/index.js +22 -23
- package/src/components/editor-interface/style.scss +4 -0
- package/src/components/editor-notices/index.js +7 -1
- package/src/components/error-boundary/index.native.js +2 -2
- package/src/components/global-styles/hooks.js +26 -0
- package/src/components/global-styles-sidebar/style.scss +0 -9
- package/src/components/header/index.js +12 -12
- package/src/components/media/media-editor-modal.js +20 -2
- package/src/components/offline-status/index.native.js +2 -2
- package/src/components/post-card-panel/index.js +5 -2
- package/src/components/post-last-revision/index.js +37 -9
- package/src/components/post-last-revision/style.scss +0 -3
- package/src/components/post-locked-modal/index.js +8 -5
- package/src/components/post-panel-row/style.scss +1 -0
- package/src/components/post-publish-button/label.js +0 -11
- package/src/components/post-publish-panel/prepublish.js +6 -2
- package/src/components/post-revisions-panel/index.js +8 -0
- package/src/components/post-revisions-preview/diff-markers.js +17 -11
- package/src/components/post-revisions-preview/revisions-canvas.js +7 -1
- package/src/components/post-revisions-preview/style.scss +4 -4
- package/src/components/post-taxonomies/check.js +1 -2
- package/src/components/post-taxonomies/index.js +1 -2
- package/src/components/preferences-modal/index.js +1 -1
- package/src/components/preview-dropdown/index.js +2 -2
- package/src/components/provider/index.js +10 -31
- package/src/components/provider/use-block-editor-settings.js +19 -12
- package/src/components/resizable-editor/resize-handle.js +22 -16
- package/src/components/sidebar/header.js +18 -28
- package/src/components/sidebar/index.js +5 -14
- package/src/components/start-page-options/index.js +19 -4
- package/src/components/start-template-options/index.js +13 -6
- package/src/components/styles-canvas/style-book.js +75 -13
- package/src/components/sync-connection-error-modal/index.tsx +25 -11
- package/src/components/template-actions-panel/block-theme-content.js +19 -13
- package/src/components/text-editor/style.scss +2 -2
- package/src/dataviews/store/private-actions.ts +0 -1
- package/src/store/private-actions.js +27 -0
- package/src/store/private-selectors.js +26 -0
- package/src/store/selectors.js +0 -24
- package/src/store/test/actions.js +34 -0
- package/src/utils/media-delete/index.js +11 -0
- package/src/utils/media-finalize/index.js +6 -1
- package/src/utils/media-finalize/test/index.js +32 -2
- package/src/utils/sync-error-messages.ts +8 -0
- package/src/utils/test/sync-error-messages.js +1 -0
- package/build/components/global-styles-provider/index.cjs +0 -181
- package/build/components/global-styles-provider/index.cjs.map +0 -7
- package/build/components/media/index.cjs.map +0 -7
- package/build/components/media/metadata-panel.cjs +0 -96
- package/build/components/media/metadata-panel.cjs.map +0 -7
- package/build/components/media/preview.cjs +0 -39
- package/build/components/media/preview.cjs.map +0 -7
- package/build-module/components/global-styles-provider/index.mjs +0 -156
- package/build-module/components/global-styles-provider/index.mjs.map +0 -7
- package/build-module/components/media/index.mjs +0 -8
- package/build-module/components/media/index.mjs.map +0 -7
- package/build-module/components/media/metadata-panel.mjs +0 -65
- package/build-module/components/media/metadata-panel.mjs.map +0 -7
- package/build-module/components/media/preview.mjs +0 -21
- package/build-module/components/media/preview.mjs.map +0 -7
- package/build-types/components/global-styles-provider/index.d.ts +0 -16
- package/build-types/components/global-styles-provider/index.d.ts.map +0 -1
- package/build-types/components/media/index.d.ts +0 -3
- package/build-types/components/media/index.d.ts.map +0 -1
- package/build-types/components/media/metadata-panel.d.ts +0 -12
- package/build-types/components/media/metadata-panel.d.ts.map +0 -1
- package/build-types/components/media/preview.d.ts +0 -9
- package/build-types/components/media/preview.d.ts.map +0 -1
- package/src/components/global-styles-provider/index.js +0 -207
- package/src/components/media/index.js +0 -2
- package/src/components/media/metadata-panel.js +0 -77
- package/src/components/media/preview.js +0 -35
|
@@ -1,12 +1,321 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Internal dependencies
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getNoteIdsFromMetadata,
|
|
6
|
+
addNoteIdToMetadata,
|
|
7
|
+
removeNoteIdFromMetadata,
|
|
8
|
+
calculateNotePositions,
|
|
9
|
+
pickPrimaryNote,
|
|
10
|
+
} from '../utils';
|
|
5
11
|
|
|
6
12
|
function makeRect( top ) {
|
|
7
13
|
return { top };
|
|
8
14
|
}
|
|
9
15
|
|
|
16
|
+
describe( 'getNoteIdsFromMetadata', () => {
|
|
17
|
+
it( 'returns empty array for null metadata', () => {
|
|
18
|
+
expect( getNoteIdsFromMetadata( null ) ).toEqual( [] );
|
|
19
|
+
} );
|
|
20
|
+
|
|
21
|
+
it( 'returns empty array for undefined metadata', () => {
|
|
22
|
+
expect( getNoteIdsFromMetadata( undefined ) ).toEqual( [] );
|
|
23
|
+
} );
|
|
24
|
+
|
|
25
|
+
it( 'returns empty array for metadata without noteId', () => {
|
|
26
|
+
expect( getNoteIdsFromMetadata( {} ) ).toEqual( [] );
|
|
27
|
+
expect( getNoteIdsFromMetadata( { name: 'test' } ) ).toEqual( [] );
|
|
28
|
+
} );
|
|
29
|
+
|
|
30
|
+
it( 'returns empty array for noteId of 0', () => {
|
|
31
|
+
expect( getNoteIdsFromMetadata( { noteId: 0 } ) ).toEqual( [] );
|
|
32
|
+
} );
|
|
33
|
+
|
|
34
|
+
it( 'returns empty array for noteId of empty string', () => {
|
|
35
|
+
expect( getNoteIdsFromMetadata( { noteId: '' } ) ).toEqual( [] );
|
|
36
|
+
} );
|
|
37
|
+
|
|
38
|
+
it( 'returns empty array for noteId of false', () => {
|
|
39
|
+
expect( getNoteIdsFromMetadata( { noteId: false } ) ).toEqual( [] );
|
|
40
|
+
} );
|
|
41
|
+
|
|
42
|
+
it( 'returns array from scalar noteId (legacy format)', () => {
|
|
43
|
+
expect( getNoteIdsFromMetadata( { noteId: 42 } ) ).toEqual( [ 42 ] );
|
|
44
|
+
} );
|
|
45
|
+
|
|
46
|
+
it( 'coerces a string-typed legacy noteId to a number', () => {
|
|
47
|
+
expect( getNoteIdsFromMetadata( { noteId: '42' } ) ).toEqual( [ 42 ] );
|
|
48
|
+
} );
|
|
49
|
+
|
|
50
|
+
it( 'drops non-numeric and non-positive ids', () => {
|
|
51
|
+
expect(
|
|
52
|
+
getNoteIdsFromMetadata( { noteId: [ 1, 'abc', -3, 2 ] } )
|
|
53
|
+
).toEqual( [ 1, 2 ] );
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
it( 'returns array from array noteId', () => {
|
|
57
|
+
expect( getNoteIdsFromMetadata( { noteId: [ 1, 2, 3 ] } ) ).toEqual( [
|
|
58
|
+
1, 2, 3,
|
|
59
|
+
] );
|
|
60
|
+
} );
|
|
61
|
+
|
|
62
|
+
it( 'filters out falsy values from array', () => {
|
|
63
|
+
expect(
|
|
64
|
+
getNoteIdsFromMetadata( { noteId: [ 1, null, 2, undefined, 3 ] } )
|
|
65
|
+
).toEqual( [ 1, 2, 3 ] );
|
|
66
|
+
} );
|
|
67
|
+
|
|
68
|
+
it( 'filters out zero and empty string from array', () => {
|
|
69
|
+
expect(
|
|
70
|
+
getNoteIdsFromMetadata( { noteId: [ 0, '', 1, false, 2 ] } )
|
|
71
|
+
).toEqual( [ 1, 2 ] );
|
|
72
|
+
} );
|
|
73
|
+
|
|
74
|
+
it( 'returns empty array when all array values are falsy', () => {
|
|
75
|
+
expect(
|
|
76
|
+
getNoteIdsFromMetadata( { noteId: [ null, undefined, 0, '' ] } )
|
|
77
|
+
).toEqual( [] );
|
|
78
|
+
} );
|
|
79
|
+
|
|
80
|
+
it( 'deduplicates repeated ids while preserving first occurrence order', () => {
|
|
81
|
+
expect( getNoteIdsFromMetadata( { noteId: [ 1, 1, 1 ] } ) ).toEqual( [
|
|
82
|
+
1,
|
|
83
|
+
] );
|
|
84
|
+
expect(
|
|
85
|
+
getNoteIdsFromMetadata( { noteId: [ 1, 2, 1, 3, 2 ] } )
|
|
86
|
+
).toEqual( [ 1, 2, 3 ] );
|
|
87
|
+
} );
|
|
88
|
+
|
|
89
|
+
it( 'deduplicates across numeric and string-typed duplicates', () => {
|
|
90
|
+
expect(
|
|
91
|
+
getNoteIdsFromMetadata( { noteId: [ 1, '1', 2, '2' ] } )
|
|
92
|
+
).toEqual( [ 1, 2 ] );
|
|
93
|
+
} );
|
|
94
|
+
} );
|
|
95
|
+
|
|
96
|
+
describe( 'addNoteIdToMetadata', () => {
|
|
97
|
+
it( 'creates array for first note on empty metadata', () => {
|
|
98
|
+
const result = addNoteIdToMetadata( {}, 42 );
|
|
99
|
+
expect( result.noteId ).toEqual( [ 42 ] );
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
it( 'creates array for first note on null metadata', () => {
|
|
103
|
+
const result = addNoteIdToMetadata( null, 42 );
|
|
104
|
+
expect( result.noteId ).toEqual( [ 42 ] );
|
|
105
|
+
} );
|
|
106
|
+
|
|
107
|
+
it( 'creates array for first note on undefined metadata', () => {
|
|
108
|
+
const result = addNoteIdToMetadata( undefined, 42 );
|
|
109
|
+
expect( result.noteId ).toEqual( [ 42 ] );
|
|
110
|
+
} );
|
|
111
|
+
|
|
112
|
+
it( 'converts scalar noteId to array and appends new id', () => {
|
|
113
|
+
const result = addNoteIdToMetadata( { noteId: 1 }, 2 );
|
|
114
|
+
expect( result.noteId ).toEqual( [ 1, 2 ] );
|
|
115
|
+
} );
|
|
116
|
+
|
|
117
|
+
it( 'appends to existing array', () => {
|
|
118
|
+
const result = addNoteIdToMetadata( { noteId: [ 1, 2 ] }, 3 );
|
|
119
|
+
expect( result.noteId ).toEqual( [ 1, 2, 3 ] );
|
|
120
|
+
} );
|
|
121
|
+
|
|
122
|
+
it( 'prevents duplicates', () => {
|
|
123
|
+
const result = addNoteIdToMetadata( { noteId: [ 1, 2 ] }, 1 );
|
|
124
|
+
expect( result ).toEqual( { noteId: [ 1, 2 ] } );
|
|
125
|
+
} );
|
|
126
|
+
|
|
127
|
+
it( 'preserves other metadata properties', () => {
|
|
128
|
+
const result = addNoteIdToMetadata( { noteId: 1, name: 'test' }, 2 );
|
|
129
|
+
expect( result ).toEqual( { noteId: [ 1, 2 ], name: 'test' } );
|
|
130
|
+
} );
|
|
131
|
+
|
|
132
|
+
it( 'returns original metadata object when duplicate is added', () => {
|
|
133
|
+
const metadata = { noteId: [ 1, 2 ] };
|
|
134
|
+
const result = addNoteIdToMetadata( metadata, 1 );
|
|
135
|
+
expect( result ).toBe( metadata );
|
|
136
|
+
} );
|
|
137
|
+
|
|
138
|
+
it( 'handles adding to metadata with other properties but no noteId', () => {
|
|
139
|
+
const result = addNoteIdToMetadata( { name: 'test' }, 5 );
|
|
140
|
+
expect( result ).toEqual( { name: 'test', noteId: [ 5 ] } );
|
|
141
|
+
} );
|
|
142
|
+
|
|
143
|
+
it( 'dedupes a numeric id against a string-typed legacy id', () => {
|
|
144
|
+
const metadata = { noteId: '5' };
|
|
145
|
+
const result = addNoteIdToMetadata( metadata, 5 );
|
|
146
|
+
expect( result ).toBe( metadata );
|
|
147
|
+
} );
|
|
148
|
+
|
|
149
|
+
it( 'dedupes a string id against a numeric id already in the array', () => {
|
|
150
|
+
const result = addNoteIdToMetadata( { noteId: [ 1, 2 ] }, '2' );
|
|
151
|
+
expect( result ).toEqual( { noteId: [ 1, 2 ] } );
|
|
152
|
+
} );
|
|
153
|
+
} );
|
|
154
|
+
|
|
155
|
+
describe( 'removeNoteIdFromMetadata', () => {
|
|
156
|
+
it( 'removes noteId from array', () => {
|
|
157
|
+
const result = removeNoteIdFromMetadata( { noteId: [ 1, 2, 3 ] }, 2 );
|
|
158
|
+
expect( result.noteId ).toEqual( [ 1, 3 ] );
|
|
159
|
+
} );
|
|
160
|
+
|
|
161
|
+
it( 'returns undefined noteId when array becomes empty', () => {
|
|
162
|
+
const result = removeNoteIdFromMetadata( { noteId: [ 1 ] }, 1 );
|
|
163
|
+
expect( result.noteId ).toBeUndefined();
|
|
164
|
+
} );
|
|
165
|
+
|
|
166
|
+
it( 'handles removing from scalar noteId (legacy format)', () => {
|
|
167
|
+
const result = removeNoteIdFromMetadata( { noteId: 42 }, 42 );
|
|
168
|
+
expect( result.noteId ).toBeUndefined();
|
|
169
|
+
} );
|
|
170
|
+
|
|
171
|
+
it( 'handles removing non-existent id', () => {
|
|
172
|
+
const result = removeNoteIdFromMetadata( { noteId: [ 1, 2 ] }, 99 );
|
|
173
|
+
expect( result.noteId ).toEqual( [ 1, 2 ] );
|
|
174
|
+
} );
|
|
175
|
+
|
|
176
|
+
it( 'handles empty metadata', () => {
|
|
177
|
+
const result = removeNoteIdFromMetadata( {}, 1 );
|
|
178
|
+
expect( result.noteId ).toBeUndefined();
|
|
179
|
+
} );
|
|
180
|
+
|
|
181
|
+
it( 'preserves other metadata properties', () => {
|
|
182
|
+
const result = removeNoteIdFromMetadata(
|
|
183
|
+
{ noteId: [ 1, 2 ], name: 'test' },
|
|
184
|
+
1
|
|
185
|
+
);
|
|
186
|
+
expect( result ).toEqual( { noteId: [ 2 ], name: 'test' } );
|
|
187
|
+
} );
|
|
188
|
+
|
|
189
|
+
it( 'handles null metadata', () => {
|
|
190
|
+
const result = removeNoteIdFromMetadata( null, 1 );
|
|
191
|
+
expect( result.noteId ).toBeUndefined();
|
|
192
|
+
} );
|
|
193
|
+
|
|
194
|
+
it( 'handles undefined metadata', () => {
|
|
195
|
+
const result = removeNoteIdFromMetadata( undefined, 1 );
|
|
196
|
+
expect( result.noteId ).toBeUndefined();
|
|
197
|
+
} );
|
|
198
|
+
|
|
199
|
+
it( 'removes last note and cleans up noteId to undefined', () => {
|
|
200
|
+
const result = removeNoteIdFromMetadata(
|
|
201
|
+
{ noteId: [ 42 ], name: 'test' },
|
|
202
|
+
42
|
|
203
|
+
);
|
|
204
|
+
expect( result ).toEqual( { name: 'test', noteId: undefined } );
|
|
205
|
+
} );
|
|
206
|
+
|
|
207
|
+
it( 'removes a numeric id when stored as a string-typed legacy scalar', () => {
|
|
208
|
+
const result = removeNoteIdFromMetadata( { noteId: '42' }, 42 );
|
|
209
|
+
expect( result.noteId ).toBeUndefined();
|
|
210
|
+
} );
|
|
211
|
+
|
|
212
|
+
it( 'removes a string id when stored as a number in the array', () => {
|
|
213
|
+
const result = removeNoteIdFromMetadata( { noteId: [ 1, 2, 3 ] }, '2' );
|
|
214
|
+
expect( result.noteId ).toEqual( [ 1, 3 ] );
|
|
215
|
+
} );
|
|
216
|
+
} );
|
|
217
|
+
|
|
218
|
+
describe( 'note id order preservation', () => {
|
|
219
|
+
// The collab sidebar relies on insertion order: the first id in the
|
|
220
|
+
// metadata array is treated as the first (block-aligned) note, with
|
|
221
|
+
// subsequent notes stacking below. These tests pin that contract.
|
|
222
|
+
// See https://github.com/WordPress/gutenberg/issues/75145#issuecomment-4361104794
|
|
223
|
+
|
|
224
|
+
it( 'preserves insertion order across multiple sequential adds', () => {
|
|
225
|
+
let metadata = {};
|
|
226
|
+
metadata = addNoteIdToMetadata( metadata, 5 );
|
|
227
|
+
metadata = addNoteIdToMetadata( metadata, 3 );
|
|
228
|
+
metadata = addNoteIdToMetadata( metadata, 7 );
|
|
229
|
+
metadata = addNoteIdToMetadata( metadata, 1 );
|
|
230
|
+
expect( metadata.noteId ).toEqual( [ 5, 3, 7, 1 ] );
|
|
231
|
+
} );
|
|
232
|
+
|
|
233
|
+
it( 'does not sort or reorder ids when adding', () => {
|
|
234
|
+
// A naive implementation might sort numerically; this confirms it
|
|
235
|
+
// preserves the order the user added notes in.
|
|
236
|
+
const result = addNoteIdToMetadata( { noteId: [ 10, 2, 30 ] }, 4 );
|
|
237
|
+
expect( result.noteId ).toEqual( [ 10, 2, 30, 4 ] );
|
|
238
|
+
} );
|
|
239
|
+
|
|
240
|
+
it( 'keeps the first id first after appending more notes', () => {
|
|
241
|
+
let metadata = addNoteIdToMetadata( {}, 42 );
|
|
242
|
+
metadata = addNoteIdToMetadata( metadata, 99 );
|
|
243
|
+
metadata = addNoteIdToMetadata( metadata, 7 );
|
|
244
|
+
const ids = getNoteIdsFromMetadata( metadata );
|
|
245
|
+
expect( ids[ 0 ] ).toBe( 42 );
|
|
246
|
+
} );
|
|
247
|
+
|
|
248
|
+
it( 'preserves order of remaining ids after removing one from the middle', () => {
|
|
249
|
+
const result = removeNoteIdFromMetadata(
|
|
250
|
+
{ noteId: [ 1, 2, 3, 4, 5 ] },
|
|
251
|
+
3
|
|
252
|
+
);
|
|
253
|
+
expect( result.noteId ).toEqual( [ 1, 2, 4, 5 ] );
|
|
254
|
+
} );
|
|
255
|
+
|
|
256
|
+
it( 'preserves remaining ids in order after removing the first id', () => {
|
|
257
|
+
const result = removeNoteIdFromMetadata(
|
|
258
|
+
{ noteId: [ 1, 2, 3, 4 ] },
|
|
259
|
+
1
|
|
260
|
+
);
|
|
261
|
+
expect( result.noteId ).toEqual( [ 2, 3, 4 ] );
|
|
262
|
+
} );
|
|
263
|
+
|
|
264
|
+
it( 'preserves remaining ids in order after removing the last id', () => {
|
|
265
|
+
const result = removeNoteIdFromMetadata(
|
|
266
|
+
{ noteId: [ 1, 2, 3, 4 ] },
|
|
267
|
+
4
|
|
268
|
+
);
|
|
269
|
+
expect( result.noteId ).toEqual( [ 1, 2, 3 ] );
|
|
270
|
+
} );
|
|
271
|
+
|
|
272
|
+
it( 'preserves order across an interleaved sequence of adds and removes', () => {
|
|
273
|
+
let metadata = {};
|
|
274
|
+
metadata = addNoteIdToMetadata( metadata, 10 );
|
|
275
|
+
metadata = addNoteIdToMetadata( metadata, 20 );
|
|
276
|
+
metadata = addNoteIdToMetadata( metadata, 30 );
|
|
277
|
+
metadata = removeNoteIdFromMetadata( metadata, 20 );
|
|
278
|
+
metadata = addNoteIdToMetadata( metadata, 40 );
|
|
279
|
+
expect( metadata.noteId ).toEqual( [ 10, 30, 40 ] );
|
|
280
|
+
} );
|
|
281
|
+
|
|
282
|
+
it( 'preserves array order through a getNoteIdsFromMetadata round-trip', () => {
|
|
283
|
+
const ids = [ 9, 4, 7, 2, 11 ];
|
|
284
|
+
expect( getNoteIdsFromMetadata( { noteId: ids } ) ).toEqual( ids );
|
|
285
|
+
} );
|
|
286
|
+
|
|
287
|
+
it( 'keeps the legacy scalar id as the first id when migrating to an array', () => {
|
|
288
|
+
// When a legacy single-note post gains a second note, the original
|
|
289
|
+
// note must remain the block-aligned (first) note.
|
|
290
|
+
const result = addNoteIdToMetadata( { noteId: 42 }, 99 );
|
|
291
|
+
expect( result.noteId ).toEqual( [ 42, 99 ] );
|
|
292
|
+
expect( result.noteId[ 0 ] ).toBe( 42 );
|
|
293
|
+
} );
|
|
294
|
+
} );
|
|
295
|
+
|
|
296
|
+
describe( 'pickPrimaryNote', () => {
|
|
297
|
+
it( 'returns null for an empty list', () => {
|
|
298
|
+
expect( pickPrimaryNote( [] ) ).toBeNull();
|
|
299
|
+
} );
|
|
300
|
+
|
|
301
|
+
it( 'returns the first unresolved thread when one exists', () => {
|
|
302
|
+
const threads = [
|
|
303
|
+
{ id: 1, status: 'approved' },
|
|
304
|
+
{ id: 2, status: 'hold' },
|
|
305
|
+
{ id: 3, status: 'hold' },
|
|
306
|
+
];
|
|
307
|
+
expect( pickPrimaryNote( threads ) ).toBe( threads[ 1 ] );
|
|
308
|
+
} );
|
|
309
|
+
|
|
310
|
+
it( 'falls back to the first thread when none are unresolved', () => {
|
|
311
|
+
const threads = [
|
|
312
|
+
{ id: 1, status: 'approved' },
|
|
313
|
+
{ id: 2, status: 'approved' },
|
|
314
|
+
];
|
|
315
|
+
expect( pickPrimaryNote( threads ) ).toBe( threads[ 0 ] );
|
|
316
|
+
} );
|
|
317
|
+
} );
|
|
318
|
+
|
|
10
319
|
describe( 'calculateNotePositions', () => {
|
|
11
320
|
it( 'returns empty positions when the anchor thread has no blockRect', () => {
|
|
12
321
|
const { positions } = calculateNotePositions( {
|
|
@@ -152,4 +461,69 @@ describe( 'calculateNotePositions', () => {
|
|
|
152
461
|
// 2: 300 + 500 - 16 = 784
|
|
153
462
|
expect( positions ).toEqual( { 1: 584, 2: 784 } );
|
|
154
463
|
} );
|
|
464
|
+
|
|
465
|
+
it( 'stacks two threads that share a block with the first as anchor', () => {
|
|
466
|
+
const threads = [ { id: 1 }, { id: 2 } ];
|
|
467
|
+
const blockRects = {
|
|
468
|
+
1: makeRect( 200 ),
|
|
469
|
+
2: makeRect( 200 ),
|
|
470
|
+
};
|
|
471
|
+
const heights = { 1: 50, 2: 50 };
|
|
472
|
+
|
|
473
|
+
const { positions } = calculateNotePositions( {
|
|
474
|
+
threads,
|
|
475
|
+
selectedNoteId: 1,
|
|
476
|
+
blockRects,
|
|
477
|
+
heights,
|
|
478
|
+
scrollTop: 0,
|
|
479
|
+
} );
|
|
480
|
+
|
|
481
|
+
// 1 (anchor): 200 - 16 = 184
|
|
482
|
+
// 2 (downward): (184 + 50) - 200 + 20 = 54 → 200 + 54 = 254
|
|
483
|
+
expect( positions ).toEqual( { 1: 184, 2: 254 } );
|
|
484
|
+
} );
|
|
485
|
+
|
|
486
|
+
it( 'stacks two threads that share a block with the second as anchor', () => {
|
|
487
|
+
const threads = [ { id: 1 }, { id: 2 } ];
|
|
488
|
+
const blockRects = {
|
|
489
|
+
1: makeRect( 200 ),
|
|
490
|
+
2: makeRect( 200 ),
|
|
491
|
+
};
|
|
492
|
+
const heights = { 1: 50, 2: 50 };
|
|
493
|
+
|
|
494
|
+
const { positions } = calculateNotePositions( {
|
|
495
|
+
threads,
|
|
496
|
+
selectedNoteId: 2,
|
|
497
|
+
blockRects,
|
|
498
|
+
heights,
|
|
499
|
+
scrollTop: 0,
|
|
500
|
+
} );
|
|
501
|
+
|
|
502
|
+
// 2 (anchor): 200 - 16 = 184
|
|
503
|
+
// 1 (upward): 184 - 200 - 50 - 20 = -86 → 200 + (-86) = 114
|
|
504
|
+
expect( positions ).toEqual( { 1: 114, 2: 184 } );
|
|
505
|
+
} );
|
|
506
|
+
|
|
507
|
+
it( 'stacks three threads sharing a block with middle as anchor', () => {
|
|
508
|
+
const threads = [ { id: 1 }, { id: 2 }, { id: 3 } ];
|
|
509
|
+
const blockRects = {
|
|
510
|
+
1: makeRect( 200 ),
|
|
511
|
+
2: makeRect( 200 ),
|
|
512
|
+
3: makeRect( 200 ),
|
|
513
|
+
};
|
|
514
|
+
const heights = { 1: 50, 2: 50, 3: 50 };
|
|
515
|
+
|
|
516
|
+
const { positions } = calculateNotePositions( {
|
|
517
|
+
threads,
|
|
518
|
+
selectedNoteId: 2,
|
|
519
|
+
blockRects,
|
|
520
|
+
heights,
|
|
521
|
+
scrollTop: 0,
|
|
522
|
+
} );
|
|
523
|
+
|
|
524
|
+
// 2 (anchor): 200 - 16 = 184
|
|
525
|
+
// 3 (downward): (184 + 50) - 200 + 20 = 54 → 254
|
|
526
|
+
// 1 (upward): 184 - 200 - 50 - 20 = -86 → 114
|
|
527
|
+
expect( positions ).toEqual( { 1: 114, 2: 184, 3: 254 } );
|
|
528
|
+
} );
|
|
155
529
|
} );
|
|
@@ -23,7 +23,7 @@ const OVERLAP_MARGIN = 20;
|
|
|
23
23
|
*/
|
|
24
24
|
const AVATAR_BORDER_COLORS = [
|
|
25
25
|
'#C36EFF', // Purple
|
|
26
|
-
'#
|
|
26
|
+
'#D94145', // Red
|
|
27
27
|
'#E4780A', // Orange
|
|
28
28
|
'#FF35EE', // Magenta
|
|
29
29
|
'#879F11', // Olive
|
|
@@ -90,6 +90,75 @@ export function getNoteExcerpt( text, excerptLength = 10 ) {
|
|
|
90
90
|
return isTrimmed ? trimmedExcerpt + '…' : trimmedExcerpt;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Normalizes noteId metadata to always return an array of unique numeric ids,
|
|
95
|
+
* preserving insertion order. Handles both scalar (legacy, possibly
|
|
96
|
+
* string-typed) and array (new) values.
|
|
97
|
+
*
|
|
98
|
+
* @param {Object} metadata Block metadata object
|
|
99
|
+
* @return {number[]} Array of note IDs (may be empty)
|
|
100
|
+
*/
|
|
101
|
+
export function getNoteIdsFromMetadata( metadata ) {
|
|
102
|
+
const noteId = metadata?.noteId;
|
|
103
|
+
const raw = Array.isArray( noteId ) ? noteId : [ noteId ];
|
|
104
|
+
const ids = new Set();
|
|
105
|
+
for ( const value of raw ) {
|
|
106
|
+
const id = Number( value );
|
|
107
|
+
if ( Number.isFinite( id ) && id > 0 ) {
|
|
108
|
+
ids.add( id );
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [ ...ids ];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Adds a note ID to the metadata.
|
|
116
|
+
* Converts scalar to array if needed, otherwise appends.
|
|
117
|
+
*
|
|
118
|
+
* @param {Object} metadata Existing block metadata
|
|
119
|
+
* @param {number} noteId Note ID to add
|
|
120
|
+
* @return {Object} Updated metadata object
|
|
121
|
+
*/
|
|
122
|
+
export function addNoteIdToMetadata( metadata, noteId ) {
|
|
123
|
+
const ids = new Set( getNoteIdsFromMetadata( metadata ) );
|
|
124
|
+
const id = Number( noteId );
|
|
125
|
+
if ( ids.has( id ) ) {
|
|
126
|
+
return metadata;
|
|
127
|
+
}
|
|
128
|
+
ids.add( id );
|
|
129
|
+
return { ...metadata, noteId: [ ...ids ] };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Picks the most relevant thread from a list: first unresolved, else first.
|
|
134
|
+
*
|
|
135
|
+
* @param {Array} threads Ordered list of thread objects.
|
|
136
|
+
* @return {Object|null} Selected thread or null when the list is empty.
|
|
137
|
+
*/
|
|
138
|
+
export function pickPrimaryNote( threads ) {
|
|
139
|
+
return (
|
|
140
|
+
threads.find( ( thread ) => thread.status === 'hold' ) ??
|
|
141
|
+
threads[ 0 ] ??
|
|
142
|
+
null
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Removes a note ID from the metadata.
|
|
148
|
+
*
|
|
149
|
+
* @param {Object} metadata Existing block metadata
|
|
150
|
+
* @param {number} noteId Note ID to remove
|
|
151
|
+
* @return {Object} Updated metadata object
|
|
152
|
+
*/
|
|
153
|
+
export function removeNoteIdFromMetadata( metadata, noteId ) {
|
|
154
|
+
const ids = new Set( getNoteIdsFromMetadata( metadata ) );
|
|
155
|
+
ids.delete( Number( noteId ) );
|
|
156
|
+
return {
|
|
157
|
+
...metadata,
|
|
158
|
+
noteId: ids.size > 0 ? [ ...ids ] : undefined,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
93
162
|
/**
|
|
94
163
|
* Calculate final top positions for all floating note threads in the
|
|
95
164
|
* editor's content coordinate space. Adjusts positions to prevent overlapping
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import { privateApis as coreDataPrivateApis } from '@wordpress/core-data';
|
|
2
|
+
import type {
|
|
3
|
+
CoreDataPrivateApis,
|
|
4
|
+
ResolvedSelection,
|
|
5
|
+
} from '@wordpress/core-data';
|
|
3
6
|
|
|
7
|
+
import { unlock } from '../../lock-unlock';
|
|
4
8
|
import {
|
|
5
9
|
getCursorPosition,
|
|
6
10
|
getSelectionRects,
|
|
@@ -10,6 +14,10 @@ import {
|
|
|
10
14
|
} from './cursor-dom-utils';
|
|
11
15
|
import type { CursorCoords, SelectionRect } from './cursor-dom-utils';
|
|
12
16
|
|
|
17
|
+
const { SelectionDirection, SelectionType } = unlock(
|
|
18
|
+
coreDataPrivateApis
|
|
19
|
+
) as Pick< CoreDataPrivateApis, 'SelectionDirection' | 'SelectionType' >;
|
|
20
|
+
|
|
13
21
|
/** Common parameters passed to cursor/selection computation helpers. */
|
|
14
22
|
interface OverlayContext {
|
|
15
23
|
editorDocument: Document;
|
|
@@ -36,6 +44,44 @@ export interface SelectionVisual {
|
|
|
36
44
|
selectionRects?: SelectionRect[];
|
|
37
45
|
}
|
|
38
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the most specific editor element the selection refers to.
|
|
49
|
+
*
|
|
50
|
+
* When the sender carries an `attributeKey`, narrow to the RichText element
|
|
51
|
+
* matching `data-wp-block-attribute-key` inside the block. This is what makes
|
|
52
|
+
* cursor placement work for blocks with multiple RichText fields (e.g.
|
|
53
|
+
* `core/table` cells: `body.0.cells.0.content`, etc.). Falls back to the
|
|
54
|
+
* block element when `attributeKey` is missing (WholeBlock selections,
|
|
55
|
+
* older senders, or DOM lookup miss).
|
|
56
|
+
*
|
|
57
|
+
* @param editorDocument - The editor document.
|
|
58
|
+
* @param resolvedSelection - The resolved selection.
|
|
59
|
+
* @return The target element (RichText editable or block), or null.
|
|
60
|
+
*/
|
|
61
|
+
function resolveTargetElement(
|
|
62
|
+
editorDocument: Document,
|
|
63
|
+
resolvedSelection: ResolvedSelection
|
|
64
|
+
): HTMLElement | null {
|
|
65
|
+
if ( ! resolvedSelection.localClientId ) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const blockElement = editorDocument.querySelector< HTMLElement >(
|
|
70
|
+
`[data-block="${ resolvedSelection.localClientId }"]`
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if ( ! blockElement || ! resolvedSelection.attributeKey ) {
|
|
74
|
+
return blockElement;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const attrKey = CSS.escape( resolvedSelection.attributeKey );
|
|
78
|
+
return (
|
|
79
|
+
blockElement.querySelector< HTMLElement >(
|
|
80
|
+
`[data-wp-block-attribute-key="${ attrKey }"]`
|
|
81
|
+
) ?? blockElement
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
39
85
|
/**
|
|
40
86
|
* Compute cursor coords and optional selection rects for a single user's selection.
|
|
41
87
|
*
|
|
@@ -83,14 +129,14 @@ function computeCursorOnly(
|
|
|
83
129
|
if ( ! start.localClientId ) {
|
|
84
130
|
return {};
|
|
85
131
|
}
|
|
86
|
-
const
|
|
87
|
-
overlayContext.editorDocument
|
|
88
|
-
|
|
89
|
-
|
|
132
|
+
const targetElement = resolveTargetElement(
|
|
133
|
+
overlayContext.editorDocument,
|
|
134
|
+
start
|
|
135
|
+
);
|
|
90
136
|
return {
|
|
91
137
|
coords: getCursorPosition(
|
|
92
138
|
start.richTextOffset,
|
|
93
|
-
|
|
139
|
+
targetElement,
|
|
94
140
|
overlayContext.editorDocument,
|
|
95
141
|
overlayContext.overlayRect
|
|
96
142
|
),
|
|
@@ -157,10 +203,10 @@ function computeTextSelection(
|
|
|
157
203
|
}
|
|
158
204
|
|
|
159
205
|
// Fallback: cursor at start position only.
|
|
160
|
-
const startBlock =
|
|
161
|
-
overlayContext.editorDocument
|
|
162
|
-
|
|
163
|
-
|
|
206
|
+
const startBlock = resolveTargetElement(
|
|
207
|
+
overlayContext.editorDocument,
|
|
208
|
+
start
|
|
209
|
+
);
|
|
164
210
|
|
|
165
211
|
return {
|
|
166
212
|
coords: getCursorPosition(
|
|
@@ -185,10 +231,10 @@ function computeSingleBlockRects(
|
|
|
185
231
|
end: ResolvedSelection,
|
|
186
232
|
overlayContext: OverlayContext
|
|
187
233
|
): SingleBlockResult {
|
|
188
|
-
const blockElement =
|
|
189
|
-
overlayContext.editorDocument
|
|
190
|
-
|
|
191
|
-
|
|
234
|
+
const blockElement = resolveTargetElement(
|
|
235
|
+
overlayContext.editorDocument,
|
|
236
|
+
start
|
|
237
|
+
);
|
|
192
238
|
if (
|
|
193
239
|
! blockElement ||
|
|
194
240
|
start.richTextOffset === null ||
|
|
@@ -227,11 +273,13 @@ function computeMultiBlockRects(
|
|
|
227
273
|
): MultiBlockResult {
|
|
228
274
|
let docFirst = start;
|
|
229
275
|
let docLast = end;
|
|
230
|
-
let firstBlock =
|
|
231
|
-
|
|
276
|
+
let firstBlock = resolveTargetElement(
|
|
277
|
+
overlayContext.editorDocument,
|
|
278
|
+
docFirst
|
|
232
279
|
);
|
|
233
|
-
let lastBlock =
|
|
234
|
-
|
|
280
|
+
let lastBlock = resolveTargetElement(
|
|
281
|
+
overlayContext.editorDocument,
|
|
282
|
+
docLast
|
|
235
283
|
);
|
|
236
284
|
|
|
237
285
|
// Swap to document order if needed.
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
+
// @ts-expect-error No exported types
|
|
5
|
+
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
4
6
|
import {
|
|
5
7
|
privateApis as coreDataPrivateApis,
|
|
6
|
-
|
|
8
|
+
type CoreDataPrivateApis,
|
|
7
9
|
type PostEditorAwarenessState as ActiveCollaborator,
|
|
8
10
|
} from '@wordpress/core-data';
|
|
11
|
+
import { useSelect } from '@wordpress/data';
|
|
9
12
|
import { useEffect, useRef, useState } from '@wordpress/element';
|
|
10
13
|
|
|
11
14
|
/**
|
|
@@ -18,6 +21,10 @@ import { useDebouncedRecompute } from './use-debounced-recompute';
|
|
|
18
21
|
|
|
19
22
|
const { useActiveCollaborators, useResolvedSelection } =
|
|
20
23
|
unlock( coreDataPrivateApis );
|
|
24
|
+
const { SelectionType } = unlock( coreDataPrivateApis ) as Pick<
|
|
25
|
+
CoreDataPrivateApis,
|
|
26
|
+
'SelectionType'
|
|
27
|
+
>;
|
|
21
28
|
|
|
22
29
|
export interface BlockHighlightData {
|
|
23
30
|
blockId: string;
|
|
@@ -67,6 +74,11 @@ export function useBlockHighlighting(
|
|
|
67
74
|
const [ recomputeToken, rerenderHighlightsAfterDelay ] =
|
|
68
75
|
useDebouncedRecompute( delayMs );
|
|
69
76
|
|
|
77
|
+
const blockClientIds = useSelect(
|
|
78
|
+
( select ) => select( blockEditorStore ).getClientIdsWithDescendants(),
|
|
79
|
+
[]
|
|
80
|
+
);
|
|
81
|
+
|
|
70
82
|
// All DOM mutations and position computations live inside useEffect.
|
|
71
83
|
useEffect( () => {
|
|
72
84
|
if ( ! blockEditorDocument ) {
|
|
@@ -203,6 +215,7 @@ export function useBlockHighlighting(
|
|
|
203
215
|
overlayElement,
|
|
204
216
|
recomputeToken,
|
|
205
217
|
resolveSelection,
|
|
218
|
+
blockClientIds,
|
|
206
219
|
] );
|
|
207
220
|
|
|
208
221
|
return { highlights, rerenderHighlightsAfterDelay };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from '@wordpress/core-data';
|
|
1
|
+
// @ts-expect-error No exported types
|
|
2
|
+
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
3
|
+
import { privateApis as coreDataPrivateApis } from '@wordpress/core-data';
|
|
5
4
|
import type {
|
|
5
|
+
CoreDataPrivateApis,
|
|
6
6
|
ResolvedSelection,
|
|
7
7
|
PostEditorAwarenessState as ActiveCollaborator,
|
|
8
8
|
} from '@wordpress/core-data';
|
|
@@ -19,6 +19,10 @@ import type { SelectionRect } from './cursor-dom-utils';
|
|
|
19
19
|
|
|
20
20
|
const { useActiveCollaborators, useResolvedSelection } =
|
|
21
21
|
unlock( coreDataPrivateApis );
|
|
22
|
+
const { SelectionType } = unlock( coreDataPrivateApis ) as Pick<
|
|
23
|
+
CoreDataPrivateApis,
|
|
24
|
+
'SelectionType'
|
|
25
|
+
>;
|
|
22
26
|
|
|
23
27
|
export type { SelectionRect };
|
|
24
28
|
|
|
@@ -66,6 +70,11 @@ export function useRenderCursors(
|
|
|
66
70
|
[]
|
|
67
71
|
);
|
|
68
72
|
|
|
73
|
+
const blockClientIds = useSelect(
|
|
74
|
+
( select ) => select( blockEditorStore ).getClientIdsWithDescendants(),
|
|
75
|
+
[]
|
|
76
|
+
);
|
|
77
|
+
|
|
69
78
|
const [ cursorPositions, setCursorPositions ] = useState< CursorData[] >(
|
|
70
79
|
[]
|
|
71
80
|
);
|
|
@@ -106,6 +115,7 @@ export function useRenderCursors(
|
|
|
106
115
|
let start: ResolvedSelection = {
|
|
107
116
|
richTextOffset: null,
|
|
108
117
|
localClientId: null,
|
|
118
|
+
attributeKey: null,
|
|
109
119
|
};
|
|
110
120
|
let end: ResolvedSelection | undefined;
|
|
111
121
|
|
|
@@ -176,6 +186,7 @@ export function useRenderCursors(
|
|
|
176
186
|
sortedUsers,
|
|
177
187
|
showOwnCursor,
|
|
178
188
|
recomputeToken,
|
|
189
|
+
blockClientIds,
|
|
179
190
|
] );
|
|
180
191
|
|
|
181
192
|
return { cursors: cursorPositions, rerenderCursorsAfterDelay };
|