@wordpress/editor 14.41.0 → 14.41.1-next.v.202603102151.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/components/collab-sidebar/index.cjs +7 -4
- package/build/components/collab-sidebar/index.cjs.map +2 -2
- package/build/components/collab-sidebar/utils.cjs +13 -15
- package/build/components/collab-sidebar/utils.cjs.map +2 -2
- package/build/components/collaborators-overlay/avatar-iframe-styles.cjs +133 -0
- package/build/components/collaborators-overlay/avatar-iframe-styles.cjs.map +7 -0
- package/build/components/collaborators-overlay/collaborator-styles.cjs +38 -2
- package/build/components/collaborators-overlay/collaborator-styles.cjs.map +2 -2
- package/build/components/collaborators-overlay/overlay-iframe-styles.cjs +142 -0
- package/build/components/collaborators-overlay/overlay-iframe-styles.cjs.map +7 -0
- package/build/components/collaborators-overlay/overlay.cjs +59 -201
- package/build/components/collaborators-overlay/overlay.cjs.map +3 -3
- package/build/components/collaborators-overlay/use-block-highlighting.cjs +91 -42
- package/build/components/collaborators-overlay/use-block-highlighting.cjs.map +2 -2
- package/build/components/collaborators-overlay/use-debounced-recompute.cjs +49 -0
- package/build/components/collaborators-overlay/use-debounced-recompute.cjs.map +7 -0
- package/build/components/collaborators-overlay/use-render-cursors.cjs +49 -50
- package/build/components/collaborators-overlay/use-render-cursors.cjs.map +2 -2
- package/build/components/collaborators-presence/avatar/component.cjs +121 -0
- package/build/components/collaborators-presence/avatar/component.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar/index.cjs +37 -0
- package/build/components/collaborators-presence/avatar/index.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar/types.cjs +19 -0
- package/build/components/collaborators-presence/avatar/types.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar/use-image-loading-status.cjs +44 -0
- package/build/components/collaborators-presence/avatar/use-image-loading-status.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar-group/component.cjs +78 -0
- package/build/components/collaborators-presence/avatar-group/component.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar-group/index.cjs +37 -0
- package/build/components/collaborators-presence/avatar-group/index.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar-group/types.cjs +19 -0
- package/build/components/collaborators-presence/avatar-group/types.cjs.map +7 -0
- package/build/components/collaborators-presence/index.cjs +17 -6
- package/build/components/collaborators-presence/index.cjs.map +3 -3
- package/build/components/collaborators-presence/list.cjs +20 -17
- package/build/components/collaborators-presence/list.cjs.map +3 -3
- package/build/components/entities-saved-states/hooks/use-is-dirty.cjs +14 -5
- package/build/components/entities-saved-states/hooks/use-is-dirty.cjs.map +2 -2
- package/build/components/global-styles/index.cjs +15 -24
- package/build/components/global-styles/index.cjs.map +3 -3
- package/build/components/global-styles-sidebar/index.cjs +6 -3
- package/build/components/global-styles-sidebar/index.cjs.map +2 -2
- package/build/components/page-attributes/parent.cjs +1 -0
- package/build/components/page-attributes/parent.cjs.map +2 -2
- package/build/components/post-revisions-preview/revisions-canvas.cjs +17 -4
- package/build/components/post-revisions-preview/revisions-canvas.cjs.map +2 -2
- package/build/components/post-url/panel.cjs +1 -0
- package/build/components/post-url/panel.cjs.map +2 -2
- package/build/components/provider/use-block-editor-settings.cjs +4 -1
- package/build/components/provider/use-block-editor-settings.cjs.map +3 -3
- package/build/components/sidebar/dataform-post-summary.cjs +167 -0
- package/build/components/sidebar/dataform-post-summary.cjs.map +7 -0
- package/build/components/sidebar/post-summary.cjs +11 -0
- package/build/components/sidebar/post-summary.cjs.map +3 -3
- package/build/components/visual-editor/index.cjs +1 -1
- package/build/components/visual-editor/index.cjs.map +2 -2
- package/build/dataviews/store/private-actions.cjs +4 -0
- package/build/dataviews/store/private-actions.cjs.map +2 -2
- package/build/utils/media-upload/on-success.cjs +46 -0
- package/build/utils/media-upload/on-success.cjs.map +7 -0
- package/build-module/components/collab-sidebar/index.mjs +7 -4
- package/build-module/components/collab-sidebar/index.mjs.map +2 -2
- package/build-module/components/collab-sidebar/utils.mjs +13 -15
- package/build-module/components/collab-sidebar/utils.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/avatar-iframe-styles.mjs +120 -0
- package/build-module/components/collaborators-overlay/avatar-iframe-styles.mjs.map +7 -0
- package/build-module/components/collaborators-overlay/collaborator-styles.mjs +25 -1
- package/build-module/components/collaborators-overlay/collaborator-styles.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/overlay-iframe-styles.mjs +124 -0
- package/build-module/components/collaborators-overlay/overlay-iframe-styles.mjs.map +7 -0
- package/build-module/components/collaborators-overlay/overlay.mjs +49 -201
- package/build-module/components/collaborators-overlay/overlay.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/use-block-highlighting.mjs +92 -43
- package/build-module/components/collaborators-overlay/use-block-highlighting.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/use-debounced-recompute.mjs +24 -0
- package/build-module/components/collaborators-overlay/use-debounced-recompute.mjs.map +7 -0
- package/build-module/components/collaborators-overlay/use-render-cursors.mjs +50 -51
- package/build-module/components/collaborators-overlay/use-render-cursors.mjs.map +2 -2
- package/build-module/components/collaborators-presence/avatar/component.mjs +90 -0
- package/build-module/components/collaborators-presence/avatar/component.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar/index.mjs +6 -0
- package/build-module/components/collaborators-presence/avatar/index.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar/types.mjs +1 -0
- package/build-module/components/collaborators-presence/avatar/types.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar/use-image-loading-status.mjs +19 -0
- package/build-module/components/collaborators-presence/avatar/use-image-loading-status.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar-group/component.mjs +47 -0
- package/build-module/components/collaborators-presence/avatar-group/component.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar-group/index.mjs +6 -0
- package/build-module/components/collaborators-presence/avatar-group/index.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar-group/types.mjs +1 -0
- package/build-module/components/collaborators-presence/avatar-group/types.mjs.map +7 -0
- package/build-module/components/collaborators-presence/index.mjs +7 -9
- package/build-module/components/collaborators-presence/index.mjs.map +2 -2
- package/build-module/components/collaborators-presence/list.mjs +11 -22
- package/build-module/components/collaborators-presence/list.mjs.map +2 -2
- package/build-module/components/entities-saved-states/hooks/use-is-dirty.mjs +14 -5
- package/build-module/components/entities-saved-states/hooks/use-is-dirty.mjs.map +2 -2
- package/build-module/components/global-styles/index.mjs +15 -24
- package/build-module/components/global-styles/index.mjs.map +2 -2
- package/build-module/components/global-styles-sidebar/index.mjs +6 -3
- package/build-module/components/global-styles-sidebar/index.mjs.map +2 -2
- package/build-module/components/page-attributes/parent.mjs +1 -0
- package/build-module/components/page-attributes/parent.mjs.map +2 -2
- package/build-module/components/post-revisions-preview/revisions-canvas.mjs +17 -4
- package/build-module/components/post-revisions-preview/revisions-canvas.mjs.map +2 -2
- package/build-module/components/post-url/panel.mjs +1 -0
- package/build-module/components/post-url/panel.mjs.map +2 -2
- package/build-module/components/provider/use-block-editor-settings.mjs +4 -1
- package/build-module/components/provider/use-block-editor-settings.mjs.map +2 -2
- package/build-module/components/sidebar/dataform-post-summary.mjs +136 -0
- package/build-module/components/sidebar/dataform-post-summary.mjs.map +7 -0
- package/build-module/components/sidebar/post-summary.mjs +11 -0
- package/build-module/components/sidebar/post-summary.mjs.map +2 -2
- package/build-module/components/visual-editor/index.mjs +1 -1
- package/build-module/components/visual-editor/index.mjs.map +2 -2
- package/build-module/dataviews/store/private-actions.mjs +8 -1
- package/build-module/dataviews/store/private-actions.mjs.map +2 -2
- package/build-module/utils/media-upload/on-success.mjs +25 -0
- package/build-module/utils/media-upload/on-success.mjs.map +7 -0
- package/build-style/style-rtl.css +876 -137
- package/build-style/style.css +876 -137
- package/build-types/components/collab-sidebar/index.d.ts.map +1 -1
- package/build-types/components/collab-sidebar/utils.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/avatar-iframe-styles.d.ts +11 -0
- package/build-types/components/collaborators-overlay/avatar-iframe-styles.d.ts.map +1 -0
- package/build-types/components/collaborators-overlay/collaborator-styles.d.ts +17 -2
- package/build-types/components/collaborators-overlay/collaborator-styles.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/overlay-iframe-styles.d.ts +6 -0
- package/build-types/components/collaborators-overlay/overlay-iframe-styles.d.ts.map +1 -0
- package/build-types/components/collaborators-overlay/overlay.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts +21 -5
- package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/use-debounced-recompute.d.ts +10 -0
- package/build-types/components/collaborators-overlay/use-debounced-recompute.d.ts.map +1 -0
- package/build-types/components/collaborators-overlay/use-render-cursors.d.ts +2 -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 +7 -0
- package/build-types/components/collaborators-presence/avatar/component.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar/index.d.ts +3 -0
- package/build-types/components/collaborators-presence/avatar/index.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar/types.d.ts +66 -0
- package/build-types/components/collaborators-presence/avatar/types.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar/use-image-loading-status.d.ts +17 -0
- package/build-types/components/collaborators-presence/avatar/use-image-loading-status.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar-group/component.d.ts +7 -0
- package/build-types/components/collaborators-presence/avatar-group/component.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar-group/index.d.ts +3 -0
- package/build-types/components/collaborators-presence/avatar-group/index.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar-group/types.d.ts +14 -0
- package/build-types/components/collaborators-presence/avatar-group/types.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/index.d.ts.map +1 -1
- package/build-types/components/collaborators-presence/list.d.ts.map +1 -1
- package/build-types/components/entities-saved-states/hooks/use-is-dirty.d.ts.map +1 -1
- package/build-types/components/global-styles/index.d.ts +2 -1
- package/build-types/components/global-styles/index.d.ts.map +1 -1
- package/build-types/components/global-styles-sidebar/index.d.ts.map +1 -1
- package/build-types/components/page-attributes/parent.d.ts.map +1 -1
- package/build-types/components/post-author/hook.d.ts +1 -1
- package/build-types/components/post-revisions-preview/revisions-canvas.d.ts.map +1 -1
- package/build-types/components/provider/use-block-editor-settings.d.ts.map +1 -1
- package/build-types/components/sidebar/dataform-post-summary.d.ts +4 -0
- package/build-types/components/sidebar/dataform-post-summary.d.ts.map +1 -0
- package/build-types/components/sidebar/post-summary.d.ts.map +1 -1
- package/build-types/dataviews/store/private-actions.d.ts.map +1 -1
- package/build-types/utils/media-upload/on-success.d.ts +9 -0
- package/build-types/utils/media-upload/on-success.d.ts.map +1 -0
- package/package.json +45 -44
- package/src/components/collab-sidebar/index.js +7 -4
- package/src/components/collab-sidebar/utils.js +9 -10
- package/src/components/collaborators-overlay/avatar-iframe-styles.ts +126 -0
- package/src/components/collaborators-overlay/collaborator-styles.ts +43 -2
- package/src/components/collaborators-overlay/overlay-iframe-styles.ts +125 -0
- package/src/components/collaborators-overlay/overlay.tsx +54 -207
- package/src/components/collaborators-overlay/use-block-highlighting.ts +147 -64
- package/src/components/collaborators-overlay/use-debounced-recompute.ts +32 -0
- package/src/components/collaborators-overlay/use-render-cursors.ts +72 -66
- package/src/components/collaborators-presence/avatar/component.tsx +123 -0
- package/src/components/collaborators-presence/avatar/index.ts +2 -0
- package/src/components/collaborators-presence/avatar/styles.scss +168 -0
- package/src/components/collaborators-presence/avatar/test/index.tsx +389 -0
- package/src/components/collaborators-presence/avatar/types.ts +66 -0
- package/src/components/collaborators-presence/avatar/use-image-loading-status.ts +36 -0
- package/src/components/collaborators-presence/avatar-group/component.tsx +55 -0
- package/src/components/collaborators-presence/avatar-group/index.ts +2 -0
- package/src/components/collaborators-presence/avatar-group/styles.scss +33 -0
- package/src/components/collaborators-presence/avatar-group/test/index.tsx +139 -0
- package/src/components/collaborators-presence/avatar-group/types.ts +13 -0
- package/src/components/collaborators-presence/index.tsx +4 -6
- package/src/components/collaborators-presence/list.tsx +7 -17
- package/src/components/collaborators-presence/styles/collaborators-list.scss +26 -19
- package/src/components/collaborators-presence/styles/collaborators-presence.scss +6 -2
- package/src/components/entities-saved-states/hooks/use-is-dirty.js +14 -5
- package/src/components/global-styles/index.js +20 -27
- package/src/components/global-styles-sidebar/index.js +3 -0
- package/src/components/page-attributes/parent.js +1 -0
- package/src/components/post-publish-panel/test/__snapshots__/index.js.snap +2 -2
- package/src/components/post-revisions-preview/revisions-canvas.js +15 -6
- package/src/components/post-url/panel.js +1 -0
- package/src/components/post-url/style.scss +5 -0
- package/src/components/provider/use-block-editor-settings.js +5 -0
- package/src/components/sidebar/dataform-post-summary.js +149 -0
- package/src/components/sidebar/post-summary.js +15 -0
- package/src/components/visual-editor/index.js +1 -1
- package/src/dataviews/store/private-actions.ts +14 -0
- package/src/style.scss +3 -0
- package/src/utils/media-upload/on-success.js +34 -0
|
@@ -4,31 +4,53 @@
|
|
|
4
4
|
import {
|
|
5
5
|
privateApis as coreDataPrivateApis,
|
|
6
6
|
SelectionType,
|
|
7
|
+
type PostEditorAwarenessState,
|
|
7
8
|
} from '@wordpress/core-data';
|
|
8
|
-
import { useEffect, useRef } from '@wordpress/element';
|
|
9
|
+
import { useEffect, useRef, useState } from '@wordpress/element';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Internal dependencies
|
|
12
13
|
*/
|
|
13
14
|
import { unlock } from '../../lock-unlock';
|
|
14
15
|
import { getAvatarBorderColor } from '../collab-sidebar/utils';
|
|
16
|
+
import { getAvatarUrl } from './get-avatar-url';
|
|
17
|
+
import { useDebouncedRecompute } from './use-debounced-recompute';
|
|
15
18
|
|
|
16
19
|
const { useActiveCollaborators, useResolvedSelection } =
|
|
17
20
|
unlock( coreDataPrivateApis );
|
|
18
21
|
|
|
22
|
+
export interface BlockHighlightData {
|
|
23
|
+
blockId: string;
|
|
24
|
+
userName: string;
|
|
25
|
+
avatarUrl?: string;
|
|
26
|
+
color: string;
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
/**
|
|
20
|
-
* Custom hook for highlighting selected blocks in the editor
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @param
|
|
32
|
+
* Custom hook for highlighting selected blocks in the editor and computing
|
|
33
|
+
* their positions for rendering avatar labels in the overlay.
|
|
34
|
+
*
|
|
35
|
+
* @param overlayElement - The overlay element used as position reference.
|
|
36
|
+
* @param blockEditorDocument - Ref to the block editor document.
|
|
37
|
+
* @param postId - The ID of the post.
|
|
38
|
+
* @param postType - The type of the post.
|
|
39
|
+
* @param delayMs - Milliseconds to wait before recomputing highlight positions.
|
|
40
|
+
* @return Highlight data for rendering and a delayed recompute function.
|
|
24
41
|
*/
|
|
25
42
|
export function useBlockHighlighting(
|
|
43
|
+
overlayElement: HTMLElement | null,
|
|
26
44
|
blockEditorDocument: Document | null,
|
|
27
45
|
postId: number | null,
|
|
28
|
-
postType: string | null
|
|
29
|
-
|
|
46
|
+
postType: string | null,
|
|
47
|
+
delayMs: number
|
|
48
|
+
): {
|
|
49
|
+
highlights: BlockHighlightData[];
|
|
50
|
+
rerenderHighlightsAfterDelay: () => () => void;
|
|
51
|
+
} {
|
|
30
52
|
const highlightedBlockIds = useRef< Set< string > >( new Set() );
|
|
31
|
-
const userStates = useActiveCollaborators(
|
|
53
|
+
const userStates: PostEditorAwarenessState[] = useActiveCollaborators(
|
|
32
54
|
postId ?? null,
|
|
33
55
|
postType ?? null
|
|
34
56
|
);
|
|
@@ -37,15 +59,78 @@ export function useBlockHighlighting(
|
|
|
37
59
|
postType ?? null
|
|
38
60
|
);
|
|
39
61
|
|
|
40
|
-
|
|
62
|
+
const [ highlights, setHighlights ] = useState< BlockHighlightData[] >(
|
|
63
|
+
[]
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Bump this counter to force the effect to re-run (e.g. after a layout shift).
|
|
67
|
+
const [ recomputeToken, rerenderHighlightsAfterDelay ] =
|
|
68
|
+
useDebouncedRecompute( delayMs );
|
|
69
|
+
|
|
70
|
+
// All DOM mutations and position computations live inside useEffect.
|
|
41
71
|
useEffect( () => {
|
|
42
|
-
|
|
43
|
-
|
|
72
|
+
if ( ! blockEditorDocument ) {
|
|
73
|
+
setHighlights( [] );
|
|
44
74
|
return;
|
|
45
75
|
}
|
|
46
76
|
|
|
47
|
-
|
|
48
|
-
|
|
77
|
+
// Capture the ref value so the cleanup closure sees the same Set
|
|
78
|
+
// even if a later render replaces it.
|
|
79
|
+
const currentHighlightedIds = highlightedBlockIds.current;
|
|
80
|
+
|
|
81
|
+
// Deduplicate by blockId — when multiple collaborators select the
|
|
82
|
+
// same block, only the first one gets the highlight and avatar label.
|
|
83
|
+
const seen = new Set< string >();
|
|
84
|
+
const blocksToHighlight = userStates
|
|
85
|
+
.filter(
|
|
86
|
+
( userState ) =>
|
|
87
|
+
! userState.isMe &&
|
|
88
|
+
userState.editorState?.selection?.type ===
|
|
89
|
+
SelectionType.WholeBlock
|
|
90
|
+
)
|
|
91
|
+
.map( ( userState ) => {
|
|
92
|
+
let localClientId;
|
|
93
|
+
try {
|
|
94
|
+
( { localClientId } = resolveSelection(
|
|
95
|
+
userState.editorState?.selection
|
|
96
|
+
) );
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if ( ! localClientId ) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
blockId: localClientId,
|
|
107
|
+
color: getAvatarBorderColor(
|
|
108
|
+
userState.collaboratorInfo.id
|
|
109
|
+
),
|
|
110
|
+
userName: userState.collaboratorInfo.name,
|
|
111
|
+
avatarUrl: getAvatarUrl(
|
|
112
|
+
userState.collaboratorInfo.avatar_urls
|
|
113
|
+
),
|
|
114
|
+
};
|
|
115
|
+
} )
|
|
116
|
+
.filter( ( block ): block is NonNullable< typeof block > => {
|
|
117
|
+
if ( ! block ) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if ( seen.has( block.blockId ) ) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
seen.add( block.blockId );
|
|
124
|
+
return true;
|
|
125
|
+
} );
|
|
126
|
+
|
|
127
|
+
// Unhighlight blocks that are no longer selected.
|
|
128
|
+
const selectedBlockIds = new Set(
|
|
129
|
+
blocksToHighlight.map( ( block ) => block.blockId )
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
for ( const blockId of currentHighlightedIds ) {
|
|
133
|
+
if ( ! selectedBlockIds.has( blockId ) ) {
|
|
49
134
|
const blockElement = getBlockElementById(
|
|
50
135
|
blockEditorDocument,
|
|
51
136
|
blockId
|
|
@@ -58,51 +143,16 @@ export function useBlockHighlighting(
|
|
|
58
143
|
);
|
|
59
144
|
}
|
|
60
145
|
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const blocksToHighlight = userStates
|
|
66
|
-
.map( ( userState: any ) => {
|
|
67
|
-
const isWholeBlockSelected =
|
|
68
|
-
userState.editorState?.selection?.type ===
|
|
69
|
-
SelectionType.WholeBlock;
|
|
70
|
-
const shouldDrawUser = ! userState.isMe;
|
|
71
|
-
|
|
72
|
-
if ( isWholeBlockSelected && shouldDrawUser ) {
|
|
73
|
-
const { localClientId } = resolveSelection(
|
|
74
|
-
userState.editorState?.selection
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
if ( ! localClientId ) {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
blockId: localClientId,
|
|
83
|
-
color: getAvatarBorderColor(
|
|
84
|
-
userState.collaboratorInfo.id
|
|
85
|
-
),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return null;
|
|
90
|
-
} )
|
|
91
|
-
.filter( ( block: any ) => block !== null );
|
|
92
|
-
|
|
93
|
-
// Unhighlight blocks that are no longer highlighted.
|
|
94
|
-
const selectedBlockIds = blocksToHighlight.map(
|
|
95
|
-
( block: any ) => block.blockId
|
|
96
|
-
);
|
|
97
|
-
const blocksIdsToUnhighlight = Array.from(
|
|
98
|
-
highlightedBlockIds.current
|
|
99
|
-
).filter( ( blockId ) => ! selectedBlockIds.includes( blockId ) );
|
|
146
|
+
currentHighlightedIds.delete( blockId );
|
|
147
|
+
}
|
|
148
|
+
}
|
|
100
149
|
|
|
101
|
-
|
|
150
|
+
// Highlight blocks and compute positions for avatar labels.
|
|
151
|
+
const results: BlockHighlightData[] = [];
|
|
152
|
+
const overlayRect = overlayElement?.getBoundingClientRect() ?? null;
|
|
102
153
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const { color, blockId } = blockColorPair;
|
|
154
|
+
blocksToHighlight.forEach( ( block ) => {
|
|
155
|
+
const { color, blockId, userName, avatarUrl } = block;
|
|
106
156
|
const blockElement = getBlockElementById(
|
|
107
157
|
blockEditorDocument,
|
|
108
158
|
blockId
|
|
@@ -112,16 +162,49 @@ export function useBlockHighlighting(
|
|
|
112
162
|
return;
|
|
113
163
|
}
|
|
114
164
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
165
|
+
blockElement.classList.add( 'is-collaborator-selected' );
|
|
166
|
+
blockElement.style.setProperty(
|
|
167
|
+
'--collaborator-outline-color',
|
|
168
|
+
color
|
|
169
|
+
);
|
|
170
|
+
currentHighlightedIds.add( blockId );
|
|
171
|
+
|
|
172
|
+
if ( overlayRect ) {
|
|
173
|
+
const blockRect = blockElement.getBoundingClientRect();
|
|
174
|
+
|
|
175
|
+
results.push( {
|
|
176
|
+
blockId,
|
|
177
|
+
userName,
|
|
178
|
+
avatarUrl,
|
|
179
|
+
color,
|
|
180
|
+
x: blockRect.left - overlayRect.left,
|
|
181
|
+
y: blockRect.top - overlayRect.top,
|
|
182
|
+
} );
|
|
122
183
|
}
|
|
123
184
|
} );
|
|
124
|
-
|
|
185
|
+
|
|
186
|
+
setHighlights( results );
|
|
187
|
+
|
|
188
|
+
// Clean up all highlights on unmount.
|
|
189
|
+
return () => {
|
|
190
|
+
for ( const blockId of currentHighlightedIds ) {
|
|
191
|
+
const el = getBlockElementById( blockEditorDocument, blockId );
|
|
192
|
+
if ( el ) {
|
|
193
|
+
el.classList.remove( 'is-collaborator-selected' );
|
|
194
|
+
el.style.removeProperty( '--collaborator-outline-color' );
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
currentHighlightedIds.clear();
|
|
198
|
+
};
|
|
199
|
+
}, [
|
|
200
|
+
userStates,
|
|
201
|
+
blockEditorDocument,
|
|
202
|
+
overlayElement,
|
|
203
|
+
recomputeToken,
|
|
204
|
+
resolveSelection,
|
|
205
|
+
] );
|
|
206
|
+
|
|
207
|
+
return { highlights, rerenderHighlightsAfterDelay };
|
|
125
208
|
}
|
|
126
209
|
|
|
127
210
|
const getBlockElementById = (
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from '@wordpress/element';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a recompute token and a debounced callback that bumps it.
|
|
5
|
+
* Rapid successive calls cancel the previous pending timeout so only one
|
|
6
|
+
* recompute fires, `delayMs` after the last call.
|
|
7
|
+
*
|
|
8
|
+
* @param delayMs - Milliseconds to wait before bumping the recompute token.
|
|
9
|
+
* @return A tuple of [recomputeToken, rerenderAfterDelay].
|
|
10
|
+
*/
|
|
11
|
+
export function useDebouncedRecompute(
|
|
12
|
+
delayMs: number
|
|
13
|
+
): [ number, () => () => void ] {
|
|
14
|
+
const [ recomputeToken, setRecomputeToken ] = useState( 0 );
|
|
15
|
+
const timeoutRef = useRef< ReturnType< typeof setTimeout > | null >( null );
|
|
16
|
+
|
|
17
|
+
const rerenderAfterDelay = useCallback( () => {
|
|
18
|
+
if ( timeoutRef.current ) {
|
|
19
|
+
clearTimeout( timeoutRef.current );
|
|
20
|
+
}
|
|
21
|
+
timeoutRef.current = setTimeout( () => {
|
|
22
|
+
setRecomputeToken( ( t ) => t + 1 );
|
|
23
|
+
}, delayMs );
|
|
24
|
+
return () => {
|
|
25
|
+
if ( timeoutRef.current ) {
|
|
26
|
+
clearTimeout( timeoutRef.current );
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}, [ delayMs ] );
|
|
30
|
+
|
|
31
|
+
return [ recomputeToken, rerenderAfterDelay ];
|
|
32
|
+
}
|
|
@@ -2,11 +2,12 @@ import {
|
|
|
2
2
|
privateApis as coreDataPrivateApis,
|
|
3
3
|
SelectionType,
|
|
4
4
|
} from '@wordpress/core-data';
|
|
5
|
-
import { useEffect,
|
|
5
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
6
6
|
|
|
7
7
|
import { unlock } from '../../lock-unlock';
|
|
8
8
|
import { getAvatarUrl } from './get-avatar-url';
|
|
9
9
|
import { getAvatarBorderColor } from '../collab-sidebar/utils';
|
|
10
|
+
import { useDebouncedRecompute } from './use-debounced-recompute';
|
|
10
11
|
|
|
11
12
|
const { useActiveCollaborators, useResolvedSelection } =
|
|
12
13
|
unlock( coreDataPrivateApis );
|
|
@@ -28,13 +29,15 @@ export interface CursorData {
|
|
|
28
29
|
* @param blockEditorDocument - The block editor document
|
|
29
30
|
* @param postId - The ID of the post
|
|
30
31
|
* @param postType - The type of the post
|
|
32
|
+
* @param delayMs - Milliseconds to wait before recomputing cursor positions.
|
|
31
33
|
* @return An array of cursor data for rendering, and a function to trigger a delayed recompute.
|
|
32
34
|
*/
|
|
33
35
|
export function useRenderCursors(
|
|
34
36
|
overlayElement: HTMLElement | null,
|
|
35
37
|
blockEditorDocument: Document | null,
|
|
36
38
|
postId: number | null,
|
|
37
|
-
postType: string | null
|
|
39
|
+
postType: string | null,
|
|
40
|
+
delayMs: number
|
|
38
41
|
): { cursors: CursorData[]; rerenderCursorsAfterDelay: () => () => void } {
|
|
39
42
|
const sortedUsers = useActiveCollaborators(
|
|
40
43
|
postId ?? null,
|
|
@@ -49,41 +52,44 @@ export function useRenderCursors(
|
|
|
49
52
|
[]
|
|
50
53
|
);
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
setCursorPositions( [] );
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
55
|
+
// Bump this counter to force the effect to re-run (e.g. after a layout shift).
|
|
56
|
+
const [ recomputeToken, rerenderCursorsAfterDelay ] =
|
|
57
|
+
useDebouncedRecompute( delayMs );
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
// All DOM position computations live inside useEffect.
|
|
60
|
+
useEffect( () => {
|
|
61
|
+
if ( ! overlayElement || ! blockEditorDocument ) {
|
|
62
|
+
setCursorPositions( [] );
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
if ( user.isMe ) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
66
|
+
const results: CursorData[] = [];
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
68
|
+
sortedUsers.forEach( ( user: any ) => {
|
|
69
|
+
if ( user.isMe ) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const selection = user.editorState?.selection ?? {
|
|
74
|
+
type: SelectionType.None,
|
|
75
|
+
};
|
|
76
|
+
const userName = user.collaboratorInfo.name;
|
|
77
|
+
const clientId = user.clientId;
|
|
78
|
+
const color = getAvatarBorderColor( user.collaboratorInfo.id );
|
|
79
|
+
const avatarUrl = getAvatarUrl( user.collaboratorInfo.avatar_urls );
|
|
80
|
+
|
|
81
|
+
let coords: {
|
|
82
|
+
x: number;
|
|
83
|
+
y: number;
|
|
84
|
+
height: number;
|
|
85
|
+
} | null = null;
|
|
86
|
+
|
|
87
|
+
if ( selection.type === SelectionType.None ) {
|
|
88
|
+
// Nothing selected.
|
|
89
|
+
} else if ( selection.type === SelectionType.WholeBlock ) {
|
|
90
|
+
// Don't draw a cursor for a whole block selection.
|
|
91
|
+
} else if ( selection.type === SelectionType.Cursor ) {
|
|
92
|
+
try {
|
|
87
93
|
const { textIndex, localClientId } =
|
|
88
94
|
resolveSelection( selection );
|
|
89
95
|
if ( localClientId ) {
|
|
@@ -94,10 +100,14 @@ export function useRenderCursors(
|
|
|
94
100
|
overlayElement
|
|
95
101
|
);
|
|
96
102
|
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
} catch {
|
|
104
|
+
// Selection may reference a stale Yjs position.
|
|
105
|
+
}
|
|
106
|
+
} else if (
|
|
107
|
+
selection.type === SelectionType.SelectionInOneBlock ||
|
|
108
|
+
selection.type === SelectionType.SelectionInMultipleBlocks
|
|
109
|
+
) {
|
|
110
|
+
try {
|
|
101
111
|
const { textIndex, localClientId } = resolveSelection( {
|
|
102
112
|
type: SelectionType.Cursor,
|
|
103
113
|
cursorPosition: selection.cursorStartPosition,
|
|
@@ -110,33 +120,30 @@ export function useRenderCursors(
|
|
|
110
120
|
overlayElement
|
|
111
121
|
);
|
|
112
122
|
}
|
|
123
|
+
} catch {
|
|
124
|
+
// Selection may reference a stale Yjs position.
|
|
113
125
|
}
|
|
126
|
+
}
|
|
114
127
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
setCursorPositions( results );
|
|
127
|
-
},
|
|
128
|
-
[ blockEditorDocument, resolveSelection, overlayElement, sortedUsers ]
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
useEffect( computeCursors, [ computeCursors ] );
|
|
128
|
+
if ( coords ) {
|
|
129
|
+
results.push( {
|
|
130
|
+
userName,
|
|
131
|
+
clientId,
|
|
132
|
+
color,
|
|
133
|
+
avatarUrl,
|
|
134
|
+
...coords,
|
|
135
|
+
} );
|
|
136
|
+
}
|
|
137
|
+
} );
|
|
132
138
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
setCursorPositions( results );
|
|
140
|
+
}, [
|
|
141
|
+
blockEditorDocument,
|
|
142
|
+
resolveSelection,
|
|
143
|
+
overlayElement,
|
|
144
|
+
sortedUsers,
|
|
145
|
+
recomputeToken,
|
|
146
|
+
] );
|
|
140
147
|
|
|
141
148
|
return { cursors: cursorPositions, rerenderCursorsAfterDelay };
|
|
142
149
|
}
|
|
@@ -236,11 +243,10 @@ const getOffsetPositionInBlock = (
|
|
|
236
243
|
|
|
237
244
|
let cursorHeight = cursorRect.height;
|
|
238
245
|
if ( cursorHeight === 0 ) {
|
|
246
|
+
const view = editorDocument.defaultView ?? window;
|
|
239
247
|
cursorHeight =
|
|
240
|
-
parseInt(
|
|
241
|
-
|
|
242
|
-
10
|
|
243
|
-
) || blockRect.height;
|
|
248
|
+
parseInt( view.getComputedStyle( blockElement ).lineHeight, 10 ) ||
|
|
249
|
+
blockRect.height;
|
|
244
250
|
}
|
|
245
251
|
|
|
246
252
|
return {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { colord, extend } from 'colord';
|
|
6
|
+
import a11yPlugin from 'colord/plugins/a11y';
|
|
7
|
+
|
|
8
|
+
extend( [ a11yPlugin ] );
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* WordPress dependencies
|
|
12
|
+
*/
|
|
13
|
+
import { Icon, Tooltip } from '@wordpress/components';
|
|
14
|
+
import { useMemo } from '@wordpress/element';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Internal dependencies
|
|
18
|
+
*/
|
|
19
|
+
import type { AvatarProps } from './types';
|
|
20
|
+
import { useImageLoadingStatus } from './use-image-loading-status';
|
|
21
|
+
|
|
22
|
+
// Runtime equivalents of @wordpress/base-styles tokens ($gray-900, $white).
|
|
23
|
+
const GRAY_900 = '#1e1e1e';
|
|
24
|
+
const WHITE = '#fff';
|
|
25
|
+
|
|
26
|
+
function Avatar( {
|
|
27
|
+
className,
|
|
28
|
+
src,
|
|
29
|
+
name,
|
|
30
|
+
label,
|
|
31
|
+
variant,
|
|
32
|
+
size = 'default',
|
|
33
|
+
borderColor,
|
|
34
|
+
dimmed = false,
|
|
35
|
+
statusIndicator,
|
|
36
|
+
style,
|
|
37
|
+
...props
|
|
38
|
+
}: AvatarProps &
|
|
39
|
+
Omit< React.HTMLAttributes< HTMLDivElement >, keyof AvatarProps > ) {
|
|
40
|
+
const {
|
|
41
|
+
status: imageStatus,
|
|
42
|
+
handleLoad,
|
|
43
|
+
handleError,
|
|
44
|
+
} = useImageLoadingStatus( src );
|
|
45
|
+
const imageLoaded = imageStatus === 'loaded';
|
|
46
|
+
|
|
47
|
+
const showBadge = variant === 'badge' && !! name;
|
|
48
|
+
const initials = name
|
|
49
|
+
? name
|
|
50
|
+
.split( /\s+/ )
|
|
51
|
+
.slice( 0, 2 )
|
|
52
|
+
.map( ( word ) => word[ 0 ] )
|
|
53
|
+
.join( '' )
|
|
54
|
+
.toUpperCase()
|
|
55
|
+
: undefined;
|
|
56
|
+
const nameColor = useMemo(
|
|
57
|
+
() =>
|
|
58
|
+
borderColor &&
|
|
59
|
+
colord( borderColor ).isReadable( GRAY_900, {
|
|
60
|
+
level: 'AA',
|
|
61
|
+
size: 'normal',
|
|
62
|
+
} )
|
|
63
|
+
? GRAY_900
|
|
64
|
+
: WHITE,
|
|
65
|
+
[ borderColor ]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const customProperties = {
|
|
69
|
+
...style,
|
|
70
|
+
...( borderColor
|
|
71
|
+
? {
|
|
72
|
+
'--editor-avatar-outline-color': borderColor,
|
|
73
|
+
'--editor-avatar-name-color': nameColor,
|
|
74
|
+
}
|
|
75
|
+
: {} ),
|
|
76
|
+
} as React.CSSProperties;
|
|
77
|
+
|
|
78
|
+
const avatar = (
|
|
79
|
+
<div
|
|
80
|
+
className={ clsx( 'editor-avatar', className, {
|
|
81
|
+
'has-avatar-border-color': !! borderColor,
|
|
82
|
+
'has-src': imageLoaded,
|
|
83
|
+
'is-badge': showBadge,
|
|
84
|
+
'is-small': size === 'small',
|
|
85
|
+
'is-dimmed': dimmed,
|
|
86
|
+
} ) }
|
|
87
|
+
style={ customProperties }
|
|
88
|
+
role={ name ? 'img' : undefined }
|
|
89
|
+
aria-label={ name || undefined }
|
|
90
|
+
{ ...props }
|
|
91
|
+
>
|
|
92
|
+
<span className="editor-avatar__image">
|
|
93
|
+
{ src && (
|
|
94
|
+
<img
|
|
95
|
+
src={ src }
|
|
96
|
+
alt=""
|
|
97
|
+
crossOrigin="anonymous"
|
|
98
|
+
className="editor-avatar__img"
|
|
99
|
+
onLoad={ handleLoad }
|
|
100
|
+
onError={ handleError }
|
|
101
|
+
/>
|
|
102
|
+
) }
|
|
103
|
+
{ ! imageLoaded && initials }
|
|
104
|
+
</span>
|
|
105
|
+
{ dimmed && !! statusIndicator && (
|
|
106
|
+
<span className="editor-avatar__status-indicator">
|
|
107
|
+
<Icon icon={ statusIndicator } />
|
|
108
|
+
</span>
|
|
109
|
+
) }
|
|
110
|
+
{ showBadge && (
|
|
111
|
+
<span className="editor-avatar__name">{ label || name }</span>
|
|
112
|
+
) }
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if ( name && ( ! showBadge || label ) ) {
|
|
117
|
+
return <Tooltip text={ name }>{ avatar }</Tooltip>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return avatar;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default Avatar;
|