@wordpress/editor 14.33.3 → 14.33.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/build/bindings/post-data.js +47 -63
  2. package/build/bindings/post-data.js.map +3 -3
  3. package/build/bindings/post-meta.js +45 -39
  4. package/build/bindings/post-meta.js.map +2 -2
  5. package/build/components/collab-sidebar/add-comment.js +8 -4
  6. package/build/components/collab-sidebar/add-comment.js.map +2 -2
  7. package/build/components/collab-sidebar/comment-author-info.js +27 -15
  8. package/build/components/collab-sidebar/comment-author-info.js.map +2 -2
  9. package/build/components/collab-sidebar/comment-indicator-toolbar.js +9 -23
  10. package/build/components/collab-sidebar/comment-indicator-toolbar.js.map +3 -3
  11. package/build/components/collab-sidebar/comments.js +32 -7
  12. package/build/components/collab-sidebar/comments.js.map +3 -3
  13. package/build/components/collab-sidebar/hooks.js +5 -3
  14. package/build/components/collab-sidebar/hooks.js.map +2 -2
  15. package/build/components/collab-sidebar/index.js +18 -7
  16. package/build/components/collab-sidebar/index.js.map +2 -2
  17. package/build/components/more-menu/index.js +1 -1
  18. package/build/components/more-menu/index.js.map +2 -2
  19. package/build/components/visual-editor/index.js +20 -9
  20. package/build/components/visual-editor/index.js.map +2 -2
  21. package/build/store/private-actions.js +8 -0
  22. package/build/store/private-actions.js.map +2 -2
  23. package/build/store/private-selectors.js +5 -0
  24. package/build/store/private-selectors.js.map +2 -2
  25. package/build/store/reducer.js +10 -0
  26. package/build/store/reducer.js.map +2 -2
  27. package/build-module/bindings/post-data.js +47 -63
  28. package/build-module/bindings/post-data.js.map +2 -2
  29. package/build-module/bindings/post-meta.js +45 -39
  30. package/build-module/bindings/post-meta.js.map +2 -2
  31. package/build-module/components/collab-sidebar/add-comment.js +9 -5
  32. package/build-module/components/collab-sidebar/add-comment.js.map +2 -2
  33. package/build-module/components/collab-sidebar/comment-author-info.js +27 -15
  34. package/build-module/components/collab-sidebar/comment-author-info.js.map +2 -2
  35. package/build-module/components/collab-sidebar/comment-indicator-toolbar.js +15 -25
  36. package/build-module/components/collab-sidebar/comment-indicator-toolbar.js.map +2 -2
  37. package/build-module/components/collab-sidebar/comments.js +34 -8
  38. package/build-module/components/collab-sidebar/comments.js.map +2 -2
  39. package/build-module/components/collab-sidebar/hooks.js +5 -3
  40. package/build-module/components/collab-sidebar/hooks.js.map +2 -2
  41. package/build-module/components/collab-sidebar/index.js +18 -7
  42. package/build-module/components/collab-sidebar/index.js.map +2 -2
  43. package/build-module/components/more-menu/index.js +1 -1
  44. package/build-module/components/more-menu/index.js.map +2 -2
  45. package/build-module/components/visual-editor/index.js +20 -9
  46. package/build-module/components/visual-editor/index.js.map +2 -2
  47. package/build-module/store/private-actions.js +7 -0
  48. package/build-module/store/private-actions.js.map +2 -2
  49. package/build-module/store/private-selectors.js +4 -0
  50. package/build-module/store/private-selectors.js.map +2 -2
  51. package/build-module/store/reducer.js +9 -0
  52. package/build-module/store/reducer.js.map +2 -2
  53. package/build-style/style-rtl.css +6 -43
  54. package/build-style/style.css +6 -43
  55. package/build-types/bindings/post-data.d.ts +18 -8
  56. package/build-types/bindings/post-meta.d.ts +1 -7
  57. package/build-types/components/collab-sidebar/add-comment.d.ts.map +1 -1
  58. package/build-types/components/collab-sidebar/comment-author-info.d.ts +5 -16
  59. package/build-types/components/collab-sidebar/comment-author-info.d.ts.map +1 -1
  60. package/build-types/components/collab-sidebar/comment-indicator-toolbar.d.ts.map +1 -1
  61. package/build-types/components/collab-sidebar/comments.d.ts.map +1 -1
  62. package/build-types/components/collab-sidebar/hooks.d.ts.map +1 -1
  63. package/build-types/components/collab-sidebar/index.d.ts.map +1 -1
  64. package/build-types/components/visual-editor/index.d.ts.map +1 -1
  65. package/build-types/store/private-actions.d.ts +7 -0
  66. package/build-types/store/private-actions.d.ts.map +1 -1
  67. package/build-types/store/private-selectors.d.ts +7 -0
  68. package/build-types/store/private-selectors.d.ts.map +1 -1
  69. package/build-types/store/reducer.d.ts +10 -0
  70. package/build-types/store/reducer.d.ts.map +1 -1
  71. package/package.json +8 -8
  72. package/src/bindings/post-data.js +63 -111
  73. package/src/bindings/post-meta.js +55 -46
  74. package/src/bindings/test/post-meta.js +211 -0
  75. package/src/components/collab-sidebar/add-comment.js +10 -5
  76. package/src/components/collab-sidebar/comment-author-info.js +33 -26
  77. package/src/components/collab-sidebar/comment-indicator-toolbar.js +19 -29
  78. package/src/components/collab-sidebar/comments.js +39 -8
  79. package/src/components/collab-sidebar/hooks.js +6 -4
  80. package/src/components/collab-sidebar/index.js +26 -8
  81. package/src/components/collab-sidebar/style.scss +6 -46
  82. package/src/components/more-menu/index.js +1 -1
  83. package/src/components/visual-editor/index.js +27 -6
  84. package/src/store/private-actions.js +13 -0
  85. package/src/store/private-selectors.js +10 -0
  86. package/src/store/reducer.js +16 -0
  87. package/tsconfig.tsbuildinfo +1 -1
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { ToolbarButton } from '@wordpress/components';
5
- import { __, _n, sprintf } from '@wordpress/i18n';
4
+ import {
5
+ ToolbarButton,
6
+ __experimentalText as Text,
7
+ __experimentalHStack as HStack,
8
+ } from '@wordpress/components';
9
+ import { __, sprintf } from '@wordpress/i18n';
6
10
  import { useMemo } from '@wordpress/element';
7
11
  import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
8
12
 
@@ -49,10 +53,16 @@ const CommentAvatarIndicator = ( { onClick, thread } ) => {
49
53
  return null;
50
54
  }
51
55
 
52
- // Show up to 3 avatars, with overflow indicator.
56
+ // If there are more than 3 participants, show 2 avatars and a "+n" number.
53
57
  const maxAvatars = 3;
54
- const visibleParticipants = threadParticipants.slice( 0, maxAvatars );
55
- const overflowCount = Math.max( 0, threadParticipants.length - maxAvatars );
58
+ const isOverflow = threadParticipants.length > maxAvatars;
59
+ const visibleParticipants = isOverflow
60
+ ? threadParticipants.slice( 0, maxAvatars - 1 )
61
+ : threadParticipants;
62
+ const overflowCount = Math.max(
63
+ 0,
64
+ threadParticipants.length - visibleParticipants.length
65
+ );
56
66
  const threadHasMoreParticipants = threadParticipants.length > 100;
57
67
 
58
68
  // If we hit the comment limit, show "100+" instead of exact overflow count.
@@ -65,19 +75,6 @@ const CommentAvatarIndicator = ( { onClick, thread } ) => {
65
75
  overflowCount
66
76
  );
67
77
 
68
- const overflowTitle =
69
- threadHasMoreParticipants && overflowCount > 0
70
- ? __( '100+ participants' )
71
- : sprintf(
72
- // translators: %s: Number of participants.
73
- _n(
74
- '+%s more participant',
75
- '+%s more participants',
76
- overflowCount
77
- ),
78
- overflowCount
79
- );
80
-
81
78
  return (
82
79
  <CommentIconToolbarSlotFill.Fill>
83
80
  <ToolbarButton
@@ -86,15 +83,14 @@ const CommentAvatarIndicator = ( { onClick, thread } ) => {
86
83
  onClick={ onClick }
87
84
  showTooltip
88
85
  >
89
- <div className="comment-avatar-stack">
90
- { visibleParticipants.map( ( participant, index ) => (
86
+ <HStack spacing="1">
87
+ { visibleParticipants.map( ( participant ) => (
91
88
  <img
92
89
  key={ participant.id }
93
90
  src={ participant.avatar }
94
91
  alt={ participant.name }
95
92
  className="comment-avatar"
96
93
  style={ {
97
- zIndex: maxAvatars - index,
98
94
  borderColor: getAvatarBorderColor(
99
95
  participant.id
100
96
  ),
@@ -102,15 +98,9 @@ const CommentAvatarIndicator = ( { onClick, thread } ) => {
102
98
  />
103
99
  ) ) }
104
100
  { overflowCount > 0 && (
105
- <div
106
- className="comment-avatar-overflow"
107
- style={ { zIndex: 0 } }
108
- title={ overflowTitle }
109
- >
110
- { overflowText }
111
- </div>
101
+ <Text weight={ 500 }>{ overflowText }</Text>
112
102
  ) }
113
- </div>
103
+ </HStack>
114
104
  </ToolbarButton>
115
105
  </CommentIconToolbarSlotFill.Fill>
116
106
  );
@@ -12,6 +12,7 @@ import {
12
12
  useEffect,
13
13
  useCallback,
14
14
  useMemo,
15
+ useRef,
15
16
  } from '@wordpress/element';
16
17
  import {
17
18
  __experimentalText as Text,
@@ -42,6 +43,7 @@ import CommentForm from './comment-form';
42
43
  import { focusCommentThread, getCommentExcerpt } from './utils';
43
44
  import { useFloatingThread } from './hooks';
44
45
  import { AddComment } from './add-comment';
46
+ import { store as editorStore } from '../../store';
45
47
 
46
48
  const { useBlockElement } = unlock( blockEditorPrivateApis );
47
49
  const { Menu } = unlock( componentsPrivateApis );
@@ -63,6 +65,7 @@ export function Comments( {
63
65
  const [ boardOffsets, setBoardOffsets ] = useState( {} );
64
66
  const [ blockRefs, setBlockRefs ] = useState( {} );
65
67
 
68
+ const { setCanvasMinHeight } = unlock( useDispatch( editorStore ) );
66
69
  const { blockCommentId, selectedBlockClientId, orderedBlockIds } =
67
70
  useSelect( ( select ) => {
68
71
  const { getBlockAttributes, getSelectedBlockClientId } =
@@ -149,8 +152,10 @@ export function Comments( {
149
152
 
150
153
  // Auto-select the related comment thread when a block is selected.
151
154
  useEffect( () => {
152
- setSelectedThread( blockCommentId ?? undefined );
153
- }, [ blockCommentId ] );
155
+ // Fallback to 'new-note-thread' when showing the comment board for a new note.
156
+ const fallback = showCommentBoard ? 'new-note-thread' : null;
157
+ setSelectedThread( blockCommentId ?? fallback );
158
+ }, [ blockCommentId, showCommentBoard ] );
154
159
 
155
160
  const setBlockRef = useCallback( ( id, blockRef ) => {
156
161
  setBlockRefs( ( prev ) => ( { ...prev, [ id ]: blockRef } ) );
@@ -166,7 +171,7 @@ export function Comments( {
166
171
  const offsets = {};
167
172
 
168
173
  if ( ! isFloating ) {
169
- return offsets;
174
+ return { offsets, minHeight: 0 };
170
175
  }
171
176
 
172
177
  // Find the index of the selected thread.
@@ -184,7 +189,7 @@ export function Comments( {
184
189
  ! selectedThreadData ||
185
190
  ! blockRefs[ selectedThreadData.id ]
186
191
  ) {
187
- return offsets;
192
+ return { offsets, minHeight: 0 };
188
193
  }
189
194
 
190
195
  let blockElement = blockRefs[ selectedThreadData.id ];
@@ -271,13 +276,36 @@ export function Comments( {
271
276
  threadTop: threadTop + additionalOffset,
272
277
  };
273
278
  }
274
- return offsets;
279
+
280
+ let editorMinHeight = 0;
281
+ // Take the calculated top of the final note plus its height as the editor min height.
282
+ const lastThread = threads[ threads.length - 1 ];
283
+ if ( blockRefs[ lastThread.id ] ) {
284
+ const lastBlockElement = blockRefs[ lastThread.id ];
285
+ const lastBlockRect = lastBlockElement?.getBoundingClientRect();
286
+ const lastThreadTop = lastBlockRect?.top || 0;
287
+ const lastThreadHeight = heights[ lastThread.id ] || 0;
288
+ const lastThreadOffset = offsets[ lastThread.id ] || 0;
289
+ editorMinHeight =
290
+ lastThreadTop + lastThreadHeight + lastThreadOffset + 32;
291
+ }
292
+
293
+ return { offsets, minHeight: editorMinHeight };
275
294
  };
276
- const newOffsets = calculateAllOffsets();
295
+ const { offsets: newOffsets, minHeight } = calculateAllOffsets();
277
296
  if ( Object.keys( newOffsets ).length > 0 ) {
278
297
  setBoardOffsets( newOffsets );
279
298
  }
280
- }, [ heights, blockRefs, isFloating, threads, selectedThread ] );
299
+ // Ensure the editor has enough height to scroll to all notes.
300
+ setCanvasMinHeight( minHeight );
301
+ }, [
302
+ heights,
303
+ blockRefs,
304
+ isFloating,
305
+ threads,
306
+ selectedThread,
307
+ setCanvasMinHeight,
308
+ ] );
281
309
 
282
310
  const hasThreads = Array.isArray( threads ) && threads.length > 0;
283
311
  if ( ! hasThreads && ! isFloating ) {
@@ -633,7 +661,7 @@ const CommentBoard = ( {
633
661
  } ) => {
634
662
  const [ actionState, setActionState ] = useState( false );
635
663
  const [ showConfirmDialog, setShowConfirmDialog ] = useState( false );
636
-
664
+ const actionButtonRef = useRef( null );
637
665
  const handleConfirmDelete = () => {
638
666
  onDelete( thread );
639
667
  setActionState( false );
@@ -643,6 +671,7 @@ const CommentBoard = ( {
643
671
  const handleCancel = () => {
644
672
  setActionState( false );
645
673
  setShowConfirmDialog( false );
674
+ actionButtonRef.current?.focus();
646
675
  };
647
676
 
648
677
  // Check if this is a resolution comment by checking metadata.
@@ -728,6 +757,7 @@ const CommentBoard = ( {
728
757
  <Menu.TriggerButton
729
758
  render={
730
759
  <Button
760
+ ref={ actionButtonRef }
731
761
  size="small"
732
762
  icon={ moreVertical }
733
763
  label={ __( 'Actions' ) }
@@ -761,6 +791,7 @@ const CommentBoard = ( {
761
791
  content: value,
762
792
  } );
763
793
  setActionState( false );
794
+ actionButtonRef.current?.focus();
764
795
  } }
765
796
  onCancel={ () => handleCancel() }
766
797
  thread={ thread }
@@ -70,6 +70,10 @@ export function useBlockComments( postId ) {
70
70
 
71
71
  // Process comments to build the tree structure.
72
72
  const { resultComments, unresolvedSortedThreads } = useMemo( () => {
73
+ if ( ! threads || threads.length === 0 ) {
74
+ return { resultComments: [], unresolvedSortedThreads: [] };
75
+ }
76
+
73
77
  const blocksWithComments = clientIds.reduce( ( results, clientId ) => {
74
78
  const commentId = getBlockAttributes( clientId )?.metadata?.noteId;
75
79
  if ( commentId ) {
@@ -82,10 +86,8 @@ export function useBlockComments( postId ) {
82
86
  const compare = {};
83
87
  const result = [];
84
88
 
85
- const allComments = threads ?? [];
86
-
87
89
  // Initialize each object with an empty `reply` array and map blockClientId.
88
- allComments.forEach( ( item ) => {
90
+ threads.forEach( ( item ) => {
89
91
  const itemBlock = Object.keys( blocksWithComments ).find(
90
92
  ( key ) => blocksWithComments[ key ] === item.id
91
93
  );
@@ -98,7 +100,7 @@ export function useBlockComments( postId ) {
98
100
  } );
99
101
 
100
102
  // Iterate over the data to build the tree structure.
101
- allComments.forEach( ( item ) => {
103
+ threads.forEach( ( item ) => {
102
104
  if ( item.parent === 0 ) {
103
105
  // If parent is 0, it's a root item, push it to the result array.
104
106
  result.push( compare[ item.id ] );
@@ -31,6 +31,7 @@ import {
31
31
  } from './hooks';
32
32
  import { focusCommentThread } from './utils';
33
33
  import PostTypeSupportCheck from '../post-type-support-check';
34
+ import { unlock } from '../../lock-unlock';
34
35
 
35
36
  function NotesSidebarContent( {
36
37
  showCommentBoard,
@@ -80,18 +81,22 @@ function NotesSidebar( { postId, mode } ) {
80
81
  const [ showCommentBoard, setShowCommentBoard ] = useState( false );
81
82
  const { getActiveComplementaryArea } = useSelect( interfaceStore );
82
83
  const { enableComplementaryArea } = useDispatch( interfaceStore );
84
+ const { toggleBlockSpotlight } = unlock( useDispatch( blockEditorStore ) );
83
85
  const isLargeViewport = useViewportMatch( 'medium' );
84
86
  const commentSidebarRef = useRef( null );
85
87
 
86
88
  const showFloatingSidebar = isLargeViewport && mode === 'post-only';
87
89
 
88
- const blockCommentId = useSelect( ( select ) => {
90
+ const { clientId, blockCommentId } = useSelect( ( select ) => {
89
91
  const { getBlockAttributes, getSelectedBlockClientId } =
90
92
  select( blockEditorStore );
91
- const clientId = getSelectedBlockClientId();
92
- return clientId
93
- ? getBlockAttributes( clientId )?.metadata?.noteId
94
- : null;
93
+ const _clientId = getSelectedBlockClientId();
94
+ return {
95
+ clientId: _clientId,
96
+ blockCommentId: _clientId
97
+ ? getBlockAttributes( _clientId )?.metadata?.noteId
98
+ : null,
99
+ };
95
100
  }, [] );
96
101
 
97
102
  const {
@@ -142,6 +147,7 @@ function NotesSidebar( { postId, mode } ) {
142
147
  // Focus a comment thread when there's a selected block with a comment.
143
148
  ! blockCommentId ? 'textarea' : undefined
144
149
  );
150
+ toggleBlockSpotlight( clientId, true );
145
151
  }
146
152
 
147
153
  return (
@@ -156,7 +162,12 @@ function NotesSidebar( { postId, mode } ) {
156
162
  <PluginSidebar
157
163
  identifier={ collabHistorySidebarName }
158
164
  name={ collabHistorySidebarName }
159
- title={ __( 'Notes' ) }
165
+ title={ __( 'All notes' ) }
166
+ header={
167
+ <h2 className="interface-complementary-area-header__title">
168
+ { __( 'All notes' ) }
169
+ </h2>
170
+ }
160
171
  icon={ commentIcon }
161
172
  closeLabel={ __( 'Close Notes' ) }
162
173
  >
@@ -197,11 +208,13 @@ function NotesSidebar( { postId, mode } ) {
197
208
  }
198
209
 
199
210
  export default function NotesSidebarContainer() {
200
- const { postId, mode } = useSelect( ( select ) => {
201
- const { getCurrentPostId, getRenderingMode } = select( editorStore );
211
+ const { postId, mode, editorMode } = useSelect( ( select ) => {
212
+ const { getCurrentPostId, getRenderingMode, getEditorMode } =
213
+ select( editorStore );
202
214
  return {
203
215
  postId: getCurrentPostId(),
204
216
  mode: getRenderingMode(),
217
+ editorMode: getEditorMode(),
205
218
  };
206
219
  }, [] );
207
220
 
@@ -209,6 +222,11 @@ export default function NotesSidebarContainer() {
209
222
  return null;
210
223
  }
211
224
 
225
+ // Hide Notes sidebar in Code Editor mode since block-level commenting.
226
+ if ( editorMode === 'text' ) {
227
+ return null;
228
+ }
229
+
212
230
  return (
213
231
  <PostTypeSupportCheck supportKeys="editor.notes">
214
232
  <NotesSidebar postId={ postId } mode={ mode } />
@@ -78,6 +78,7 @@
78
78
  border-width: var(--wp-admin-border-width-focus);
79
79
  border-style: solid;
80
80
  padding: var(--wp-admin-border-width-focus);
81
+ background: $white;
81
82
  }
82
83
 
83
84
  .editor-collab-sidebar-panel__comment-status {
@@ -154,57 +155,16 @@
154
155
  bottom: $grid-unit-10;
155
156
  }
156
157
 
157
- // Comment avatar indicators.
158
- .comment-avatar-indicator {
159
- position: relative;
160
- padding: 4px;
161
- min-width: auto;
162
- background: transparent;
163
- border: none;
164
- }
165
-
166
- .comment-avatar-stack {
167
- display: flex;
168
- align-items: center;
169
- position: relative;
170
- height: $icon-size;
171
- }
172
-
173
158
  .comment-avatar {
174
159
  width: $icon-size;
175
- height: $icon-size;
176
160
  border-radius: $radius-round;
177
- border: 2px solid $white;
178
- margin-left: -6px;
179
- flex-shrink: 0;
161
+ margin-left: -12px;
162
+ border-width: var(--wp-admin-border-width-focus);
163
+ border-style: solid;
164
+ padding: var(--wp-admin-border-width-focus);
165
+ background: $white;
180
166
 
181
167
  &:first-child {
182
168
  margin-left: 0;
183
- border-color: #de6e55;
184
- }
185
-
186
- &:nth-child(2) {
187
- border-color: #599637;
188
- }
189
-
190
- &:nth-child(3) {
191
- border-color: #3858e9;
192
169
  }
193
170
  }
194
-
195
- .comment-avatar-overflow {
196
- width: fit-content;
197
- height: $icon-size;
198
- border-radius: 4rem;
199
- padding: 0 4px;
200
- background: #757575;
201
- color: $white;
202
- border: 2px solid $white;
203
- margin-left: -6px;
204
- display: flex;
205
- align-items: center;
206
- justify-content: center;
207
- font-size: 10px;
208
- font-weight: 600;
209
- flex-shrink: 0;
210
- }
@@ -112,7 +112,7 @@ export default function MoreMenu() {
112
112
  <ModeSwitcher />
113
113
  <ActionItem.Slot
114
114
  name="core/plugin-more-menu"
115
- label={ __( 'Plugins' ) }
115
+ label={ __( 'Panels' ) }
116
116
  fillProps={ { onClick: onClose } }
117
117
  />
118
118
  <MenuGroup label={ __( 'Tools' ) }>
@@ -114,6 +114,7 @@ function VisualEditor( {
114
114
  isDesignPostType,
115
115
  postType,
116
116
  isPreview,
117
+ canvasMinHeight,
117
118
  } = useSelect( ( select ) => {
118
119
  const {
119
120
  getCurrentPostId,
@@ -122,7 +123,8 @@ function VisualEditor( {
122
123
  getEditorSettings,
123
124
  getRenderingMode,
124
125
  getDeviceType,
125
- } = select( editorStore );
126
+ getCanvasMinHeight,
127
+ } = unlock( select( editorStore ) );
126
128
  const { getPostType, getEditedEntityRecord } = select( coreStore );
127
129
  const postTypeSlug = getCurrentPostType();
128
130
  const _renderingMode = getRenderingMode();
@@ -162,6 +164,7 @@ function VisualEditor( {
162
164
  isFocusedEntity: !! editorSettings.onNavigateToPreviousEntityRecord,
163
165
  postType: postTypeSlug,
164
166
  isPreview: editorSettings.isPreviewMode,
167
+ canvasMinHeight: getCanvasMinHeight(),
165
168
  };
166
169
  }, [] );
167
170
  const { isCleanNewPost } = useSelect( editorStore );
@@ -185,6 +188,7 @@ function VisualEditor( {
185
188
  };
186
189
  }, [] );
187
190
 
191
+ const localRef = useRef();
188
192
  const deviceStyles = useResizeCanvas( deviceType );
189
193
  const [ globalLayoutSettings ] = useSettings( 'layout' );
190
194
 
@@ -331,30 +335,47 @@ function VisualEditor( {
331
335
  // Disable resizing in zoomed-out mode.
332
336
  ! isZoomedOut;
333
337
 
338
+ // Calculate the minimum height including scroll offset to fit all notes.
339
+ const calculatedMinHeight = useMemo( () => {
340
+ if ( ! localRef.current ) {
341
+ return canvasMinHeight;
342
+ }
343
+
344
+ const { ownerDocument } = localRef.current;
345
+ const scrollTop =
346
+ ownerDocument.documentElement.scrollTop ||
347
+ ownerDocument.body.scrollTop;
348
+
349
+ return canvasMinHeight + scrollTop;
350
+ }, [ canvasMinHeight ] );
351
+
334
352
  const iframeStyles = useMemo( () => {
335
353
  return [
336
354
  ...( styles ?? [] ),
337
355
  {
338
356
  // Ensures margins of children are contained so that the body background paints behind them.
339
- // Otherwise, the background of html (when zoomed out) would show there and appear broken. Its
357
+ // Otherwise, the background of html (when zoomed out) would show there and appear broken. It's
340
358
  // important mostly for post-only views yet conceivably an issue in templated views too.
341
- css: `:where(.block-editor-iframe__body){display:flow-root;}.is-root-container{display:flow-root;${
359
+ css: `:where(.block-editor-iframe__body){display:flow-root;${
360
+ calculatedMinHeight
361
+ ? `min-height:${ calculatedMinHeight }px;`
362
+ : ''
363
+ }}.is-root-container{display:flow-root;${
342
364
  // Some themes will have `min-height: 100vh` for the root container,
343
365
  // which isn't a requirement in auto resize mode.
344
366
  enableResizing ? 'min-height:0!important;' : ''
345
367
  }}
346
368
  ${
347
369
  enableResizing
348
- ? '.block-editor-iframe__html{background:var(--wp-editor-canvas-background);display:flex;align-items:center;justify-content:center;min-height:100vh;}.block-editor-iframe__body{width:100%;}'
370
+ ? `.block-editor-iframe__html{background:var(--wp-editor-canvas-background);display:flex;align-items:center;justify-content:center;min-height:100vh;}.block-editor-iframe__body{width:100%;}`
349
371
  : ''
350
372
  }`,
351
373
  // The CSS above centers the body content vertically when resizing is enabled and applies a background
352
374
  // color to the iframe HTML element to match the background color of the editor canvas.
353
375
  },
354
376
  ];
355
- }, [ styles, enableResizing ] );
377
+ }, [ styles, enableResizing, calculatedMinHeight ] );
356
378
 
357
- const localRef = useRef();
358
379
  const typewriterRef = useTypewriter();
359
380
  contentRef = useMergeRefs( [
360
381
  localRef,
@@ -526,3 +526,16 @@ export const setDefaultRenderingMode =
526
526
  .dispatch( preferencesStore )
527
527
  .set( 'core', 'renderingModes', newModes );
528
528
  };
529
+
530
+ /**
531
+ * Set the minimum height of the canvas.
532
+ *
533
+ * @param {number} minHeight
534
+ * @return {Object} Action object.
535
+ */
536
+ export function setCanvasMinHeight( minHeight ) {
537
+ return {
538
+ type: 'SET_CANVAS_MIN_HEIGHT',
539
+ minHeight,
540
+ };
541
+ }
@@ -270,3 +270,13 @@ export const getDefaultRenderingMode = createRegistrySelector(
270
270
  return defaultMode;
271
271
  }
272
272
  );
273
+
274
+ /**
275
+ * Get the canvas minimum height.
276
+ *
277
+ * @param {Object} state Global application state.
278
+ * @return {number} The canvas minimum height.
279
+ */
280
+ export function getCanvasMinHeight( state ) {
281
+ return state.canvasMinHeight;
282
+ }
@@ -384,6 +384,21 @@ export function publishSidebarActive( state = false, action ) {
384
384
  return state;
385
385
  }
386
386
 
387
+ /**
388
+ * Reducer for the canvas minimum height.
389
+ *
390
+ * @param {number} state Current state.
391
+ * @param {Object} action Dispatched action.
392
+ * @return {number} Updated state.
393
+ */
394
+ export function canvasMinHeight( state = 0, action ) {
395
+ switch ( action.type ) {
396
+ case 'SET_CANVAS_MIN_HEIGHT':
397
+ return action.minHeight;
398
+ }
399
+ return state;
400
+ }
401
+
387
402
  export default combineReducers( {
388
403
  postId,
389
404
  postType,
@@ -403,5 +418,6 @@ export default combineReducers( {
403
418
  listViewPanel,
404
419
  listViewToggleRef,
405
420
  publishSidebarActive,
421
+ canvasMinHeight,
406
422
  dataviews: dataviewsReducer,
407
423
  } );