@wordpress/block-library 7.6.0 → 7.7.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.
Files changed (152) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/button/deprecated.js +175 -35
  3. package/build/button/deprecated.js.map +1 -1
  4. package/build/button/edit.js +1 -1
  5. package/build/button/edit.js.map +1 -1
  6. package/build/button/save.js +1 -1
  7. package/build/button/save.js.map +1 -1
  8. package/build/columns/index.js +3 -1
  9. package/build/columns/index.js.map +1 -1
  10. package/build/cover/deprecated.js +212 -207
  11. package/build/cover/deprecated.js.map +1 -1
  12. package/build/cover/edit/index.js +13 -3
  13. package/build/cover/edit/index.js.map +1 -1
  14. package/build/cover/save.js +18 -5
  15. package/build/cover/save.js.map +1 -1
  16. package/build/cover/shared.js +14 -17
  17. package/build/cover/shared.js.map +1 -1
  18. package/build/file/edit.js +1 -1
  19. package/build/file/edit.js.map +1 -1
  20. package/build/file/save.js +9 -1
  21. package/build/file/save.js.map +1 -1
  22. package/build/gallery/gap-styles.js +14 -4
  23. package/build/gallery/gap-styles.js.map +1 -1
  24. package/build/gallery/index.js +1 -1
  25. package/build/image/edit.js +5 -59
  26. package/build/image/edit.js.map +1 -1
  27. package/build/image/edit.native.js +56 -3
  28. package/build/image/edit.native.js.map +1 -1
  29. package/build/image/image.js +3 -13
  30. package/build/image/image.js.map +1 -1
  31. package/build/list-item/hooks/use-split.js +17 -3
  32. package/build/list-item/hooks/use-split.js.map +1 -1
  33. package/build/navigation-link/edit.js +31 -5
  34. package/build/navigation-link/edit.js.map +1 -1
  35. package/build/post-author-name/index.js +3 -0
  36. package/build/post-author-name/index.js.map +1 -1
  37. package/build/post-author-name/transforms.js +41 -0
  38. package/build/post-author-name/transforms.js.map +1 -0
  39. package/build/post-comments-form/form.js +11 -1
  40. package/build/post-comments-form/form.js.map +1 -1
  41. package/build/post-date/edit.js +13 -7
  42. package/build/post-date/edit.js.map +1 -1
  43. package/build/search/edit.js +1 -1
  44. package/build/search/edit.js.map +1 -1
  45. package/build/search/index.js +4 -0
  46. package/build/search/index.js.map +1 -1
  47. package/build/shortcode/edit.native.js +16 -13
  48. package/build/shortcode/edit.native.js.map +1 -1
  49. package/build/social-links/edit.js +2 -2
  50. package/build/social-links/edit.js.map +1 -1
  51. package/build/table-of-contents/edit.js +11 -3
  52. package/build/table-of-contents/edit.js.map +1 -1
  53. package/build-module/button/deprecated.js +175 -35
  54. package/build-module/button/deprecated.js.map +1 -1
  55. package/build-module/button/edit.js +2 -2
  56. package/build-module/button/edit.js.map +1 -1
  57. package/build-module/button/save.js +2 -2
  58. package/build-module/button/save.js.map +1 -1
  59. package/build-module/columns/index.js +3 -1
  60. package/build-module/columns/index.js.map +1 -1
  61. package/build-module/cover/deprecated.js +206 -198
  62. package/build-module/cover/deprecated.js.map +1 -1
  63. package/build-module/cover/edit/index.js +14 -4
  64. package/build-module/cover/edit/index.js.map +1 -1
  65. package/build-module/cover/save.js +19 -6
  66. package/build-module/cover/save.js.map +1 -1
  67. package/build-module/cover/shared.js +11 -12
  68. package/build-module/cover/shared.js.map +1 -1
  69. package/build-module/file/edit.js +2 -2
  70. package/build-module/file/edit.js.map +1 -1
  71. package/build-module/file/save.js +7 -2
  72. package/build-module/file/save.js.map +1 -1
  73. package/build-module/gallery/gap-styles.js +14 -4
  74. package/build-module/gallery/gap-styles.js.map +1 -1
  75. package/build-module/gallery/index.js +1 -1
  76. package/build-module/image/edit.js +5 -56
  77. package/build-module/image/edit.js.map +1 -1
  78. package/build-module/image/edit.native.js +57 -5
  79. package/build-module/image/edit.native.js.map +1 -1
  80. package/build-module/image/image.js +4 -14
  81. package/build-module/image/image.js.map +1 -1
  82. package/build-module/list-item/hooks/use-split.js +19 -5
  83. package/build-module/list-item/hooks/use-split.js.map +1 -1
  84. package/build-module/navigation-link/edit.js +31 -6
  85. package/build-module/navigation-link/edit.js.map +1 -1
  86. package/build-module/post-author-name/index.js +2 -0
  87. package/build-module/post-author-name/index.js.map +1 -1
  88. package/build-module/post-author-name/transforms.js +32 -0
  89. package/build-module/post-author-name/transforms.js.map +1 -0
  90. package/build-module/post-comments-form/form.js +7 -1
  91. package/build-module/post-comments-form/form.js.map +1 -1
  92. package/build-module/post-date/edit.js +15 -9
  93. package/build-module/post-date/edit.js.map +1 -1
  94. package/build-module/search/edit.js +2 -2
  95. package/build-module/search/edit.js.map +1 -1
  96. package/build-module/search/index.js +4 -0
  97. package/build-module/search/index.js.map +1 -1
  98. package/build-module/shortcode/edit.native.js +17 -13
  99. package/build-module/shortcode/edit.native.js.map +1 -1
  100. package/build-module/social-links/edit.js +2 -2
  101. package/build-module/social-links/edit.js.map +1 -1
  102. package/build-module/table-of-contents/edit.js +12 -4
  103. package/build-module/table-of-contents/edit.js.map +1 -1
  104. package/build-style/cover/style-rtl.css +32 -23
  105. package/build-style/cover/style.css +32 -23
  106. package/build-style/site-logo/style-rtl.css +4 -0
  107. package/build-style/site-logo/style.css +4 -0
  108. package/build-style/style-rtl.css +36 -23
  109. package/build-style/style.css +36 -23
  110. package/package.json +28 -28
  111. package/src/button/deprecated.js +145 -0
  112. package/src/button/edit.js +3 -1
  113. package/src/button/save.js +3 -1
  114. package/src/buttons/test/__snapshots__/edit.native.js.snap +1 -1
  115. package/src/buttons/test/edit.native.js +1 -1
  116. package/src/columns/block.json +3 -1
  117. package/src/cover/deprecated.js +230 -183
  118. package/src/cover/edit/index.js +25 -13
  119. package/src/cover/save.js +26 -13
  120. package/src/cover/shared.js +5 -7
  121. package/src/cover/style.scss +27 -22
  122. package/src/file/edit.js +5 -1
  123. package/src/file/save.js +14 -2
  124. package/src/gallery/block.json +1 -1
  125. package/src/gallery/gap-styles.js +21 -6
  126. package/src/gallery/index.php +23 -12
  127. package/src/image/edit.js +1 -53
  128. package/src/image/edit.native.js +65 -3
  129. package/src/image/image.js +2 -11
  130. package/src/image/styles.native.scss +11 -0
  131. package/src/list-item/hooks/use-split.js +18 -9
  132. package/src/navigation-link/edit.js +35 -5
  133. package/src/navigation-link/test/edit.js +0 -20
  134. package/src/post-author/index.php +3 -2
  135. package/src/post-author-name/index.js +2 -0
  136. package/src/post-author-name/index.php +1 -1
  137. package/src/post-author-name/transforms.js +25 -0
  138. package/src/post-comments/index.php +1 -1
  139. package/src/post-comments-form/form.js +11 -1
  140. package/src/post-comments-form/index.php +1 -1
  141. package/src/post-date/edit.js +4 -3
  142. package/src/query-pagination/index.php +0 -1
  143. package/src/search/block.json +4 -0
  144. package/src/search/edit.js +3 -1
  145. package/src/search/index.php +28 -15
  146. package/src/shortcode/edit.native.js +29 -15
  147. package/src/shortcode/style.native.scss +11 -4
  148. package/src/shortcode/test/__snapshots__/edit.native.js.snap +9 -0
  149. package/src/shortcode/test/edit.native.js +58 -40
  150. package/src/site-logo/style.scss +6 -0
  151. package/src/social-links/edit.js +11 -10
  152. package/src/table-of-contents/edit.js +19 -6
package/src/file/save.js CHANGED
@@ -1,7 +1,16 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import classnames from 'classnames';
5
+
1
6
  /**
2
7
  * WordPress dependencies
3
8
  */
4
- import { RichText, useBlockProps } from '@wordpress/block-editor';
9
+ import {
10
+ RichText,
11
+ useBlockProps,
12
+ __experimentalElementButtonClassName,
13
+ } from '@wordpress/block-editor';
5
14
  import { __, sprintf } from '@wordpress/i18n';
6
15
 
7
16
  export default function save( { attributes } ) {
@@ -63,7 +72,10 @@ export default function save( { attributes } ) {
63
72
  { showDownloadButton && (
64
73
  <a
65
74
  href={ href }
66
- className="wp-block-file__button"
75
+ className={ classnames(
76
+ 'wp-block-file__button',
77
+ __experimentalElementButtonClassName
78
+ ) }
67
79
  download={ true }
68
80
  aria-describedby={ describedById }
69
81
  >
@@ -110,7 +110,7 @@
110
110
  "html": false,
111
111
  "units": [ "px", "em", "rem", "vh", "vw" ],
112
112
  "spacing": {
113
- "blockGap": true,
113
+ "blockGap": [ "horizontal", "vertical" ],
114
114
  "__experimentalSkipSerialization": [ "blockGap" ],
115
115
  "__experimentalDefaultControls": {
116
116
  "blockGap": true
@@ -8,12 +8,27 @@ export default function GapStyles( { blockGap, clientId } ) {
8
8
  const styleElement = useContext( BlockList.__unstableElementContext );
9
9
  // --gallery-block--gutter-size is deprecated. --wp--style--gallery-gap-default should be used by themes that want to set a default
10
10
  // gap on the gallery.
11
- const gapValue = blockGap
12
- ? blockGap
13
- : `var( --wp--style--gallery-gap-default, var( --gallery-block--gutter-size, var( --wp--style--block-gap, 0.5em ) ) )`;
14
- const gap = `#block-${ clientId } {
15
- --wp--style--unstable-gallery-gap: ${ gapValue };
16
- gap: ${ gapValue }
11
+ const fallbackValue = `var( --wp--style--gallery-gap-default, var( --gallery-block--gutter-size, var( --wp--style--block-gap, 0.5em ) ) )`;
12
+ let gapValue = fallbackValue;
13
+ let column = fallbackValue;
14
+ let row;
15
+
16
+ // Check for the possibility of split block gap values. See: https://github.com/WordPress/gutenberg/pull/37736
17
+ if ( !! blockGap ) {
18
+ row =
19
+ typeof blockGap === 'string'
20
+ ? blockGap
21
+ : blockGap?.top || fallbackValue;
22
+ column =
23
+ typeof blockGap === 'string'
24
+ ? blockGap
25
+ : blockGap?.left || fallbackValue;
26
+ gapValue = row === column ? row : `${ row } ${ column }`;
27
+ }
28
+
29
+ const gap = `#block-${ clientId } {
30
+ --wp--style--unstable-gallery-gap: ${ column };
31
+ gap: ${ gapValue }
17
32
  }`;
18
33
 
19
34
  const GapStyle = () => {
@@ -48,7 +48,14 @@ function block_core_gallery_render( $attributes, $content ) {
48
48
  // Skip if gap value contains unsupported characters.
49
49
  // Regex for CSS value borrowed from `safecss_filter_attr`, and used here
50
50
  // because we only want to match against the value, not the CSS attribute.
51
- $gap = preg_match( '%[\\\(&=}]|/\*%', $gap ) ? null : $gap;
51
+ if ( is_array( $gap ) ) {
52
+ foreach ( $gap as $key => $value ) {
53
+ $gap[ $key ] = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value;
54
+ }
55
+ } else {
56
+ $gap = $gap && preg_match( '%[\\\(&=}]|/\*%', $gap ) ? null : $gap;
57
+ }
58
+
52
59
  $class = wp_unique_id( 'wp-block-gallery-' );
53
60
  $content = preg_replace(
54
61
  '/' . preg_quote( 'class="', '/' ) . '/',
@@ -56,19 +63,23 @@ function block_core_gallery_render( $attributes, $content ) {
56
63
  $content,
57
64
  1
58
65
  );
66
+
59
67
  // --gallery-block--gutter-size is deprecated. --wp--style--gallery-gap-default should be used by themes that want to set a default
60
68
  // gap on the gallery.
61
- $gap_value = $gap ? $gap : 'var( --wp--style--gallery-gap-default, var( --gallery-block--gutter-size, var( --wp--style--block-gap, 0.5em ) ) )';
62
- $style = '.' . $class . '{ --wp--style--unstable-gallery-gap: ' . $gap_value . '; gap: ' . $gap_value . '}';
63
- // Ideally styles should be loaded in the head, but blocks may be parsed
64
- // after that, so loading in the footer for now.
65
- // See https://core.trac.wordpress.org/ticket/53494.
66
- add_action(
67
- 'wp_footer',
68
- function () use ( $style ) {
69
- echo '<style> ' . $style . '</style>';
70
- }
71
- );
69
+ $fallback_gap = 'var( --wp--style--gallery-gap-default, var( --gallery-block--gutter-size, var( --wp--style--block-gap, 0.5em ) ) )';
70
+ $gap_value = $gap ? $gap : $fallback_gap;
71
+ $gap_column = $gap_value;
72
+
73
+ if ( is_array( $gap_value ) ) {
74
+ $gap_row = isset( $gap_value['top'] ) ? $gap_value['top'] : $fallback_gap;
75
+ $gap_column = isset( $gap_value['left'] ) ? $gap_value['left'] : $fallback_gap;
76
+ $gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column;
77
+ }
78
+
79
+ // Set the CSS variable to the column value, and the `gap` property to the combined gap value.
80
+ $style = '.' . $class . '{ --wp--style--unstable-gallery-gap: ' . $gap_column . '; gap: ' . $gap_value . '}';
81
+
82
+ gutenberg_enqueue_block_support_styles( $style, 11 );
72
83
  return $content;
73
84
  }
74
85
  /**
package/src/image/edit.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  store as blockEditorStore,
20
20
  } from '@wordpress/block-editor';
21
21
  import { useEffect, useRef, useState } from '@wordpress/element';
22
- import { __, sprintf } from '@wordpress/i18n';
22
+ import { __ } from '@wordpress/i18n';
23
23
  import { image as icon } from '@wordpress/icons';
24
24
 
25
25
  /**
@@ -85,20 +85,6 @@ function hasDefaultSize( image, defaultSize ) {
85
85
  );
86
86
  }
87
87
 
88
- /**
89
- * Checks if a media attachment object has been "destroyed",
90
- * that is, removed from the media library. The core Media Library
91
- * adds a `destroyed` property to a deleted attachment object in the media collection.
92
- *
93
- * @param {number} id The attachment id.
94
- *
95
- * @return {boolean} Whether the image has been destroyed.
96
- */
97
- export function isMediaDestroyed( id ) {
98
- const attachment = window?.wp?.media?.attachment( id ) || {};
99
- return attachment.destroyed;
100
- }
101
-
102
88
  export function ImageEdit( {
103
89
  attributes,
104
90
  setAttributes,
@@ -139,41 +125,6 @@ export function ImageEdit( {
139
125
  return pick( getSettings(), [ 'imageDefaultSize', 'mediaUpload' ] );
140
126
  }, [] );
141
127
 
142
- // A callback passed to MediaUpload,
143
- // fired when the media modal closes.
144
- function onCloseModal() {
145
- if ( isMediaDestroyed( attributes?.id ) ) {
146
- setAttributes( {
147
- url: undefined,
148
- id: undefined,
149
- } );
150
- }
151
- }
152
-
153
- /*
154
- Runs an error callback if the image does not load.
155
- If the error callback is triggered, we infer that that image
156
- has been deleted.
157
- */
158
- function onImageError( isReplaced = false ) {
159
- noticeOperations.removeAllNotices();
160
- noticeOperations.createErrorNotice(
161
- sprintf(
162
- /* translators: %s url or missing image */
163
- __( 'Error loading image: %s' ),
164
- url
165
- )
166
- );
167
- // If the image block was not replaced with an embed,
168
- // clear the attributes and trigger the placeholder.
169
- if ( ! isReplaced ) {
170
- setAttributes( {
171
- url: undefined,
172
- id: undefined,
173
- } );
174
- }
175
- }
176
-
177
128
  function onUploadError( message ) {
178
129
  noticeOperations.removeAllNotices();
179
130
  noticeOperations.createErrorNotice( message );
@@ -372,8 +323,6 @@ export function ImageEdit( {
372
323
  containerRef={ ref }
373
324
  context={ context }
374
325
  clientId={ clientId }
375
- onCloseModal={ onCloseModal }
376
- onImageLoadError={ onImageError }
377
326
  />
378
327
  ) }
379
328
  { ! url && (
@@ -390,7 +339,6 @@ export function ImageEdit( {
390
339
  onSelectURL={ onSelectURL }
391
340
  notices={ noticeUI }
392
341
  onError={ onUploadError }
393
- onClose={ onCloseModal }
394
342
  accept="image/*"
395
343
  allowedTypes={ ALLOWED_MEDIA_TYPES }
396
344
  value={ { id, src } }
@@ -1,7 +1,12 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { View, TouchableWithoutFeedback } from 'react-native';
4
+ import {
5
+ ActivityIndicator,
6
+ Image as RNImage,
7
+ TouchableWithoutFeedback,
8
+ View,
9
+ } from 'react-native';
5
10
  import { useRoute } from '@react-navigation/native';
6
11
 
7
12
  /**
@@ -45,7 +50,7 @@ import {
45
50
  blockSettingsScreens,
46
51
  } from '@wordpress/block-editor';
47
52
  import { __, _x, sprintf } from '@wordpress/i18n';
48
- import { getProtocol, hasQueryArg } from '@wordpress/url';
53
+ import { getProtocol, hasQueryArg, isURL } from '@wordpress/url';
49
54
  import { doAction, hasAction } from '@wordpress/hooks';
50
55
  import { compose, withPreferredColorScheme } from '@wordpress/compose';
51
56
  import { withSelect, withDispatch } from '@wordpress/data';
@@ -57,6 +62,7 @@ import {
57
62
  } from '@wordpress/icons';
58
63
  import { store as coreStore } from '@wordpress/core-data';
59
64
  import { store as editPostStore } from '@wordpress/edit-post';
65
+ import { store as noticesStore } from '@wordpress/notices';
60
66
 
61
67
  /**
62
68
  * Internal dependencies
@@ -207,6 +213,7 @@ export class ImageEdit extends Component {
207
213
  this.onImagePressed = this.onImagePressed.bind( this );
208
214
  this.onSetFeatured = this.onSetFeatured.bind( this );
209
215
  this.onFocusCaption = this.onFocusCaption.bind( this );
216
+ this.onSelectURL = this.onSelectURL.bind( this );
210
217
  this.updateAlignment = this.updateAlignment.bind( this );
211
218
  this.accessibilityLabelCreator = this.accessibilityLabelCreator.bind(
212
219
  this
@@ -461,6 +468,45 @@ export class ImageEdit extends Component {
461
468
  } );
462
469
  }
463
470
 
471
+ onSelectURL( newURL ) {
472
+ const {
473
+ createErrorNotice,
474
+ imageDefaultSize,
475
+ setAttributes,
476
+ } = this.props;
477
+
478
+ if ( isURL( newURL ) ) {
479
+ this.setState( {
480
+ isFetchingImage: true,
481
+ } );
482
+
483
+ // Use RN's Image.getSize to determine if URL is a valid image
484
+ RNImage.getSize(
485
+ newURL,
486
+ () => {
487
+ setAttributes( {
488
+ url: newURL,
489
+ id: undefined,
490
+ width: undefined,
491
+ height: undefined,
492
+ sizeSlug: imageDefaultSize,
493
+ } );
494
+ this.setState( {
495
+ isFetchingImage: false,
496
+ } );
497
+ },
498
+ () => {
499
+ createErrorNotice( __( 'Image file not found.' ) );
500
+ this.setState( {
501
+ isFetchingImage: false,
502
+ } );
503
+ }
504
+ );
505
+ } else {
506
+ createErrorNotice( __( 'Invalid URL.' ) );
507
+ }
508
+ }
509
+
464
510
  onFocusCaption() {
465
511
  if ( this.props.onFocus ) {
466
512
  this.props.onFocus();
@@ -484,6 +530,14 @@ export class ImageEdit extends Component {
484
530
  );
485
531
  }
486
532
 
533
+ showLoadingIndicator() {
534
+ return (
535
+ <View style={ styles.image__loading }>
536
+ <ActivityIndicator animating />
537
+ </View>
538
+ );
539
+ }
540
+
487
541
  getWidth() {
488
542
  const { attributes } = this.props;
489
543
  const { align, width } = attributes;
@@ -611,7 +665,7 @@ export class ImageEdit extends Component {
611
665
  }
612
666
 
613
667
  render() {
614
- const { isCaptionSelected } = this.state;
668
+ const { isCaptionSelected, isFetchingImage } = this.state;
615
669
  const {
616
670
  attributes,
617
671
  isSelected,
@@ -713,9 +767,11 @@ export class ImageEdit extends Component {
713
767
  if ( ! url ) {
714
768
  return (
715
769
  <View style={ styles.content }>
770
+ { isFetchingImage && this.showLoadingIndicator() }
716
771
  <MediaPlaceholder
717
772
  allowedTypes={ [ MEDIA_TYPE_IMAGE ] }
718
773
  onSelect={ this.onSelectMediaUploadOption }
774
+ onSelectURL={ this.onSelectURL }
719
775
  icon={ this.getPlaceholderIcon() }
720
776
  onFocus={ this.props.onFocus }
721
777
  autoOpenMediaUpload={
@@ -784,6 +840,8 @@ export class ImageEdit extends Component {
784
840
  } ) => {
785
841
  return (
786
842
  <View style={ imageContainerStyles }>
843
+ { isFetchingImage &&
844
+ this.showLoadingIndicator() }
787
845
  <Image
788
846
  align={
789
847
  align && alignToFlex[ align ]
@@ -836,6 +894,7 @@ export class ImageEdit extends Component {
836
894
  allowedTypes={ [ MEDIA_TYPE_IMAGE ] }
837
895
  isReplacingMedia={ true }
838
896
  onSelect={ this.onSelectMediaUploadOption }
897
+ onSelectURL={ this.onSelectURL }
839
898
  render={ ( { open, getMediaOptions } ) => {
840
899
  return getImageComponent( open, getMediaOptions );
841
900
  } }
@@ -881,7 +940,10 @@ export default compose( [
881
940
  };
882
941
  } ),
883
942
  withDispatch( ( dispatch ) => {
943
+ const { createErrorNotice } = dispatch( noticesStore );
944
+
884
945
  return {
946
+ createErrorNotice,
885
947
  closeSettingsBottomSheet() {
886
948
  dispatch( editPostStore ).closeGeneralSidebar();
887
949
  },
@@ -47,7 +47,7 @@ import { store as coreStore } from '@wordpress/core-data';
47
47
  */
48
48
  import { createUpgradedEmbedBlock } from '../embed/util';
49
49
  import useClientWidth from './use-client-width';
50
- import { isExternalImage, isMediaDestroyed } from './edit';
50
+ import { isExternalImage } from './edit';
51
51
 
52
52
  /**
53
53
  * Module constants
@@ -76,14 +76,12 @@ export default function Image( {
76
76
  isSelected,
77
77
  insertBlocksAfter,
78
78
  onReplace,
79
- onCloseModal,
80
79
  onSelectImage,
81
80
  onSelectURL,
82
81
  onUploadError,
83
82
  containerRef,
84
83
  context,
85
84
  clientId,
86
- onImageLoadError,
87
85
  } ) {
88
86
  const imageRef = useRef();
89
87
  const captionRef = useRef();
@@ -225,13 +223,10 @@ export default function Image( {
225
223
  // Check if there's an embed block that handles this URL, e.g., instagram URL.
226
224
  // See: https://github.com/WordPress/gutenberg/pull/11472
227
225
  const embedBlock = createUpgradedEmbedBlock( { attributes: { url } } );
228
- const shouldReplace = undefined !== embedBlock;
229
226
 
230
- if ( shouldReplace ) {
227
+ if ( undefined !== embedBlock ) {
231
228
  onReplace( embedBlock );
232
229
  }
233
-
234
- onImageLoadError( shouldReplace );
235
230
  }
236
231
 
237
232
  function onSetHref( props ) {
@@ -303,9 +298,6 @@ export default function Image( {
303
298
  if ( ! isSelected ) {
304
299
  setIsEditingImage( false );
305
300
  }
306
- if ( isSelected && isMediaDestroyed( id ) ) {
307
- onImageLoadError();
308
- }
309
301
  }, [ isSelected ] );
310
302
 
311
303
  const canEditImage = id && naturalWidth && naturalHeight && imageEditing;
@@ -369,7 +361,6 @@ export default function Image( {
369
361
  onSelect={ onSelectImage }
370
362
  onSelectURL={ onSelectURL }
371
363
  onError={ onUploadError }
372
- onCloseModal={ onCloseModal }
373
364
  />
374
365
  </BlockControls>
375
366
  ) }
@@ -57,3 +57,14 @@
57
57
  .removeFeaturedButton {
58
58
  color: $alert-red;
59
59
  }
60
+
61
+ .image__loading {
62
+ align-items: center;
63
+ background-color: rgba(10, 10, 10, 0.5);
64
+ flex: 1;
65
+ height: 100%;
66
+ justify-content: center;
67
+ position: absolute;
68
+ width: 100%;
69
+ z-index: 1;
70
+ }
@@ -1,23 +1,32 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { useCallback } from '@wordpress/element';
4
+ import { useCallback, useRef } from '@wordpress/element';
5
5
  import { useSelect } from '@wordpress/data';
6
6
  import { store as blockEditorStore } from '@wordpress/block-editor';
7
- import { cloneBlock } from '@wordpress/blocks';
7
+ import { cloneBlock, createBlock } from '@wordpress/blocks';
8
8
 
9
9
  export default function useSplit( clientId ) {
10
+ // We can not rely on the isAfterOriginal parameter of the callback,
11
+ // because if the value after the split is empty isAfterOriginal is false
12
+ // while the value is in fact after the original. So to avoid that issue we use
13
+ // a flag where the first execution of the callback is false (it is the before value)
14
+ // and the second execution is true, it is the after value.
15
+ const isAfter = useRef( false );
10
16
  const { getBlock } = useSelect( blockEditorStore );
11
17
  return useCallback(
12
- ( value, isAfterOriginal ) => {
18
+ ( value ) => {
13
19
  const block = getBlock( clientId );
14
- return cloneBlock(
15
- block,
16
- {
20
+ if ( isAfter.current ) {
21
+ return cloneBlock( block, {
17
22
  content: value,
18
- },
19
- isAfterOriginal ? [] : block.innerBlocks
20
- );
23
+ } );
24
+ }
25
+ isAfter.current = true;
26
+ return createBlock( block.name, {
27
+ ...block.attributes,
28
+ content: value,
29
+ } );
21
30
  },
22
31
  [ clientId, getBlock ]
23
32
  );
@@ -2,7 +2,7 @@
2
2
  * External dependencies
3
3
  */
4
4
  import classnames from 'classnames';
5
- import { escape } from 'lodash';
5
+ import { escape, unescape } from 'lodash';
6
6
 
7
7
  /**
8
8
  * WordPress dependencies
@@ -43,6 +43,7 @@ import {
43
43
  import { placeCaretAtHorizontalEdge } from '@wordpress/dom';
44
44
  import { link as linkIcon, addSubmenu } from '@wordpress/icons';
45
45
  import { store as coreStore } from '@wordpress/core-data';
46
+ import { decodeEntities } from '@wordpress/html-entities';
46
47
 
47
48
  /**
48
49
  * Internal dependencies
@@ -239,6 +240,15 @@ export const updateNavigationLinkBlockAttributes = (
239
240
  normalizedTitle !== normalizedURL &&
240
241
  originalLabel !== title;
241
242
 
243
+ // Unfortunately this causes the escaping model to be inverted.
244
+ // The escaped content is stored in the block attributes (and ultimately in the database),
245
+ // and then the raw data is "recovered" when outputting into the DOM.
246
+ // It would be preferable to store the **raw** data in the block attributes and escape it in JS.
247
+ // Why? Because there isn't one way to escape data. Depending on the context, you need to do
248
+ // different transforms. It doesn't make sense to me to choose one of them for the purposes of storage.
249
+ // See also:
250
+ // - https://github.com/WordPress/gutenberg/pull/41063
251
+ // - https://github.com/WordPress/gutenberg/pull/18617.
242
252
  const label = escapeTitle
243
253
  ? escape( title )
244
254
  : originalLabel || escape( normalizedURL );
@@ -606,7 +616,17 @@ export default function NavigationLinkEdit( {
606
616
  return {
607
617
  id: page.id,
608
618
  type: postType,
609
- title: page.title.rendered,
619
+ // Make `title` property consistent with that in `fetchLinkSuggestions` where the `rendered` title (containing HTML entities)
620
+ // is also being decoded. By being consistent in both locations we avoid having to branch in the rendering output code.
621
+ // Ideally in the future we will update both APIs to utilise the "raw" form of the title which is better suited to edit contexts.
622
+ // e.g.
623
+ // - title.raw = "Yes & No"
624
+ // - title.rendered = "Yes &#038; No"
625
+ // - decodeEntities( title.rendered ) = "Yes & No"
626
+ // See:
627
+ // - https://github.com/WordPress/gutenberg/pull/41063
628
+ // - https://github.com/WordPress/gutenberg/blob/a1e1fdc0e6278457e9f4fc0b31ac6d2095f5450b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js#L212-L218
629
+ title: decodeEntities( page.title.rendered ),
610
630
  url: page.link,
611
631
  kind: 'post-type',
612
632
  };
@@ -795,10 +815,20 @@ export default function NavigationLinkEdit( {
795
815
  text={ tooltipText }
796
816
  >
797
817
  <>
798
- <span>
818
+ <span
819
+ aria-label={ __(
820
+ 'Navigation link text'
821
+ ) }
822
+ >
799
823
  {
800
- /* Trim to avoid trailing white space when the placeholder text is not present */
801
- `${ label } ${ placeholderText }`.trim()
824
+ // Some attributes are stored in an escaped form. It's a legacy issue.
825
+ // Ideally they would be stored in a raw, unescaped form.
826
+ // Unescape is used here to "recover" the escaped characters
827
+ // so they display without encoding.
828
+ // See `updateNavigationLinkBlockAttributes` for more details.
829
+ `${ unescape(
830
+ label
831
+ ) } ${ placeholderText }`.trim()
802
832
  }
803
833
  </span>
804
834
  <span className="wp-block-navigation-link__missing_text-tooltip">
@@ -372,26 +372,6 @@ describe( 'edit', () => {
372
372
  url: 'https://wordpress.org',
373
373
  } );
374
374
  } );
375
- // https://github.com/WordPress/gutenberg/pull/18617
376
- it( 'label is javascript escaped', () => {
377
- const setAttributes = jest.fn();
378
- const linkSuggestion = {
379
- opensInNewTab: false,
380
- title: '<Navigation />',
381
- type: 'URL',
382
- url: 'https://wordpress.local?p=1',
383
- };
384
- updateNavigationLinkBlockAttributes(
385
- linkSuggestion,
386
- setAttributes
387
- );
388
- expect( setAttributes ).toHaveBeenCalledWith( {
389
- opensInNewTab: false,
390
- label: '&lt;Navigation /&gt;',
391
- kind: 'custom',
392
- url: 'https://wordpress.local?p=1',
393
- } );
394
- } );
395
375
  // https://github.com/WordPress/gutenberg/pull/19679
396
376
  it( 'url when escaped is still an actual link', () => {
397
377
  const setAttributes = jest.fn();
@@ -15,10 +15,11 @@
15
15
  */
16
16
  function render_block_core_post_author( $attributes, $content, $block ) {
17
17
  if ( ! isset( $block->context['postId'] ) ) {
18
- return '';
18
+ $author_id = get_query_var( 'author' );
19
+ } else {
20
+ $author_id = get_post_field( 'post_author', $block->context['postId'] );
19
21
  }
20
22
 
21
- $author_id = get_post_field( 'post_author', $block->context['postId'] );
22
23
  if ( empty( $author_id ) ) {
23
24
  return '';
24
25
  }
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import metadata from './block.json';
5
5
  import edit from './edit';
6
+ import transforms from './transforms';
6
7
 
7
8
  /**
8
9
  * WordPress dependencies
@@ -14,5 +15,6 @@ export { metadata, name };
14
15
 
15
16
  export const settings = {
16
17
  icon,
18
+ transforms,
17
19
  edit,
18
20
  };
@@ -32,7 +32,7 @@ function render_block_core_post_author_name( $attributes, $content, $block ) {
32
32
 
33
33
  $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $align_class_name ) );
34
34
 
35
- return sprintf( '<div %1$s>', $wrapper_attributes ) . $author_name . '</div>';
35
+ return sprintf( '<div %1$s>%2$s</div>', $wrapper_attributes, $author_name );
36
36
  }
37
37
 
38
38
  /**
@@ -0,0 +1,25 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { createBlock } from '@wordpress/blocks';
5
+
6
+ const transforms = {
7
+ from: [
8
+ {
9
+ type: 'block',
10
+ blocks: [ 'core/post-author' ],
11
+ transform: ( { textAlign } ) =>
12
+ createBlock( 'core/post-author-name', { textAlign } ),
13
+ },
14
+ ],
15
+ to: [
16
+ {
17
+ type: 'block',
18
+ blocks: [ 'core/post-author' ],
19
+ transform: ( { textAlign } ) =>
20
+ createBlock( 'core/post-author', { textAlign } ),
21
+ },
22
+ ],
23
+ };
24
+
25
+ export default transforms;
@@ -78,7 +78,7 @@ add_action( 'init', 'register_block_core_post_comments' );
78
78
  */
79
79
  function post_comments_block_form_defaults( $fields ) {
80
80
  if ( wp_is_block_theme() ) {
81
- $fields['submit_button'] = '<input name="%1$s" type="submit" id="%2$s" class="%3$s wp-block-button__link" value="%4$s" />';
81
+ $fields['submit_button'] = '<input name="%1$s" type="submit" id="%2$s" class="%3$s wp-block-button__link ' . WP_Theme_JSON_Gutenberg::__EXPERIMENTAL_ELEMENT_BUTTON_CLASS_NAME . '" value="%4$s" />';
82
82
  $fields['submit_field'] = '<p class="form-submit wp-block-button">%1$s %2$s</p>';
83
83
  }
84
84