@wordpress/block-library 9.40.2-next.v.202602241322.0 → 9.41.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 (153) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/accordion/view.cjs +0 -34
  3. package/build/accordion/view.cjs.map +2 -2
  4. package/build/button/block.json +11 -3
  5. package/build/button/deprecated.cjs +246 -13
  6. package/build/button/deprecated.cjs.map +2 -2
  7. package/build/button/edit.cjs +45 -58
  8. package/build/button/edit.cjs.map +3 -3
  9. package/build/button/save.cjs +3 -7
  10. package/build/button/save.cjs.map +2 -2
  11. package/build/button/utils.cjs +59 -0
  12. package/build/button/utils.cjs.map +7 -0
  13. package/build/image/image.cjs +1 -1
  14. package/build/image/image.cjs.map +2 -2
  15. package/build/navigation/edit/index.cjs +4 -2
  16. package/build/navigation/edit/index.cjs.map +3 -3
  17. package/build/navigation/edit/leaf-more-menu.cjs +68 -6
  18. package/build/navigation/edit/leaf-more-menu.cjs.map +3 -3
  19. package/build/navigation/edit/menu-inspector-controls.cjs +20 -91
  20. package/build/navigation/edit/menu-inspector-controls.cjs.map +3 -3
  21. package/build/navigation/edit/navigation-link-ui.cjs +97 -0
  22. package/build/navigation/edit/navigation-link-ui.cjs.map +7 -0
  23. package/build/navigation/edit/navigation-list-view-header.cjs +86 -0
  24. package/build/navigation/edit/navigation-list-view-header.cjs.map +7 -0
  25. package/build/navigation/edit/navigation-menu-selector.cjs +4 -2
  26. package/build/navigation/edit/navigation-menu-selector.cjs.map +3 -3
  27. package/build/navigation/edit/placeholder/index.cjs +2 -2
  28. package/build/navigation/edit/placeholder/index.cjs.map +3 -3
  29. package/build/navigation-link/shared/controls.cjs +29 -52
  30. package/build/navigation-link/shared/controls.cjs.map +3 -3
  31. package/build/navigation-link/shared/use-link-preview.cjs +8 -9
  32. package/build/navigation-link/shared/use-link-preview.cjs.map +2 -2
  33. package/build/page-list-item/edit.cjs +6 -3
  34. package/build/page-list-item/edit.cjs.map +2 -2
  35. package/build/playlist/edit.cjs +43 -136
  36. package/build/playlist/edit.cjs.map +3 -3
  37. package/build/playlist/view.cjs +56 -38
  38. package/build/playlist/view.cjs.map +2 -2
  39. package/build/playlist-track/edit.cjs +0 -1
  40. package/build/playlist-track/edit.cjs.map +2 -2
  41. package/build/post-title/block.json +3 -0
  42. package/build/post-title/edit.cjs +2 -2
  43. package/build/post-title/edit.cjs.map +2 -2
  44. package/build/utils/waveform-player.cjs +68 -0
  45. package/build/utils/waveform-player.cjs.map +7 -0
  46. package/build/utils/waveform-utils.cjs +171 -0
  47. package/build/utils/waveform-utils.cjs.map +7 -0
  48. package/build-module/accordion/view.mjs +1 -35
  49. package/build-module/accordion/view.mjs.map +2 -2
  50. package/build-module/button/block.json +11 -3
  51. package/build-module/button/deprecated.mjs +246 -13
  52. package/build-module/button/deprecated.mjs.map +2 -2
  53. package/build-module/button/edit.mjs +47 -63
  54. package/build-module/button/edit.mjs.map +2 -2
  55. package/build-module/button/save.mjs +3 -7
  56. package/build-module/button/save.mjs.map +2 -2
  57. package/build-module/button/utils.mjs +33 -0
  58. package/build-module/button/utils.mjs.map +7 -0
  59. package/build-module/image/image.mjs +1 -1
  60. package/build-module/image/image.mjs.map +2 -2
  61. package/build-module/navigation/edit/index.mjs +4 -2
  62. package/build-module/navigation/edit/index.mjs.map +2 -2
  63. package/build-module/navigation/edit/leaf-more-menu.mjs +73 -7
  64. package/build-module/navigation/edit/leaf-more-menu.mjs.map +2 -2
  65. package/build-module/navigation/edit/menu-inspector-controls.mjs +21 -101
  66. package/build-module/navigation/edit/menu-inspector-controls.mjs.map +2 -2
  67. package/build-module/navigation/edit/navigation-link-ui.mjs +76 -0
  68. package/build-module/navigation/edit/navigation-link-ui.mjs.map +7 -0
  69. package/build-module/navigation/edit/navigation-list-view-header.mjs +58 -0
  70. package/build-module/navigation/edit/navigation-list-view-header.mjs.map +7 -0
  71. package/build-module/navigation/edit/navigation-menu-selector.mjs +5 -3
  72. package/build-module/navigation/edit/navigation-menu-selector.mjs.map +2 -2
  73. package/build-module/navigation/edit/placeholder/index.mjs +2 -2
  74. package/build-module/navigation/edit/placeholder/index.mjs.map +2 -2
  75. package/build-module/navigation-link/shared/controls.mjs +29 -53
  76. package/build-module/navigation-link/shared/controls.mjs.map +2 -2
  77. package/build-module/navigation-link/shared/use-link-preview.mjs +8 -9
  78. package/build-module/navigation-link/shared/use-link-preview.mjs.map +2 -2
  79. package/build-module/page-list-item/edit.mjs +6 -3
  80. package/build-module/page-list-item/edit.mjs.map +2 -2
  81. package/build-module/playlist/edit.mjs +41 -139
  82. package/build-module/playlist/edit.mjs.map +2 -2
  83. package/build-module/playlist/view.mjs +56 -38
  84. package/build-module/playlist/view.mjs.map +2 -2
  85. package/build-module/playlist-track/edit.mjs +0 -1
  86. package/build-module/playlist-track/edit.mjs.map +2 -2
  87. package/build-module/post-title/block.json +3 -0
  88. package/build-module/post-title/edit.mjs +2 -2
  89. package/build-module/post-title/edit.mjs.map +2 -2
  90. package/build-module/utils/waveform-player.mjs +43 -0
  91. package/build-module/utils/waveform-player.mjs.map +7 -0
  92. package/build-module/utils/waveform-utils.mjs +131 -0
  93. package/build-module/utils/waveform-utils.mjs.map +7 -0
  94. package/build-style/button/style-rtl.css +6 -0
  95. package/build-style/button/style.css +6 -0
  96. package/build-style/editor-rtl.css +13 -3
  97. package/build-style/editor.css +13 -3
  98. package/build-style/navigation-link/editor-rtl.css +10 -0
  99. package/build-style/navigation-link/editor.css +10 -0
  100. package/build-style/playlist/editor-rtl.css +3 -3
  101. package/build-style/playlist/editor.css +3 -3
  102. package/build-style/playlist/style-rtl.css +351 -17
  103. package/build-style/playlist/style.css +351 -17
  104. package/build-style/style-rtl.css +357 -17
  105. package/build-style/style.css +357 -17
  106. package/package.json +39 -38
  107. package/src/accordion/view.js +1 -44
  108. package/src/accordion-item/index.php +0 -1
  109. package/src/button/block.json +11 -3
  110. package/src/button/deprecated.js +254 -16
  111. package/src/button/edit.js +50 -61
  112. package/src/button/index.php +68 -0
  113. package/src/button/save.js +2 -8
  114. package/src/button/style.scss +49 -7
  115. package/src/button/test/utils.js +84 -0
  116. package/src/button/utils.js +42 -0
  117. package/src/cover/index.php +4 -4
  118. package/src/image/image.js +14 -15
  119. package/src/image/index.php +3 -1
  120. package/src/navigation/edit/index.js +4 -2
  121. package/src/navigation/edit/leaf-more-menu.js +86 -11
  122. package/src/navigation/edit/menu-inspector-controls.js +23 -142
  123. package/src/navigation/edit/navigation-link-ui.js +115 -0
  124. package/src/navigation/edit/navigation-list-view-header.js +62 -0
  125. package/src/navigation/edit/navigation-menu-selector.js +5 -3
  126. package/src/navigation/edit/placeholder/index.js +3 -2
  127. package/src/navigation/edit/test/navigation-menu-selector.js +23 -20
  128. package/src/navigation-link/editor.scss +18 -0
  129. package/src/navigation-link/shared/controls.js +35 -62
  130. package/src/navigation-link/shared/test/controls.js +5 -5
  131. package/src/navigation-link/shared/test/use-link-preview.test.js +19 -1
  132. package/src/navigation-link/shared/use-link-preview.js +14 -15
  133. package/src/page-list/index.php +1 -1
  134. package/src/page-list-item/edit.js +8 -7
  135. package/src/playlist/edit.js +60 -154
  136. package/src/playlist/editor.scss +3 -3
  137. package/src/playlist/index.php +15 -40
  138. package/src/playlist/style.scss +34 -27
  139. package/src/playlist/test/edit.js +137 -0
  140. package/src/playlist/view.js +97 -40
  141. package/src/playlist-track/edit.js +0 -1
  142. package/src/post-title/block.json +3 -0
  143. package/src/post-title/edit.js +4 -2
  144. package/src/query-title/index.php +1 -1
  145. package/src/search/index.php +1 -1
  146. package/src/utils/test/waveform-utils.js +328 -0
  147. package/src/utils/waveform-player.js +77 -0
  148. package/src/utils/waveform-utils.js +232 -0
  149. package/build/navigation/use-navigation-entities.cjs +0 -67
  150. package/build/navigation/use-navigation-entities.cjs.map +0 -7
  151. package/build-module/navigation/use-navigation-entities.mjs +0 -46
  152. package/build-module/navigation/use-navigation-entities.mjs.map +0 -7
  153. package/src/navigation/use-navigation-entities.js +0 -72
@@ -7,7 +7,7 @@ import { v4 as uuid } from 'uuid';
7
7
  /**
8
8
  * WordPress dependencies
9
9
  */
10
- import { useState, useCallback, useEffect } from '@wordpress/element';
10
+ import { useCallback, useEffect } from '@wordpress/element';
11
11
  import {
12
12
  store as blockEditorStore,
13
13
  MediaPlaceholder,
@@ -23,15 +23,13 @@ import {
23
23
  ToggleControl,
24
24
  Disabled,
25
25
  SelectControl,
26
- Spinner,
27
26
  __experimentalToolsPanel as ToolsPanel,
28
27
  __experimentalToolsPanelItem as ToolsPanelItem,
29
28
  } from '@wordpress/components';
30
29
  import { useSelect, useDispatch } from '@wordpress/data';
31
30
  import { store as noticesStore } from '@wordpress/notices';
32
- import { __, _x, sprintf } from '@wordpress/i18n';
31
+ import { __ } from '@wordpress/i18n';
33
32
  import { audio as icon } from '@wordpress/icons';
34
- import { safeHTML, __unstableStripHTML as stripHTML } from '@wordpress/dom';
35
33
  import { createBlock } from '@wordpress/blocks';
36
34
 
37
35
  /**
@@ -39,99 +37,42 @@ import { createBlock } from '@wordpress/blocks';
39
37
  */
40
38
  import { Caption } from '../utils/caption';
41
39
  import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
40
+ import { WaveformPlayer } from '../utils/waveform-player';
42
41
 
43
42
  const ALLOWED_MEDIA_TYPES = [ 'audio' ];
44
43
 
45
- const CurrentTrack = ( { track, showImages, onTrackEnd } ) => {
46
- /**
47
- * dangerouslySetInnerHTML and safeHTML are used because
48
- * the media library allows using some HTML tags in the title, artist, and album fields.
49
- */
50
- const trackTitle = {
51
- dangerouslySetInnerHTML: {
52
- __html: safeHTML( track?.title ? track.title : __( 'Untitled' ) ),
53
- },
54
- };
55
- const trackArtist = {
56
- dangerouslySetInnerHTML: {
57
- __html: safeHTML(
58
- track?.artist ? track.artist : __( 'Unknown artist' )
59
- ),
60
- },
61
- };
62
- const trackAlbum = {
63
- dangerouslySetInnerHTML: {
64
- __html: safeHTML(
65
- track?.album ? track.album : __( 'Unknown album' )
66
- ),
67
- },
44
+ /**
45
+ * Transform media library data into track block attributes.
46
+ *
47
+ * @param {Object} media - Media object from the media library.
48
+ * @return {Object} Track attributes for the playlist-track block.
49
+ */
50
+ function getTrackAttributes( media ) {
51
+ return {
52
+ id: media.id || media.url, // Attachment ID or URL.
53
+ uniqueId: uuid(), // Unique ID for the track.
54
+ src: media.url,
55
+ title: media.title,
56
+ artist:
57
+ media.artist ||
58
+ media?.meta?.artist ||
59
+ media?.media_details?.artist ||
60
+ __( 'Unknown artist' ),
61
+ album:
62
+ media.album ||
63
+ media?.meta?.album ||
64
+ media?.media_details?.album ||
65
+ __( 'Unknown album' ),
66
+ length: media?.fileLength || media?.media_details?.length_formatted,
67
+ // Prevent using the default media attachment icon as the track image.
68
+ // Note: Image is not available when a new track is uploaded.
69
+ image:
70
+ media?.image?.src &&
71
+ media?.image?.src.endsWith( '/images/media/audio.svg' )
72
+ ? ''
73
+ : media?.image?.src,
68
74
  };
69
-
70
- let ariaLabel;
71
- if ( track?.title && track?.artist && track?.album ) {
72
- ariaLabel = stripHTML(
73
- sprintf(
74
- /* translators: %1$s: track title, %2$s artist name, %3$s: album name. */
75
- _x(
76
- '%1$s by %2$s from the album %3$s',
77
- 'track title, artist name, album name'
78
- ),
79
- track?.title,
80
- track?.artist,
81
- track?.album
82
- )
83
- );
84
- } else if ( track?.title ) {
85
- ariaLabel = stripHTML( track.title );
86
- } else {
87
- ariaLabel = stripHTML( __( 'Untitled' ) );
88
- }
89
-
90
- return (
91
- <>
92
- <div className="wp-block-playlist__current-item">
93
- { showImages && track?.image && (
94
- <img
95
- className="wp-block-playlist__item-image"
96
- src={ track.image }
97
- alt=""
98
- width="70px"
99
- height="70px"
100
- />
101
- ) }
102
- <div>
103
- { ! track?.title ? (
104
- <span className="wp-block-playlist__item-title">
105
- <Spinner />
106
- </span>
107
- ) : (
108
- <span
109
- className="wp-block-playlist__item-title"
110
- { ...trackTitle }
111
- />
112
- ) }
113
- <div className="wp-block-playlist__current-item-artist-album">
114
- <span
115
- className="wp-block-playlist__item-artist"
116
- { ...trackArtist }
117
- />
118
- <span
119
- className="wp-block-playlist__item-album"
120
- { ...trackAlbum }
121
- />
122
- </div>
123
- </div>
124
- </div>
125
- <audio
126
- controls="controls"
127
- src={ track?.url ? track.url : '' }
128
- onEnded={ onTrackEnd }
129
- aria-label={ ariaLabel }
130
- tabIndex={ 0 }
131
- />
132
- </>
133
- );
134
- };
75
+ }
135
76
 
136
77
  const PlaylistEdit = ( {
137
78
  attributes,
@@ -148,7 +89,6 @@ const PlaylistEdit = ( {
148
89
  showArtists,
149
90
  currentTrack,
150
91
  } = attributes;
151
- const [ trackListIndex, setTrackListIndex ] = useState( 0 );
152
92
  const blockProps = useBlockProps();
153
93
  const { replaceInnerBlocks, __unstableMarkNextChangeAsNotPersistent } =
154
94
  useDispatch( blockEditorStore );
@@ -235,33 +175,7 @@ const PlaylistEdit = ( {
235
175
  media = [ media ];
236
176
  }
237
177
 
238
- const trackAttributes = ( track ) => ( {
239
- id: track.id || track.url, // Attachment ID or URL.
240
- uniqueId: uuid(), // Unique ID for the track.
241
- src: track.url,
242
- title: track.title,
243
- artist:
244
- track.artist ||
245
- track?.meta?.artist ||
246
- track?.media_details?.artist ||
247
- __( 'Unknown artist' ),
248
- album:
249
- track.album ||
250
- track?.meta?.album ||
251
- track?.media_details?.album ||
252
- __( 'Unknown album' ),
253
- length:
254
- track?.fileLength || track?.media_details?.length_formatted,
255
- // Prevent using the default media attachment icon as the track image.
256
- // Note: Image is not available when a new track is uploaded.
257
- image:
258
- track?.image?.src &&
259
- track?.image?.src.endsWith( '/images/media/audio.svg' )
260
- ? ''
261
- : track?.image?.src,
262
- } );
263
-
264
- const trackList = media.map( trackAttributes );
178
+ const trackList = media.map( getTrackAttributes );
265
179
  __unstableMarkNextChangeAsNotPersistent();
266
180
  setAttributes( {
267
181
  currentTrack:
@@ -282,29 +196,21 @@ const PlaylistEdit = ( {
282
196
  ]
283
197
  );
284
198
 
285
- const onTrackEnd = useCallback( () => {
286
- /* If there are tracks left, play the next track */
287
- if ( trackListIndex < tracks.length - 1 ) {
288
- if ( tracks[ trackListIndex + 1 ]?.uniqueId ) {
289
- setTrackListIndex( trackListIndex + 1 );
290
- setAttributes( {
291
- currentTrack: tracks[ trackListIndex + 1 ].uniqueId,
292
- } );
293
- }
294
- } else {
295
- setTrackListIndex( 0 );
296
- if ( tracks[ 0 ].uniqueId ) {
297
- setAttributes( { currentTrack: tracks[ 0 ].uniqueId } );
298
- } else if ( tracks.length > 0 ) {
299
- const validTrack = tracks.find(
300
- ( track ) => track.uniqueId !== undefined
301
- );
302
- if ( validTrack ) {
303
- setAttributes( { currentTrack: validTrack.uniqueId } );
304
- }
305
- }
199
+ // Get current track data by finding the track with matching uniqueId.
200
+ const currentTrackData = tracks.find(
201
+ ( track ) => track.uniqueId === currentTrack
202
+ );
203
+
204
+ // Handle track end - advance to next track or loop to first.
205
+ const onTrackEnded = useCallback( () => {
206
+ const currentIndex = tracks.findIndex(
207
+ ( track ) => track.uniqueId === currentTrack
208
+ );
209
+ const nextTrack = tracks[ currentIndex + 1 ] || tracks[ 0 ];
210
+ if ( nextTrack?.uniqueId ) {
211
+ setAttributes( { currentTrack: nextTrack.uniqueId } );
306
212
  }
307
- }, [ setAttributes, trackListIndex, tracks ] );
213
+ }, [ currentTrack, tracks, setAttributes ] );
308
214
 
309
215
  const onChangeOrder = useCallback(
310
216
  ( trackOrder ) => {
@@ -317,16 +223,13 @@ const PlaylistEdit = ( {
317
223
  }
318
224
  return titleB.localeCompare( titleA );
319
225
  } );
320
- const sortedTracks = sortedBlocks.map(
321
- ( block ) => block.attributes
322
- );
226
+ const firstUniqueId = sortedBlocks[ 0 ]?.attributes?.uniqueId;
323
227
  replaceInnerBlocks( clientId, sortedBlocks );
324
228
  setAttributes( {
325
229
  order: trackOrder,
326
230
  currentTrack:
327
- sortedTracks.length > 0 &&
328
- sortedTracks[ 0 ].uniqueId !== currentTrack
329
- ? sortedTracks[ 0 ].uniqueId
231
+ firstUniqueId && firstUniqueId !== currentTrack
232
+ ? firstUniqueId
330
233
  : currentTrack,
331
234
  } );
332
235
  },
@@ -358,7 +261,7 @@ const PlaylistEdit = ( {
358
261
  renderAppender: hasAnySelected && InnerBlocks.ButtonBlockAppender,
359
262
  } );
360
263
 
361
- if ( ! tracks || ( Array.isArray( tracks ) && tracks.length === 0 ) ) {
264
+ if ( tracks.length === 0 ) {
362
265
  return (
363
266
  <div
364
267
  { ...blockProps }
@@ -498,10 +401,12 @@ const PlaylistEdit = ( {
498
401
  </InspectorControls>
499
402
  <figure { ...blockProps }>
500
403
  <Disabled isDisabled={ ! isSelected }>
501
- <CurrentTrack
502
- track={ tracks[ trackListIndex ] }
503
- showImages={ showImages }
504
- onTrackEnd={ onTrackEnd }
404
+ <WaveformPlayer
405
+ src={ currentTrackData?.src }
406
+ title={ currentTrackData?.title }
407
+ artist={ currentTrackData?.artist }
408
+ image={ currentTrackData?.image }
409
+ onEnded={ onTrackEnded }
505
410
  />
506
411
  </Disabled>
507
412
  { showTracklist && (
@@ -529,3 +434,4 @@ const PlaylistEdit = ( {
529
434
  };
530
435
 
531
436
  export default PlaylistEdit;
437
+ export { getTrackAttributes };
@@ -1,6 +1,6 @@
1
1
  .wp-block-playlist {
2
- &.is-placeholder {
3
- padding: 0;
4
- border: none;
2
+ li.block-list-appender.block-list-appender {
3
+ position: initial;
4
+ margin-top: var(--wp--preset--spacing--30, 1em);
5
5
  }
6
6
  }
@@ -58,13 +58,16 @@ function render_block_core_playlist( $attributes, $content, $block ) {
58
58
  );
59
59
  }
60
60
 
61
+ // Data is passed to wp_interactivity_state() which JSON-encodes it,
62
+ // so we use wp_strip_all_tags() instead of esc_html() to prevent
63
+ // HTML injection without double-encoding. URLs still use esc_url().
61
64
  $tracks_data[ $unique_id ] = array(
62
65
  'url' => esc_url( $url ),
63
- 'title' => esc_html( $title ),
64
- 'artist' => esc_html( $artist ),
65
- 'album' => esc_html( $album ),
66
+ 'title' => wp_strip_all_tags( $title ),
67
+ 'artist' => wp_strip_all_tags( $artist ),
68
+ 'album' => wp_strip_all_tags( $album ),
66
69
  'image' => esc_url( $image ),
67
- 'ariaLabel' => esc_attr( $aria_label ),
70
+ 'ariaLabel' => wp_strip_all_tags( $aria_label ),
68
71
  );
69
72
 
70
73
  if ( $unique_id === $current_media_id ) {
@@ -96,41 +99,14 @@ function render_block_core_playlist( $attributes, $content, $block ) {
96
99
  )
97
100
  );
98
101
 
99
- // Create the HTML for the current track which shows above the tracklist.
100
- $html = '<div class="wp-block-playlist__current-item">';
101
-
102
- // The alt attribute is intentionally left empty, as the image is decorative.
103
- if ( $attributes['showImages'] ?? false ) {
104
- $html .=
105
- '<img
106
- class="wp-block-playlist__item-image"
107
- alt=""
108
- width="70px"
109
- height="70px"
110
- data-wp-bind--src="state.currentTrack.image"
111
- data-wp-bind--hidden="!state.currentTrack.image"
112
- />';
113
- }
114
-
115
- $html .= '
116
- <div>
117
- <span class="wp-block-playlist__item-title" data-wp-text="state.currentTrack.title"></span>
118
- <div class="wp-block-playlist__current-item-artist-album">
119
- <span class="wp-block-playlist__item-artist" data-wp-text="state.currentTrack.artist"></span>
120
- <span class="wp-block-playlist__item-album" data-wp-text="state.currentTrack.album"></span>
121
- </div>
122
- </div>
123
- </div>
124
- <audio
125
- controls="controls"
126
- data-wp-on--ended="actions.nextSong"
127
- data-wp-on--play="actions.isPlaying"
128
- data-wp-on--pause="actions.isPaused"
129
- data-wp-bind--src="state.currentTrack.url"
130
- data-wp-bind--aria-label="state.currentTrack.ariaLabel"
131
- data-wp-watch="callbacks.autoPlay"
132
- ></audio>
133
- ';
102
+ // Add waveform player container with translated button labels.
103
+ $label_play = esc_attr__( 'Play' );
104
+ $label_pause = esc_attr__( 'Pause' );
105
+ $html = '<div class="wp-block-playlist__waveform-player"
106
+ data-wp-watch="callbacks.initWaveformPlayer"
107
+ data-label-play="' . $label_play . '"
108
+ data-label-pause="' . $label_pause . '"
109
+ ></div>';
134
110
 
135
111
  // Add the HTML for the current track inside the figure.
136
112
  $figure = null;
@@ -149,7 +125,6 @@ function render_block_core_playlist( $attributes, $content, $block ) {
149
125
  'playlistId' => $playlist_id,
150
126
  'currentId' => $current_unique_id,
151
127
  'tracks' => $playlist_tracks,
152
- 'isPlaying' => false,
153
128
  )
154
129
  )
155
130
  );
@@ -1,32 +1,44 @@
1
+ @use "@arraypress/waveform-player/dist/waveform-player";
2
+
3
+ // Waveform player dimensions.
4
+ $waveform-player-height: 100px;
5
+
1
6
  .wp-block-playlist {
2
- .wp-block-playlist__current-item {
3
- display: flex;
4
- align-items: center;
5
- gap: var(--wp--preset--spacing--40, 1.5em);
6
- align-self: stretch;
7
- padding-bottom: var(--wp--preset--spacing--30, 1em);
8
- margin-bottom: var(--wp--preset--spacing--30, 1em);
9
-
10
- div {
11
- display: flex;
12
- flex-direction: column;
13
- align-items: flex-start;
14
- gap: var(--wp--preset--spacing--20, 0.5em);
15
- }
7
+ // Main waveform player container.
8
+ .wp-block-playlist__waveform-player {
9
+ width: 100%;
10
+ margin: var(--wp--preset--spacing--20, 0.625em) 0;
11
+ position: relative;
12
+ }
16
13
 
17
- & .wp-block-playlist__current-item-artist-album {
18
- flex-direction: row;
19
- }
14
+ // Set the waveform track height and remove gap between button and waveform.
15
+ .waveform-track {
16
+ height: $waveform-player-height;
17
+ gap: 0;
18
+ }
20
19
 
21
- .wp-block-playlist__item-title {
22
- word-break: break-all;
20
+ // WaveformPlayer button styling.
21
+ .waveform-btn {
22
+ border-radius: 0;
23
+ border-end-start-radius: 0.125rem;
24
+ border-start-start-radius: 0.125rem;
25
+ width: $waveform-player-height;
26
+ height: $waveform-player-height;
27
+ min-width: $waveform-player-height;
28
+ background: currentColor;
29
+ margin: 0;
30
+
31
+ &:hover:not(:disabled) {
32
+ transform: none;
23
33
  }
34
+ }
24
35
 
36
+ .waveform-track.waveform-align-bottom .waveform-btn {
37
+ margin-bottom: 0;
25
38
  }
26
39
 
27
- audio {
28
- width: 100%;
29
- margin-top: var(--wp--preset--spacing--20, 0.625em);
40
+ .waveform-subtitle {
41
+ opacity: 0.7;
30
42
  }
31
43
 
32
44
  .wp-block-playlist__tracklist {
@@ -49,9 +61,4 @@
49
61
  counter-reset: playlist-track;
50
62
  }
51
63
  }
52
-
53
- li.block-list-appender.block-list-appender {
54
- position: initial;
55
- margin-top: var(--wp--preset--spacing--30, 1em);
56
- }
57
64
  }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ /**
6
+ * Internal dependencies
7
+ */
8
+ import { getTrackAttributes } from '../edit';
9
+
10
+ // Mock uuid to return predictable values.
11
+ jest.mock( 'uuid', () => ( {
12
+ v4: jest.fn( () => 'mock-uuid-1234' ),
13
+ } ) );
14
+
15
+ describe( 'Playlist block edit utilities', () => {
16
+ describe( 'getTrackAttributes', () => {
17
+ it( 'should transform media object to track attributes', () => {
18
+ const media = {
19
+ id: 123,
20
+ url: 'https://example.com/song.mp3',
21
+ title: 'My Song',
22
+ artist: 'The Artist',
23
+ album: 'Great Album',
24
+ fileLength: '3:45',
25
+ image: { src: 'https://example.com/cover.jpg' },
26
+ };
27
+
28
+ const result = getTrackAttributes( media );
29
+
30
+ expect( result ).toEqual( {
31
+ id: 123,
32
+ uniqueId: 'mock-uuid-1234',
33
+ src: 'https://example.com/song.mp3',
34
+ title: 'My Song',
35
+ artist: 'The Artist',
36
+ album: 'Great Album',
37
+ length: '3:45',
38
+ image: 'https://example.com/cover.jpg',
39
+ } );
40
+ } );
41
+
42
+ it( 'should use URL as id when attachment id is not available', () => {
43
+ const media = {
44
+ url: 'https://example.com/song.mp3',
45
+ title: 'My Song',
46
+ };
47
+
48
+ const result = getTrackAttributes( media );
49
+
50
+ expect( result.id ).toBe( 'https://example.com/song.mp3' );
51
+ } );
52
+
53
+ it( 'should fall back to meta.artist when artist is not available', () => {
54
+ const media = {
55
+ url: 'https://example.com/song.mp3',
56
+ title: 'My Song',
57
+ meta: { artist: 'Meta Artist' },
58
+ };
59
+
60
+ const result = getTrackAttributes( media );
61
+
62
+ expect( result.artist ).toBe( 'Meta Artist' );
63
+ } );
64
+
65
+ it( 'should fall back to media_details.artist when artist and meta.artist are not available', () => {
66
+ const media = {
67
+ url: 'https://example.com/song.mp3',
68
+ title: 'My Song',
69
+ media_details: { artist: 'Media Details Artist' },
70
+ };
71
+
72
+ const result = getTrackAttributes( media );
73
+
74
+ expect( result.artist ).toBe( 'Media Details Artist' );
75
+ } );
76
+
77
+ it( 'should use "Unknown artist" when no artist is available', () => {
78
+ const media = {
79
+ url: 'https://example.com/song.mp3',
80
+ title: 'My Song',
81
+ };
82
+
83
+ const result = getTrackAttributes( media );
84
+
85
+ expect( result.artist ).toBe( 'Unknown artist' );
86
+ } );
87
+
88
+ it( 'should use "Unknown album" when no album is available', () => {
89
+ const media = {
90
+ url: 'https://example.com/song.mp3',
91
+ title: 'My Song',
92
+ };
93
+
94
+ const result = getTrackAttributes( media );
95
+
96
+ expect( result.album ).toBe( 'Unknown album' );
97
+ } );
98
+
99
+ it( 'should use media_details.length_formatted when fileLength is not available', () => {
100
+ const media = {
101
+ url: 'https://example.com/song.mp3',
102
+ title: 'My Song',
103
+ media_details: { length_formatted: '4:30' },
104
+ };
105
+
106
+ const result = getTrackAttributes( media );
107
+
108
+ expect( result.length ).toBe( '4:30' );
109
+ } );
110
+
111
+ it( 'should exclude default audio icon from image', () => {
112
+ const media = {
113
+ url: 'https://example.com/song.mp3',
114
+ title: 'My Song',
115
+ image: {
116
+ src: 'https://example.com/wp-includes/images/media/audio.svg',
117
+ },
118
+ };
119
+
120
+ const result = getTrackAttributes( media );
121
+
122
+ expect( result.image ).toBe( '' );
123
+ } );
124
+
125
+ it( 'should include image URLs', () => {
126
+ const media = {
127
+ url: 'https://example.com/song.mp3',
128
+ title: 'My Song',
129
+ image: { src: 'https://example.com/cover.jpg' },
130
+ };
131
+
132
+ const result = getTrackAttributes( media );
133
+
134
+ expect( result.image ).toBe( 'https://example.com/cover.jpg' );
135
+ } );
136
+ } );
137
+ } );