@wordpress/block-library 9.41.1-next.v.202603102151.0 → 9.42.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 +2 -0
- package/build/cover/edit/cover-placeholder.cjs +7 -0
- package/build/cover/edit/cover-placeholder.cjs.map +2 -2
- package/build/html/modal.cjs +151 -229
- package/build/html/modal.cjs.map +2 -2
- package/build/image/edit.cjs +7 -0
- package/build/image/edit.cjs.map +2 -2
- package/build/media-text/media-container.cjs +6 -0
- package/build/media-text/media-container.cjs.map +2 -2
- package/build/navigation/edit/index.cjs +5 -4
- package/build/navigation/edit/index.cjs.map +2 -2
- package/build/navigation-link/shared/use-link-preview.cjs +29 -0
- package/build/navigation-link/shared/use-link-preview.cjs.map +2 -2
- package/build/nextpage/block.json +0 -1
- package/build/playlist-track/block.json +0 -0
- package/build/post-date/block.json +1 -3
- package/build/post-date/deprecated.cjs +82 -6
- package/build/post-date/deprecated.cjs.map +3 -3
- package/build/post-date/edit.cjs +49 -62
- package/build/post-date/edit.cjs.map +3 -3
- package/build/site-logo/edit.cjs +1 -3
- package/build/site-logo/edit.cjs.map +2 -2
- package/build/site-title/index.cjs +5 -1
- package/build/site-title/index.cjs.map +2 -2
- package/build/tab/add-tab-toolbar-control.cjs +22 -5
- package/build/tab/add-tab-toolbar-control.cjs.map +2 -2
- package/build/tab/remove-tab-toolbar-control.cjs +19 -1
- package/build/tab/remove-tab-toolbar-control.cjs.map +2 -2
- package/build/tabs/edit.cjs +85 -7
- package/build/tabs/edit.cjs.map +2 -2
- package/build/tabs/index.cjs +12 -2
- package/build/tabs/index.cjs.map +2 -2
- package/build/tabs-menu/block.json +1 -6
- package/build/tabs-menu/edit.cjs +11 -151
- package/build/tabs-menu/edit.cjs.map +3 -3
- package/build/tabs-menu/save.cjs.map +2 -2
- package/build/tabs-menu-item/block.json +14 -11
- package/build/tabs-menu-item/controls.cjs +2 -133
- package/build/tabs-menu-item/controls.cjs.map +3 -3
- package/build/tabs-menu-item/edit.cjs +44 -56
- package/build/tabs-menu-item/edit.cjs.map +3 -3
- package/build/tabs-menu-item/save.cjs +0 -1
- package/build/tabs-menu-item/save.cjs.map +2 -2
- package/build/utils/media-control.cjs +72 -29
- package/build/utils/media-control.cjs.map +3 -3
- package/build-module/cover/edit/cover-placeholder.mjs +7 -0
- package/build-module/cover/edit/cover-placeholder.mjs.map +2 -2
- package/build-module/html/modal.mjs +151 -229
- package/build-module/html/modal.mjs.map +2 -2
- package/build-module/image/edit.mjs +7 -0
- package/build-module/image/edit.mjs.map +2 -2
- package/build-module/media-text/media-container.mjs +7 -1
- package/build-module/media-text/media-container.mjs.map +2 -2
- package/build-module/navigation/edit/index.mjs +5 -4
- package/build-module/navigation/edit/index.mjs.map +2 -2
- package/build-module/navigation-link/shared/use-link-preview.mjs +28 -0
- package/build-module/navigation-link/shared/use-link-preview.mjs.map +2 -2
- package/build-module/nextpage/block.json +0 -1
- package/build-module/playlist-track/block.json +0 -0
- package/build-module/post-date/block.json +1 -3
- package/build-module/post-date/deprecated.mjs +82 -6
- package/build-module/post-date/deprecated.mjs.map +2 -2
- package/build-module/post-date/edit.mjs +49 -63
- package/build-module/post-date/edit.mjs.map +2 -2
- package/build-module/site-logo/edit.mjs +1 -3
- package/build-module/site-logo/edit.mjs.map +2 -2
- package/build-module/site-title/index.mjs +5 -1
- package/build-module/site-title/index.mjs.map +2 -2
- package/build-module/tab/add-tab-toolbar-control.mjs +22 -5
- package/build-module/tab/add-tab-toolbar-control.mjs.map +2 -2
- package/build-module/tab/remove-tab-toolbar-control.mjs +19 -1
- package/build-module/tab/remove-tab-toolbar-control.mjs.map +2 -2
- package/build-module/tabs/edit.mjs +87 -9
- package/build-module/tabs/edit.mjs.map +2 -2
- package/build-module/tabs/index.mjs +12 -2
- package/build-module/tabs/index.mjs.map +2 -2
- package/build-module/tabs-menu/block.json +1 -6
- package/build-module/tabs-menu/edit.mjs +13 -162
- package/build-module/tabs-menu/edit.mjs.map +2 -2
- package/build-module/tabs-menu/save.mjs.map +2 -2
- package/build-module/tabs-menu-item/block.json +14 -11
- package/build-module/tabs-menu-item/controls.mjs +4 -143
- package/build-module/tabs-menu-item/controls.mjs.map +2 -2
- package/build-module/tabs-menu-item/edit.mjs +45 -57
- package/build-module/tabs-menu-item/edit.mjs.map +3 -3
- package/build-module/tabs-menu-item/save.mjs +0 -1
- package/build-module/tabs-menu-item/save.mjs.map +2 -2
- package/build-module/utils/media-control.mjs +73 -30
- package/build-module/utils/media-control.mjs.map +2 -2
- package/build-style/editor-rtl.css +45 -11
- package/build-style/editor.css +45 -11
- package/build-style/navigation/style-rtl.css +4 -0
- package/build-style/navigation/style.css +4 -0
- package/build-style/navigation-overlay-close/style-rtl.css +3 -3
- package/build-style/navigation-overlay-close/style.css +3 -3
- package/build-style/style-rtl.css +7 -3
- package/build-style/style.css +7 -3
- package/build-style/tabs-menu/editor-rtl.css +5 -3
- package/build-style/tabs-menu/editor.css +5 -3
- package/package.json +38 -38
- package/src/cover/edit/cover-placeholder.js +8 -0
- package/src/html/modal.js +6 -77
- package/src/image/edit.js +8 -0
- package/src/media-text/media-container.js +8 -1
- package/src/navigation/edit/index.js +6 -4
- package/src/navigation/index.php +24 -17
- package/src/navigation/style.scss +10 -0
- package/src/navigation-link/index.php +9 -9
- package/src/navigation-link/shared/test/use-link-preview.test.js +149 -0
- package/src/navigation-link/shared/use-link-preview.js +43 -1
- package/src/navigation-overlay-close/style.scss +3 -3
- package/src/navigation-submenu/index.php +17 -11
- package/src/nextpage/block.json +0 -1
- package/src/playlist-track/block.json +0 -0
- package/src/playlist-track/edit.js +0 -0
- package/src/playlist-track/index.js +0 -0
- package/src/playlist-track/index.php +0 -0
- package/src/playlist-track/init.js +0 -0
- package/src/playlist-track/style.scss +0 -0
- package/src/post-date/block.json +1 -3
- package/src/post-date/deprecated.js +86 -6
- package/src/post-date/edit.js +65 -82
- package/src/site-logo/edit.js +1 -3
- package/src/site-title/index.js +5 -1
- package/src/tab/add-tab-toolbar-control.js +48 -23
- package/src/tab/remove-tab-toolbar-control.js +30 -10
- package/src/tabs/edit.js +133 -10
- package/src/tabs/index.js +12 -2
- package/src/tabs-menu/block.json +1 -6
- package/src/tabs-menu/edit.js +13 -214
- package/src/tabs-menu/editor.scss +7 -3
- package/src/tabs-menu/index.php +42 -27
- package/src/tabs-menu/save.js +0 -4
- package/src/tabs-menu-item/block.json +14 -11
- package/src/tabs-menu-item/controls.js +4 -167
- package/src/tabs-menu-item/edit.js +60 -69
- package/src/tabs-menu-item/index.php +11 -23
- package/src/tabs-menu-item/save.js +0 -1
- package/src/utils/media-control.js +61 -21
- package/src/utils/media-control.scss +54 -18
package/src/html/modal.js
CHANGED
|
@@ -37,8 +37,6 @@ export default function HTMLEditModal( {
|
|
|
37
37
|
const [ editedHtml, setEditedHtml ] = useState( html );
|
|
38
38
|
const [ editedCss, setEditedCss ] = useState( css );
|
|
39
39
|
const [ editedJs, setEditedJs ] = useState( js );
|
|
40
|
-
const [ isDirty, setIsDirty ] = useState( false );
|
|
41
|
-
const [ showUnsavedWarning, setShowUnsavedWarning ] = useState( false );
|
|
42
40
|
const [ isFullscreen, setIsFullscreen ] = useState( false );
|
|
43
41
|
|
|
44
42
|
const isMobileViewport = useViewportMatch( 'small', '<' );
|
|
@@ -60,18 +58,6 @@ export default function HTMLEditModal( {
|
|
|
60
58
|
return null;
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
const handleHtmlChange = ( value ) => {
|
|
64
|
-
setEditedHtml( value );
|
|
65
|
-
setIsDirty( true );
|
|
66
|
-
};
|
|
67
|
-
const handleCssChange = ( value ) => {
|
|
68
|
-
setEditedCss( value );
|
|
69
|
-
setIsDirty( true );
|
|
70
|
-
};
|
|
71
|
-
const handleJsChange = ( value ) => {
|
|
72
|
-
setEditedJs( value );
|
|
73
|
-
setIsDirty( true );
|
|
74
|
-
};
|
|
75
61
|
const handleUpdate = () => {
|
|
76
62
|
// For users without unfiltered_html capability, strip CSS and JS content
|
|
77
63
|
// to prevent kses from leaving broken content
|
|
@@ -82,25 +68,6 @@ export default function HTMLEditModal( {
|
|
|
82
68
|
js: canUserUseUnfilteredHTML ? editedJs : '',
|
|
83
69
|
} ),
|
|
84
70
|
} );
|
|
85
|
-
setIsDirty( false );
|
|
86
|
-
};
|
|
87
|
-
const handleCancel = () => {
|
|
88
|
-
setIsDirty( false );
|
|
89
|
-
onRequestClose();
|
|
90
|
-
};
|
|
91
|
-
const handleRequestClose = () => {
|
|
92
|
-
if ( isDirty ) {
|
|
93
|
-
setShowUnsavedWarning( true );
|
|
94
|
-
} else {
|
|
95
|
-
onRequestClose();
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
const handleDiscardChanges = () => {
|
|
99
|
-
setShowUnsavedWarning( false );
|
|
100
|
-
onRequestClose();
|
|
101
|
-
};
|
|
102
|
-
const handleContinueEditing = () => {
|
|
103
|
-
setShowUnsavedWarning( false );
|
|
104
71
|
};
|
|
105
72
|
const handleUpdateAndClose = () => {
|
|
106
73
|
handleUpdate();
|
|
@@ -114,12 +81,11 @@ export default function HTMLEditModal( {
|
|
|
114
81
|
<>
|
|
115
82
|
<Modal
|
|
116
83
|
title={ __( 'Edit HTML' ) }
|
|
117
|
-
onRequestClose={
|
|
84
|
+
onRequestClose={ onRequestClose }
|
|
118
85
|
className="block-library-html__modal"
|
|
119
86
|
size="large"
|
|
120
87
|
isDismissible={ false }
|
|
121
|
-
shouldCloseOnClickOutside={
|
|
122
|
-
shouldCloseOnEsc={ ! isDirty }
|
|
88
|
+
shouldCloseOnClickOutside={ false }
|
|
123
89
|
isFullScreen={ isFullscreen }
|
|
124
90
|
__experimentalHideHeader
|
|
125
91
|
>
|
|
@@ -183,7 +149,7 @@ export default function HTMLEditModal( {
|
|
|
183
149
|
>
|
|
184
150
|
<PlainText
|
|
185
151
|
value={ editedHtml }
|
|
186
|
-
onChange={
|
|
152
|
+
onChange={ setEditedHtml }
|
|
187
153
|
placeholder={ __( 'Write HTML…' ) }
|
|
188
154
|
aria-label={ __( 'HTML' ) }
|
|
189
155
|
className="block-library-html__modal-editor"
|
|
@@ -197,7 +163,7 @@ export default function HTMLEditModal( {
|
|
|
197
163
|
>
|
|
198
164
|
<PlainText
|
|
199
165
|
value={ editedCss }
|
|
200
|
-
onChange={
|
|
166
|
+
onChange={ setEditedCss }
|
|
201
167
|
placeholder={ __( 'Write CSS…' ) }
|
|
202
168
|
aria-label={ __( 'CSS' ) }
|
|
203
169
|
className="block-library-html__modal-editor"
|
|
@@ -212,7 +178,7 @@ export default function HTMLEditModal( {
|
|
|
212
178
|
>
|
|
213
179
|
<PlainText
|
|
214
180
|
value={ editedJs }
|
|
215
|
-
onChange={
|
|
181
|
+
onChange={ setEditedJs }
|
|
216
182
|
placeholder={ __(
|
|
217
183
|
'Write JavaScript…'
|
|
218
184
|
) }
|
|
@@ -241,7 +207,7 @@ export default function HTMLEditModal( {
|
|
|
241
207
|
<Button
|
|
242
208
|
__next40pxDefaultSize
|
|
243
209
|
variant="tertiary"
|
|
244
|
-
onClick={
|
|
210
|
+
onClick={ onRequestClose }
|
|
245
211
|
>
|
|
246
212
|
{ __( 'Cancel' ) }
|
|
247
213
|
</Button>
|
|
@@ -256,43 +222,6 @@ export default function HTMLEditModal( {
|
|
|
256
222
|
</VStack>
|
|
257
223
|
</Tabs>
|
|
258
224
|
</Modal>
|
|
259
|
-
|
|
260
|
-
{ showUnsavedWarning && (
|
|
261
|
-
<Modal
|
|
262
|
-
title={ __( 'Unsaved changes' ) }
|
|
263
|
-
onRequestClose={ handleContinueEditing }
|
|
264
|
-
size="medium"
|
|
265
|
-
>
|
|
266
|
-
<p>
|
|
267
|
-
{ __(
|
|
268
|
-
'You have unsaved changes. What would you like to do?'
|
|
269
|
-
) }
|
|
270
|
-
</p>
|
|
271
|
-
<Flex direction="row" justify="flex-end" gap={ 2 }>
|
|
272
|
-
<Button
|
|
273
|
-
__next40pxDefaultSize
|
|
274
|
-
variant="secondary"
|
|
275
|
-
onClick={ handleDiscardChanges }
|
|
276
|
-
>
|
|
277
|
-
{ __( 'Discard unsaved changes' ) }
|
|
278
|
-
</Button>
|
|
279
|
-
<Button
|
|
280
|
-
__next40pxDefaultSize
|
|
281
|
-
variant="secondary"
|
|
282
|
-
onClick={ handleContinueEditing }
|
|
283
|
-
>
|
|
284
|
-
{ __( 'Continue editing' ) }
|
|
285
|
-
</Button>
|
|
286
|
-
<Button
|
|
287
|
-
__next40pxDefaultSize
|
|
288
|
-
variant="primary"
|
|
289
|
-
onClick={ handleUpdateAndClose }
|
|
290
|
-
>
|
|
291
|
-
{ __( 'Update and close' ) }
|
|
292
|
-
</Button>
|
|
293
|
-
</Flex>
|
|
294
|
-
</Modal>
|
|
295
|
-
) }
|
|
296
225
|
</>
|
|
297
226
|
);
|
|
298
227
|
}
|
package/src/image/edit.js
CHANGED
|
@@ -159,6 +159,7 @@ export function ImageEdit( {
|
|
|
159
159
|
const { createErrorNotice } = useDispatch( noticesStore );
|
|
160
160
|
function onUploadError( message ) {
|
|
161
161
|
createErrorNotice( message, { type: 'snackbar' } );
|
|
162
|
+
setTemporaryURL();
|
|
162
163
|
setAttributes( {
|
|
163
164
|
src: undefined,
|
|
164
165
|
id: undefined,
|
|
@@ -167,6 +168,12 @@ export function ImageEdit( {
|
|
|
167
168
|
} );
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
function onFilesPreUpload( files ) {
|
|
172
|
+
if ( files.length === 1 ) {
|
|
173
|
+
setTemporaryURL( createBlobURL( files[ 0 ] ) );
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
170
177
|
function onSelectImagesList( images ) {
|
|
171
178
|
const win = containerRef.current?.ownerDocument.defaultView;
|
|
172
179
|
|
|
@@ -479,6 +486,7 @@ export function ImageEdit( {
|
|
|
479
486
|
icon={ <BlockIcon icon={ icon } /> }
|
|
480
487
|
onSelect={ onSelectImage }
|
|
481
488
|
onSelectURL={ onSelectURL }
|
|
489
|
+
onFilesPreUpload={ onFilesPreUpload }
|
|
482
490
|
onError={ onUploadError }
|
|
483
491
|
placeholder={ placeholder }
|
|
484
492
|
allowedTypes={ ALLOWED_MEDIA_TYPES }
|
|
@@ -18,7 +18,7 @@ import { __ } from '@wordpress/i18n';
|
|
|
18
18
|
import { useViewportMatch } from '@wordpress/compose';
|
|
19
19
|
import { useDispatch } from '@wordpress/data';
|
|
20
20
|
import { forwardRef } from '@wordpress/element';
|
|
21
|
-
import { isBlobURL } from '@wordpress/blob';
|
|
21
|
+
import { createBlobURL, isBlobURL } from '@wordpress/blob';
|
|
22
22
|
import { store as noticesStore } from '@wordpress/notices';
|
|
23
23
|
import { media as icon } from '@wordpress/icons';
|
|
24
24
|
|
|
@@ -82,6 +82,12 @@ function PlaceholderContainer( {
|
|
|
82
82
|
createErrorNotice( message, { type: 'snackbar' } );
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
+
const onFilesPreUpload = ( files ) => {
|
|
86
|
+
if ( files.length === 1 ) {
|
|
87
|
+
onSelectMedia( { url: createBlobURL( files[ 0 ] ) } );
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
85
91
|
return (
|
|
86
92
|
<MediaPlaceholder
|
|
87
93
|
icon={ <BlockIcon icon={ icon } /> }
|
|
@@ -92,6 +98,7 @@ function PlaceholderContainer( {
|
|
|
92
98
|
onSelect={ onSelectMedia }
|
|
93
99
|
onToggleFeaturedImage={ toggleUseFeaturedImage }
|
|
94
100
|
allowedTypes={ ALLOWED_MEDIA_TYPES }
|
|
101
|
+
onFilesPreUpload={ onFilesPreUpload }
|
|
95
102
|
onError={ onUploadError }
|
|
96
103
|
disableMediaButtons={ mediaUrl }
|
|
97
104
|
/>
|
|
@@ -86,7 +86,7 @@ import {
|
|
|
86
86
|
NAVIGATION_OVERLAY_TEMPLATE_PART_AREA,
|
|
87
87
|
} from '../constants';
|
|
88
88
|
|
|
89
|
-
const {
|
|
89
|
+
const { isNavigationPostEditorKey } = unlock( blockEditorPrivateApis );
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* Component that renders the Add page button for the Navigation block.
|
|
@@ -330,15 +330,17 @@ function Navigation( {
|
|
|
330
330
|
} = useSelect( ( select ) => {
|
|
331
331
|
const { getSettings } = select( blockEditorStore );
|
|
332
332
|
const settings = getSettings();
|
|
333
|
+
|
|
333
334
|
return {
|
|
334
335
|
isPreviewMode: settings.isPreviewMode,
|
|
335
336
|
onNavigateToEntityRecord: settings?.onNavigateToEntityRecord,
|
|
336
337
|
// Needed to construct the template part ID for the overlay preview.
|
|
337
338
|
currentTheme: select( coreStore ).getCurrentTheme()?.stylesheet,
|
|
338
|
-
//
|
|
339
|
-
//
|
|
339
|
+
// When editing a navigation post directly in an isolated editor,
|
|
340
|
+
// always show navigation expanded (no hamburger) so users can see
|
|
341
|
+
// and interact with all menu items.
|
|
340
342
|
editorDisabledResponsive:
|
|
341
|
-
|
|
343
|
+
!! settings?.[ isNavigationPostEditorKey ],
|
|
342
344
|
};
|
|
343
345
|
}, [] );
|
|
344
346
|
const hasAlreadyRendered = isPreviewMode ? false : recursionDetected;
|
package/src/navigation/index.php
CHANGED
|
@@ -697,14 +697,10 @@ class WP_Navigation_Block_Renderer {
|
|
|
697
697
|
if ( ! empty( $attributes['overlay'] ) ) {
|
|
698
698
|
// Get blocks from the overlay template part.
|
|
699
699
|
$overlay_blocks = static::get_overlay_blocks_from_template_part( $attributes['overlay'], $attributes );
|
|
700
|
-
// Check if overlay contains a navigation-overlay-close block.
|
|
701
|
-
$has_custom_overlay_close_block = block_core_navigation_block_tree_has_block_type(
|
|
702
|
-
$overlay_blocks,
|
|
703
|
-
'core/navigation-overlay-close',
|
|
704
|
-
array( 'core/navigation' ) // Skip navigation blocks, as they cannot contain an overlay close block
|
|
705
|
-
);
|
|
706
700
|
// Render template part blocks directly without navigation container wrapper.
|
|
707
701
|
$overlay_blocks_html = static::get_template_part_blocks_html( $overlay_blocks );
|
|
702
|
+
// Check if overlay contains a navigation-overlay-close block (detect in rendered HTML so it works with patterns).
|
|
703
|
+
$has_custom_overlay_close_block = block_core_navigation_overlay_html_has_close_block( $overlay_blocks_html );
|
|
708
704
|
// Add Interactivity API directives to the overlay close block if present.
|
|
709
705
|
if ( $has_custom_overlay_close_block && $is_interactive ) {
|
|
710
706
|
$tags = new WP_HTML_Tag_Processor( $overlay_blocks_html );
|
|
@@ -1094,6 +1090,28 @@ if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
|
|
|
1094
1090
|
}
|
|
1095
1091
|
}
|
|
1096
1092
|
|
|
1093
|
+
/**
|
|
1094
|
+
* Checks if the overlay HTML contains a navigation-overlay-close block.
|
|
1095
|
+
*
|
|
1096
|
+
* Uses WP_HTML_Tag_Processor to detect the close button in rendered output,
|
|
1097
|
+
* so it works when the overlay uses patterns (pattern content is rendered at
|
|
1098
|
+
* output time, not in the block tree).
|
|
1099
|
+
*
|
|
1100
|
+
* @since 7.0.0
|
|
1101
|
+
*
|
|
1102
|
+
* @param string $html The rendered overlay HTML.
|
|
1103
|
+
* @return bool True if a close button element is found.
|
|
1104
|
+
*/
|
|
1105
|
+
function block_core_navigation_overlay_html_has_close_block( $html ) {
|
|
1106
|
+
$tags = new WP_HTML_Tag_Processor( $html );
|
|
1107
|
+
return $tags->next_tag(
|
|
1108
|
+
array(
|
|
1109
|
+
'tag_name' => 'BUTTON',
|
|
1110
|
+
'class_name' => 'wp-block-navigation-overlay-close',
|
|
1111
|
+
)
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1097
1115
|
/**
|
|
1098
1116
|
* Add Interactivity API directives to the navigation-overlay-close block
|
|
1099
1117
|
* markup using the Tag Processor.
|
|
@@ -1326,17 +1344,6 @@ function block_core_navigation_build_css_font_sizes( $attributes ) {
|
|
|
1326
1344
|
return $font_sizes;
|
|
1327
1345
|
}
|
|
1328
1346
|
|
|
1329
|
-
/**
|
|
1330
|
-
* Returns the top-level submenu SVG chevron icon.
|
|
1331
|
-
*
|
|
1332
|
-
* @since 5.9.0
|
|
1333
|
-
*
|
|
1334
|
-
* @return string
|
|
1335
|
-
*/
|
|
1336
|
-
function block_core_navigation_render_submenu_icon() {
|
|
1337
|
-
return '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true" focusable="false"><path d="M1.50002 4L6.00002 8L10.5 4" stroke-width="1.5"></path></svg>';
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
1347
|
/**
|
|
1341
1348
|
* Filter out empty "null" blocks from the block list.
|
|
1342
1349
|
* 'parse_blocks' includes a null block with '\n\n' as the content when
|
|
@@ -700,8 +700,18 @@ button.wp-block-navigation-item__content {
|
|
|
700
700
|
display: none;
|
|
701
701
|
}
|
|
702
702
|
|
|
703
|
+
// Override justification dropdown menu positioning rules inside custom overlays.
|
|
704
|
+
// Mirrors the protection at lines 674-678 for default overlays, which does not
|
|
705
|
+
// apply here. Prevents .items-justified-right descendant rules from cascading
|
|
706
|
+
// right: 0 into the overlay and anchoring submenus off the left edge of the viewport.
|
|
707
|
+
// See: https://github.com/WordPress/gutenberg/issues/76276
|
|
703
708
|
.wp-block-navigation__overlay-container {
|
|
704
709
|
display: block;
|
|
710
|
+
|
|
711
|
+
.wp-block-navigation__submenu-container {
|
|
712
|
+
right: auto;
|
|
713
|
+
left: 0;
|
|
714
|
+
}
|
|
705
715
|
}
|
|
706
716
|
}
|
|
707
717
|
}
|
|
@@ -5,14 +5,8 @@
|
|
|
5
5
|
* @package WordPress
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
require_once __DIR__ . '/shared/item-should-render.php';
|
|
11
|
-
require_once __DIR__ . '/shared/render-submenu-icon.php';
|
|
12
|
-
} else {
|
|
13
|
-
require_once __DIR__ . '/navigation-link/shared/item-should-render.php';
|
|
14
|
-
require_once __DIR__ . '/navigation-link/shared/render-submenu-icon.php';
|
|
15
|
-
}
|
|
8
|
+
require_once __DIR__ . '/navigation-link/shared/item-should-render.php';
|
|
9
|
+
require_once __DIR__ . '/navigation-link/shared/render-submenu-icon.php';
|
|
16
10
|
|
|
17
11
|
/**
|
|
18
12
|
* Build an array with CSS classes and inline styles defining the colors
|
|
@@ -262,7 +256,13 @@ function render_block_core_navigation_link( $attributes, $content, $block ) {
|
|
|
262
256
|
|
|
263
257
|
if ( isset( $block->context['showSubmenuIcon'] ) && $block->context['showSubmenuIcon'] && $has_submenu ) {
|
|
264
258
|
// The submenu icon can be hidden by a CSS rule on the Navigation Block.
|
|
265
|
-
$html .= '<span class="wp-block-navigation__submenu-icon">'
|
|
259
|
+
$html .= '<span class="wp-block-navigation__submenu-icon">';
|
|
260
|
+
if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
|
|
261
|
+
$html .= gutenberg_block_core_shared_navigation_render_submenu_icon();
|
|
262
|
+
} else {
|
|
263
|
+
$html .= block_core_shared_navigation_render_submenu_icon();
|
|
264
|
+
}
|
|
265
|
+
$html .= '</span>';
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
if ( $has_submenu ) {
|
|
@@ -36,6 +36,7 @@ jest.mock( '../../../lock-unlock', () => ( {
|
|
|
36
36
|
import {
|
|
37
37
|
computeDisplayUrl,
|
|
38
38
|
computeBadges,
|
|
39
|
+
isHomepage,
|
|
39
40
|
useLinkPreview,
|
|
40
41
|
} from '../use-link-preview';
|
|
41
42
|
|
|
@@ -144,6 +145,74 @@ describe( 'computeDisplayUrl', () => {
|
|
|
144
145
|
} );
|
|
145
146
|
} );
|
|
146
147
|
|
|
148
|
+
describe( 'isHomepage', () => {
|
|
149
|
+
const host = 'homepage.com';
|
|
150
|
+
const homeUrl = 'https://' + host;
|
|
151
|
+
|
|
152
|
+
test.each( [
|
|
153
|
+
[ '/', homeUrl ],
|
|
154
|
+
[ '/', undefined ],
|
|
155
|
+
] )( 'should return true for root path "%s"', ( url, homeUrlParam ) => {
|
|
156
|
+
expect( isHomepage( url, homeUrlParam ) ).toBe( true );
|
|
157
|
+
} );
|
|
158
|
+
|
|
159
|
+
// Check combinations of http/s and trailing slash
|
|
160
|
+
test.each( [
|
|
161
|
+
[ `http://${ host }`, homeUrl ],
|
|
162
|
+
[ `https://${ host }`, homeUrl ],
|
|
163
|
+
[ `http://${ host }/`, homeUrl ],
|
|
164
|
+
[ `https://${ host }/`, homeUrl ],
|
|
165
|
+
] )( 'should return true for site URL "%s"', ( url ) => {
|
|
166
|
+
expect( isHomepage( url, homeUrl ) ).toBe( true );
|
|
167
|
+
} );
|
|
168
|
+
|
|
169
|
+
test.each( [
|
|
170
|
+
[ '', homeUrl ],
|
|
171
|
+
[ `https://${ host }`, '' ],
|
|
172
|
+
[ `https://${ host }`, undefined ],
|
|
173
|
+
] )(
|
|
174
|
+
'should return false when url or homeUrl is empty and not a / path',
|
|
175
|
+
( url, homeUrlParam ) => {
|
|
176
|
+
expect( isHomepage( url, homeUrlParam ) ).toBe( false );
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const subdomain = 'sub.' + host;
|
|
181
|
+
test.each( [
|
|
182
|
+
[ false, `http://${ subdomain }/`, homeUrl ],
|
|
183
|
+
[ false, `https://${ subdomain }`, homeUrl ],
|
|
184
|
+
[ true, `http://${ subdomain }/`, `https://${ subdomain }` ],
|
|
185
|
+
[ true, `https://${ subdomain }`, `https://${ subdomain }` ],
|
|
186
|
+
] )(
|
|
187
|
+
'should return %s for subdomain (url=%s, homeUrl=%s)',
|
|
188
|
+
( expected, url, homeUrlParam ) => {
|
|
189
|
+
expect( isHomepage( url, homeUrlParam ) ).toBe( expected );
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const path = '/wordpress';
|
|
194
|
+
const subdirHomeUrl = 'https://' + host + path;
|
|
195
|
+
|
|
196
|
+
test.each( [
|
|
197
|
+
[ `https://${ host }${ path }`, subdirHomeUrl ],
|
|
198
|
+
[ `https://${ host }${ path }/`, subdirHomeUrl ],
|
|
199
|
+
[ `http://${ host }${ path }`, subdirHomeUrl ],
|
|
200
|
+
[ `http://${ host }${ path }/`, subdirHomeUrl ],
|
|
201
|
+
] )( 'should return true for subdirectory homepage "%s"', ( url ) => {
|
|
202
|
+
expect( isHomepage( url, subdirHomeUrl ) ).toBe( true );
|
|
203
|
+
} );
|
|
204
|
+
|
|
205
|
+
test.each( [
|
|
206
|
+
[ `https://${ host }/page`, homeUrl ],
|
|
207
|
+
[ '/page', homeUrl ],
|
|
208
|
+
[ `https://${ host }`, subdirHomeUrl ],
|
|
209
|
+
[ `https://${ host }/`, subdirHomeUrl ],
|
|
210
|
+
[ `https://${ host }${ path }/page`, subdirHomeUrl ],
|
|
211
|
+
] )( 'should return false for non-homepage "%s"', ( url, homeUrlParam ) => {
|
|
212
|
+
expect( isHomepage( url, homeUrlParam ) ).toBe( false );
|
|
213
|
+
} );
|
|
214
|
+
} );
|
|
215
|
+
|
|
147
216
|
describe( 'computeBadges', () => {
|
|
148
217
|
describe( 'kind badges', () => {
|
|
149
218
|
it( 'should show "External link" badge for external links', () => {
|
|
@@ -183,6 +252,67 @@ describe( 'computeBadges', () => {
|
|
|
183
252
|
} );
|
|
184
253
|
} );
|
|
185
254
|
|
|
255
|
+
it( 'should show "Homepage" badge for root path', () => {
|
|
256
|
+
const badges = computeBadges( {
|
|
257
|
+
url: '/',
|
|
258
|
+
isExternal: false,
|
|
259
|
+
} );
|
|
260
|
+
|
|
261
|
+
expect( badges ).toContainEqual( {
|
|
262
|
+
label: 'Homepage',
|
|
263
|
+
intent: 'default',
|
|
264
|
+
} );
|
|
265
|
+
} );
|
|
266
|
+
|
|
267
|
+
test.each( [
|
|
268
|
+
[ 'https://example.com' ],
|
|
269
|
+
[ 'https://example.com/' ],
|
|
270
|
+
[ 'http://example.com' ],
|
|
271
|
+
[ 'http://example.com/' ],
|
|
272
|
+
] )( 'should show "Homepage" badge when url is "%s"', ( url ) => {
|
|
273
|
+
const badges = computeBadges( {
|
|
274
|
+
url,
|
|
275
|
+
homeUrl: 'https://example.com',
|
|
276
|
+
isExternal: false,
|
|
277
|
+
} );
|
|
278
|
+
expect( badges ).toContainEqual( {
|
|
279
|
+
label: 'Homepage',
|
|
280
|
+
intent: 'default',
|
|
281
|
+
} );
|
|
282
|
+
} );
|
|
283
|
+
|
|
284
|
+
test.each( [ [ 'https://sub.example.com/', 'https://example.com' ] ] )(
|
|
285
|
+
'should not show Homepage badge when subdomain url "%s" does not match homeUrl "%s"',
|
|
286
|
+
( url, homeUrl ) => {
|
|
287
|
+
const badges = computeBadges( {
|
|
288
|
+
url,
|
|
289
|
+
homeUrl,
|
|
290
|
+
isExternal: false,
|
|
291
|
+
} );
|
|
292
|
+
expect( badges ).not.toContainEqual( {
|
|
293
|
+
label: 'Homepage',
|
|
294
|
+
intent: 'default',
|
|
295
|
+
} );
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
test.each( [
|
|
300
|
+
[ 'https://sub.example.com/', 'https://sub.example.com' ],
|
|
301
|
+
] )(
|
|
302
|
+
'should show Homepage badge when subdomain url "%s" matches homeUrl "%s"',
|
|
303
|
+
( url, homeUrl ) => {
|
|
304
|
+
const badges = computeBadges( {
|
|
305
|
+
url,
|
|
306
|
+
homeUrl,
|
|
307
|
+
isExternal: false,
|
|
308
|
+
} );
|
|
309
|
+
expect( badges ).toContainEqual( {
|
|
310
|
+
label: 'Homepage',
|
|
311
|
+
intent: 'default',
|
|
312
|
+
} );
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
|
|
186
316
|
it( 'should show page badge for relative paths', () => {
|
|
187
317
|
const badges = computeBadges( {
|
|
188
318
|
url: '/relative-path',
|
|
@@ -269,6 +399,25 @@ it( 'should show "Internal link" badge for hash links even when type is present'
|
|
|
269
399
|
} );
|
|
270
400
|
} );
|
|
271
401
|
|
|
402
|
+
it( 'should show "Homepage" badge for root path even when type is present', () => {
|
|
403
|
+
const badges = computeBadges( {
|
|
404
|
+
url: '/',
|
|
405
|
+
type: 'page',
|
|
406
|
+
isExternal: false,
|
|
407
|
+
} );
|
|
408
|
+
|
|
409
|
+
// Should prioritize homepage detection over type
|
|
410
|
+
expect( badges ).toContainEqual( {
|
|
411
|
+
label: 'Homepage',
|
|
412
|
+
intent: 'default',
|
|
413
|
+
} );
|
|
414
|
+
// Should NOT show Page badge
|
|
415
|
+
expect( badges ).not.toContainEqual( {
|
|
416
|
+
label: 'Page',
|
|
417
|
+
intent: 'default',
|
|
418
|
+
} );
|
|
419
|
+
} );
|
|
420
|
+
|
|
272
421
|
test.each( [
|
|
273
422
|
[ 'www.test.com', 'URLs without protocol' ],
|
|
274
423
|
[ 'google.com', 'domain-only URLs without protocol' ],
|
|
@@ -26,6 +26,41 @@ function capitalize( str ) {
|
|
|
26
26
|
return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Check if a URL points to the site homepage.
|
|
31
|
+
* Handles protocol (http/https) and trailing slash variations.
|
|
32
|
+
* Does not match subdomains unless they are the site URL.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} url - The URL to check
|
|
35
|
+
* @param {string} homeUrl - The WordPress site URL
|
|
36
|
+
* @return {boolean} True if url is the homepage
|
|
37
|
+
*/
|
|
38
|
+
export function isHomepage( url, homeUrl ) {
|
|
39
|
+
if ( url === '/' ) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
if ( ! url || ! homeUrl ) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const urlParsed = new URL( url, homeUrl );
|
|
47
|
+
const homeParsed = new URL( homeUrl );
|
|
48
|
+
|
|
49
|
+
// Same host, i.e. sub.homepage.com or homepage.com
|
|
50
|
+
if ( urlParsed.hostname !== homeParsed.hostname ) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Path must match site root (normalize trailing slash)
|
|
55
|
+
const urlPath = urlParsed.pathname.replace( /\/$/, '' );
|
|
56
|
+
const homePath = homeParsed.pathname.replace( /\/$/, '' );
|
|
57
|
+
|
|
58
|
+
return urlPath === homePath;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
29
64
|
/**
|
|
30
65
|
* Compute display URL - strips site URL if internal, shows full URL if external.
|
|
31
66
|
*
|
|
@@ -79,6 +114,7 @@ export function computeDisplayUrl( { linkUrl, homeUrl } = {} ) {
|
|
|
79
114
|
*
|
|
80
115
|
* @param {Object} options - Options object
|
|
81
116
|
* @param {string} options.url - Link URL
|
|
117
|
+
* @param {string} options.homeUrl - WordPress site URL (for homepage detection)
|
|
82
118
|
* @param {string} options.type - Entity type (page, post, etc.)
|
|
83
119
|
* @param {boolean} options.isExternal - Whether link is external
|
|
84
120
|
* @param {string} options.entityStatus - Entity status (publish, draft, etc.)
|
|
@@ -88,6 +124,7 @@ export function computeDisplayUrl( { linkUrl, homeUrl } = {} ) {
|
|
|
88
124
|
*/
|
|
89
125
|
export function computeBadges( {
|
|
90
126
|
url,
|
|
127
|
+
homeUrl,
|
|
91
128
|
type,
|
|
92
129
|
isExternal,
|
|
93
130
|
entityStatus,
|
|
@@ -95,7 +132,6 @@ export function computeBadges( {
|
|
|
95
132
|
isEntityAvailable,
|
|
96
133
|
} ) {
|
|
97
134
|
const badges = [];
|
|
98
|
-
|
|
99
135
|
// Kind badge
|
|
100
136
|
if ( url ) {
|
|
101
137
|
if ( isExternal ) {
|
|
@@ -110,6 +146,11 @@ export function computeBadges( {
|
|
|
110
146
|
label: __( 'Internal link' ),
|
|
111
147
|
intent: 'default',
|
|
112
148
|
} );
|
|
149
|
+
} else if ( isHomepage( url, homeUrl ) ) {
|
|
150
|
+
badges.push( {
|
|
151
|
+
label: __( 'Homepage' ),
|
|
152
|
+
intent: 'default',
|
|
153
|
+
} );
|
|
113
154
|
} else if ( type && type !== 'custom' ) {
|
|
114
155
|
// Show entity type badge (page, post, category, etc.)
|
|
115
156
|
// but not 'custom' since that's just a manual link
|
|
@@ -226,6 +267,7 @@ export function useLinkPreview( {
|
|
|
226
267
|
// Compute badges
|
|
227
268
|
const badges = computeBadges( {
|
|
228
269
|
url,
|
|
270
|
+
homeUrl,
|
|
229
271
|
type,
|
|
230
272
|
isExternal,
|
|
231
273
|
entityStatus: entityRecord?.status,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
align-items: center;
|
|
4
4
|
justify-content: center;
|
|
5
5
|
gap: 0.5em;
|
|
6
|
-
padding: 0
|
|
6
|
+
padding: 0;
|
|
7
7
|
border: none;
|
|
8
8
|
background: transparent;
|
|
9
9
|
cursor: pointer;
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
svg {
|
|
17
|
-
width:
|
|
18
|
-
height:
|
|
17
|
+
width: 24px;
|
|
18
|
+
height: 24px;
|
|
19
19
|
fill: currentColor;
|
|
20
20
|
display: block;
|
|
21
21
|
flex-shrink: 0;
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* @package WordPress
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
require_once __DIR__ . '/navigation-link/shared/item-should-render.php';
|
|
9
|
+
require_once __DIR__ . '/navigation-link/shared/render-submenu-icon.php';
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
12
|
* Returns the submenu visibility value with backward compatibility
|
|
10
13
|
* for the deprecated openSubmenusOnClick attribute.
|
|
@@ -46,15 +49,6 @@ function block_core_navigation_submenu_get_submenu_visibility( $context ) {
|
|
|
46
49
|
return $submenu_visibility ?? 'hover';
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
// Path differs between source and build: '../navigation-link/shared/' in source, './navigation-link/shared/' in build.
|
|
50
|
-
if ( file_exists( __DIR__ . '/../navigation-link/shared/item-should-render.php' ) ) {
|
|
51
|
-
require_once __DIR__ . '/../navigation-link/shared/item-should-render.php';
|
|
52
|
-
require_once __DIR__ . '/../navigation-link/shared/render-submenu-icon.php';
|
|
53
|
-
} else {
|
|
54
|
-
require_once __DIR__ . '/navigation-link/shared/item-should-render.php';
|
|
55
|
-
require_once __DIR__ . '/navigation-link/shared/render-submenu-icon.php';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
52
|
/**
|
|
59
53
|
* Build an array with CSS classes and inline styles defining the font sizes
|
|
60
54
|
* which will be applied to the navigation markup in the front-end.
|
|
@@ -240,7 +234,13 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) {
|
|
|
240
234
|
if ( $show_submenu_indicators && $has_submenu ) {
|
|
241
235
|
// The submenu icon is rendered in a button here
|
|
242
236
|
// so that there's a clickable element to open the submenu.
|
|
243
|
-
$html .= '<button aria-label="' . esc_attr( $aria_label ) . '" class="wp-block-navigation__submenu-icon wp-block-navigation-submenu__toggle" aria-expanded="false">'
|
|
237
|
+
$html .= '<button aria-label="' . esc_attr( $aria_label ) . '" class="wp-block-navigation__submenu-icon wp-block-navigation-submenu__toggle" aria-expanded="false">';
|
|
238
|
+
if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
|
|
239
|
+
$html .= gutenberg_block_core_shared_navigation_render_submenu_icon();
|
|
240
|
+
} else {
|
|
241
|
+
$html .= block_core_shared_navigation_render_submenu_icon();
|
|
242
|
+
}
|
|
243
|
+
$html .= '</button>';
|
|
244
244
|
}
|
|
245
245
|
} else {
|
|
246
246
|
$html .= '<button aria-label="' . esc_attr( $aria_label ) . '" class="wp-block-navigation-item__content wp-block-navigation-submenu__toggle" aria-expanded="false">';
|
|
@@ -262,7 +262,13 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) {
|
|
|
262
262
|
$html .= '</button>';
|
|
263
263
|
|
|
264
264
|
if ( $has_submenu ) {
|
|
265
|
-
$html .= '<span class="wp-block-navigation__submenu-icon">'
|
|
265
|
+
$html .= '<span class="wp-block-navigation__submenu-icon">';
|
|
266
|
+
if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
|
|
267
|
+
$html .= gutenberg_block_core_shared_navigation_render_submenu_icon();
|
|
268
|
+
} else {
|
|
269
|
+
$html .= block_core_shared_navigation_render_submenu_icon();
|
|
270
|
+
}
|
|
271
|
+
$html .= '</span>';
|
|
266
272
|
}
|
|
267
273
|
}
|
|
268
274
|
|