@wordpress/block-library 8.12.12 → 8.12.14

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.
@@ -12,16 +12,24 @@ import { insertObject } from '@wordpress/rich-text';
12
12
  import {
13
13
  RichTextToolbarButton,
14
14
  store as blockEditorStore,
15
+ privateApis,
15
16
  } from '@wordpress/block-editor';
16
17
  import { useSelect, useDispatch, useRegistry } from '@wordpress/data';
17
- import { createBlock } from '@wordpress/blocks';
18
+ import { createBlock, store as blocksStore } from '@wordpress/blocks';
18
19
 
19
20
  /**
20
21
  * Internal dependencies
21
22
  */
22
23
  import { name } from './block.json';
24
+ import { unlock } from '../lock-unlock';
25
+
26
+ const { usesContextKey } = unlock( privateApis );
23
27
 
24
28
  export const formatName = 'core/footnote';
29
+
30
+ const POST_CONTENT_BLOCK_NAME = 'core/post-content';
31
+ const SYNCED_PATTERN_BLOCK_NAME = 'core/block';
32
+
25
33
  export const format = {
26
34
  title: __( 'Footnote' ),
27
35
  tagName: 'sup',
@@ -30,17 +38,56 @@ export const format = {
30
38
  'data-fn': 'data-fn',
31
39
  },
32
40
  contentEditable: false,
33
- edit: function Edit( { value, onChange, isObjectActive } ) {
41
+ [ usesContextKey ]: [ 'postType' ],
42
+ edit: function Edit( {
43
+ value,
44
+ onChange,
45
+ isObjectActive,
46
+ context: { postType },
47
+ } ) {
34
48
  const registry = useRegistry();
35
49
  const {
36
50
  getSelectedBlockClientId,
51
+ getBlocks,
37
52
  getBlockRootClientId,
38
53
  getBlockName,
39
- getBlocks,
54
+ getBlockParentsByBlockName,
40
55
  } = useSelect( blockEditorStore );
56
+ const footnotesBlockType = useSelect( ( select ) =>
57
+ select( blocksStore ).getBlockType( name )
58
+ );
59
+ /*
60
+ * This useSelect exists because we need to use its return value
61
+ * outside the event callback.
62
+ */
63
+ const isBlockWithinPattern = useSelect( ( select ) => {
64
+ const {
65
+ getBlockParentsByBlockName: _getBlockParentsByBlockName,
66
+ getSelectedBlockClientId: _getSelectedBlockClientId,
67
+ } = select( blockEditorStore );
68
+ const parentCoreBlocks = _getBlockParentsByBlockName(
69
+ _getSelectedBlockClientId(),
70
+ SYNCED_PATTERN_BLOCK_NAME
71
+ );
72
+ return parentCoreBlocks && parentCoreBlocks.length > 0;
73
+ }, [] );
74
+
41
75
  const { selectionChange, insertBlock } =
42
76
  useDispatch( blockEditorStore );
43
77
 
78
+ if ( ! footnotesBlockType ) {
79
+ return null;
80
+ }
81
+
82
+ if ( postType !== 'post' && postType !== 'page' ) {
83
+ return null;
84
+ }
85
+
86
+ // Checks if the selected block lives within a pattern.
87
+ if ( isBlockWithinPattern ) {
88
+ return null;
89
+ }
90
+
44
91
  function onClick() {
45
92
  registry.batch( () => {
46
93
  let id;
@@ -65,10 +112,27 @@ export const format = {
65
112
  onChange( newValue );
66
113
  }
67
114
 
115
+ const selectedClientId = getSelectedBlockClientId();
116
+
117
+ /*
118
+ * Attempts to find a common parent post content block.
119
+ * This allows for locating blocks within a page edited in the site editor.
120
+ */
121
+ const parentPostContent = getBlockParentsByBlockName(
122
+ selectedClientId,
123
+ POST_CONTENT_BLOCK_NAME
124
+ );
125
+
126
+ // When called with a post content block, getBlocks will return
127
+ // the block with controlled inner blocks included.
128
+ const blocks = parentPostContent.length
129
+ ? getBlocks( parentPostContent[ 0 ] )
130
+ : getBlocks();
131
+
68
132
  // BFS search to find the first footnote block.
69
133
  let fnBlock = null;
70
134
  {
71
- const queue = [ ...getBlocks() ];
135
+ const queue = [ ...blocks ];
72
136
  while ( queue.length ) {
73
137
  const block = queue.shift();
74
138
  if ( block.name === name ) {
@@ -83,12 +147,11 @@ export const format = {
83
147
  // When there is no footnotes block in the post, create one and
84
148
  // insert it at the bottom.
85
149
  if ( ! fnBlock ) {
86
- const clientId = getSelectedBlockClientId();
87
- let rootClientId = getBlockRootClientId( clientId );
150
+ let rootClientId = getBlockRootClientId( selectedClientId );
88
151
 
89
152
  while (
90
153
  rootClientId &&
91
- getBlockName( rootClientId ) !== 'core/post-content'
154
+ getBlockName( rootClientId ) !== POST_CONTENT_BLOCK_NAME
92
155
  ) {
93
156
  rootClientId = getBlockRootClientId( rootClientId );
94
157
  }
@@ -21,7 +21,6 @@ export const settings = {
21
21
  edit,
22
22
  };
23
23
 
24
- // Would be good to remove the format and HoR if the block is unregistered.
25
24
  registerFormatType( formatName, format );
26
25
 
27
26
  export const init = () => {
@@ -211,4 +211,77 @@ add_filter( '_wp_post_revision_fields', 'wp_add_footnotes_to_revision' );
211
211
  function wp_get_footnotes_from_revision( $revision_field, $field, $revision ) {
212
212
  return get_metadata( 'post', $revision->ID, $field, true );
213
213
  }
214
- add_filter( 'wp_post_revision_field_footnotes', 'wp_get_footnotes_from_revision', 10, 3 );
214
+ add_filter( '_wp_post_revision_field_footnotes', 'wp_get_footnotes_from_revision', 10, 3 );
215
+
216
+ /**
217
+ * The REST API autosave endpoint doesn't save meta, so we can use the
218
+ * `wp_creating_autosave` when it updates an exiting autosave, and
219
+ * `_wp_put_post_revision` when it creates a new autosave.
220
+ *
221
+ * @since 6.3.0
222
+ *
223
+ * @param int|array $autosave The autosave ID or array.
224
+ */
225
+ function _wp_rest_api_autosave_meta( $autosave ) {
226
+ // Ensure it's a REST API request.
227
+ if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) {
228
+ return;
229
+ }
230
+
231
+ $body = rest_get_server()->get_raw_data();
232
+ $body = json_decode( $body, true );
233
+
234
+ if ( ! isset( $body['meta']['footnotes'] ) ) {
235
+ return;
236
+ }
237
+
238
+ // `wp_creating_autosave` passes the array,
239
+ // `_wp_put_post_revision` passes the ID.
240
+ $id = is_int( $autosave ) ? $autosave : $autosave['ID'];
241
+
242
+ if ( ! $id ) {
243
+ return;
244
+ }
245
+
246
+ update_post_meta( $id, 'footnotes', $body['meta']['footnotes'] );
247
+ }
248
+ // See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L391C1-L391C1.
249
+ add_action( 'wp_creating_autosave', '_wp_rest_api_autosave_meta' );
250
+ // See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L398.
251
+ // Then https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/revision.php#L367.
252
+ add_action( '_wp_put_post_revision', '_wp_rest_api_autosave_meta' );
253
+
254
+ /**
255
+ * This is a workaround for the autosave endpoint returning early if the
256
+ * revision field are equal. The problem is that "footnotes" is not real
257
+ * revision post field, so there's nothing to compare against.
258
+ *
259
+ * This trick sets the "footnotes" field (value doesn't matter), which will
260
+ * cause the autosave endpoint to always update the latest revision. That should
261
+ * be fine, it should be ok to update the revision even if nothing changed. Of
262
+ * course, this is temporary fix.
263
+ *
264
+ * @since 6.3.0
265
+ *
266
+ * @param WP_Post $prepared_post The prepared post object.
267
+ * @param WP_REST_Request $request The request object.
268
+ *
269
+ * See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L365-L384.
270
+ * See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L219.
271
+ */
272
+ function _wp_rest_api_force_autosave_difference( $prepared_post, $request ) {
273
+ // We only want to be altering POST requests.
274
+ if ( $request->get_method() !== 'POST' ) {
275
+ return $prepared_post;
276
+ }
277
+
278
+ // Only alter requests for the '/autosaves' route.
279
+ if ( substr( $request->get_route(), -strlen( '/autosaves' ) ) !== '/autosaves' ) {
280
+ return $prepared_post;
281
+ }
282
+
283
+ $prepared_post->footnotes = '[]';
284
+ return $prepared_post;
285
+ }
286
+
287
+ add_filter( 'rest_pre_insert_post', '_wp_rest_api_force_autosave_difference', 10, 2 );
@@ -566,9 +566,9 @@ export default function Image( {
566
566
  className={ borderProps.className }
567
567
  style={ {
568
568
  width:
569
- ( width && height ) || aspectRatio ? '100%' : 'inherit',
569
+ ( width && height ) || aspectRatio ? '100%' : undefined,
570
570
  height:
571
- ( width && height ) || aspectRatio ? '100%' : 'inherit',
571
+ ( width && height ) || aspectRatio ? '100%' : undefined,
572
572
  objectFit: scale,
573
573
  ...borderProps.style,
574
574
  } }
@@ -9,6 +9,11 @@ import {
9
9
  useBlockProps,
10
10
  } from '@wordpress/block-editor';
11
11
 
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ import { unlock } from '../lock-unlock';
16
+
12
17
  const PatternEdit = ( { attributes, clientId } ) => {
13
18
  const selectedPattern = useSelect(
14
19
  ( select ) =>
@@ -20,6 +25,10 @@ const PatternEdit = ( { attributes, clientId } ) => {
20
25
 
21
26
  const { replaceBlocks, __unstableMarkNextChangeAsNotPersistent } =
22
27
  useDispatch( blockEditorStore );
28
+ const { setBlockEditingMode } = unlock( useDispatch( blockEditorStore ) );
29
+ const { getBlockRootClientId, getBlockEditingMode } = unlock(
30
+ useSelect( blockEditorStore )
31
+ );
23
32
 
24
33
  // Run this effect when the component loads.
25
34
  // This adds the Pattern's contents to the post.
@@ -33,13 +42,22 @@ const PatternEdit = ( { attributes, clientId } ) => {
33
42
  // because nested pattern blocks cannot be inserted if the parent block supports
34
43
  // inner blocks but doesn't have blockSettings in the state.
35
44
  window.queueMicrotask( () => {
45
+ const rootClientId = getBlockRootClientId( clientId );
36
46
  // Clone blocks from the pattern before insertion to ensure they receive
37
47
  // distinct client ids. See https://github.com/WordPress/gutenberg/issues/50628.
38
48
  const clonedBlocks = selectedPattern.blocks.map( ( block ) =>
39
49
  cloneBlock( block )
40
50
  );
51
+ const rootEditingMode = getBlockEditingMode( rootClientId );
52
+ // Temporarily set the root block to default mode to allow replacing the pattern.
53
+ // This could happen when the page is disabling edits of non-content blocks.
54
+ __unstableMarkNextChangeAsNotPersistent();
55
+ setBlockEditingMode( rootClientId, 'default' );
41
56
  __unstableMarkNextChangeAsNotPersistent();
42
57
  replaceBlocks( clientId, clonedBlocks );
58
+ // Restore the root block's original mode.
59
+ __unstableMarkNextChangeAsNotPersistent();
60
+ setBlockEditingMode( rootClientId, rootEditingMode );
43
61
  } );
44
62
  }
45
63
  }, [
@@ -47,6 +65,9 @@ const PatternEdit = ( { attributes, clientId } ) => {
47
65
  selectedPattern?.blocks,
48
66
  __unstableMarkNextChangeAsNotPersistent,
49
67
  replaceBlocks,
68
+ getBlockEditingMode,
69
+ setBlockEditingMode,
70
+ getBlockRootClientId,
50
71
  ] );
51
72
 
52
73
  const props = useBlockProps();
@@ -250,7 +250,7 @@ function build_template_part_block_instance_variations() {
250
250
  'area' => $template_part->area,
251
251
  ),
252
252
  'scope' => array( 'inserter' ),
253
- 'icon' => $icon_by_area[ $template_part->area ],
253
+ 'icon' => isset( $icon_by_area[ $template_part->area ] ) ? $icon_by_area[ $template_part->area ] : null,
254
254
  'example' => array(
255
255
  'attributes' => array(
256
256
  'slug' => $template_part->slug,