@wordpress/edit-site 5.28.8 → 5.28.10

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 (36) hide show
  1. package/build/components/global-styles/font-library-modal/context.js +80 -23
  2. package/build/components/global-styles/font-library-modal/context.js.map +1 -1
  3. package/build/components/global-styles/font-library-modal/font-collection.js +1 -0
  4. package/build/components/global-styles/font-library-modal/font-collection.js.map +1 -1
  5. package/build/components/global-styles/font-library-modal/index.js +1 -1
  6. package/build/components/global-styles/font-library-modal/index.js.map +1 -1
  7. package/build/components/global-styles/font-library-modal/installed-fonts.js +5 -2
  8. package/build/components/global-styles/font-library-modal/installed-fonts.js.map +1 -1
  9. package/build/components/global-styles/font-library-modal/utils/index.js +20 -2
  10. package/build/components/global-styles/font-library-modal/utils/index.js.map +1 -1
  11. package/build/hooks/push-changes-to-global-styles/index.js +4 -40
  12. package/build/hooks/push-changes-to-global-styles/index.js.map +1 -1
  13. package/build/utils/set-nested-value.js +44 -0
  14. package/build/utils/set-nested-value.js.map +1 -0
  15. package/build-module/components/global-styles/font-library-modal/context.js +79 -23
  16. package/build-module/components/global-styles/font-library-modal/context.js.map +1 -1
  17. package/build-module/components/global-styles/font-library-modal/font-collection.js +1 -0
  18. package/build-module/components/global-styles/font-library-modal/font-collection.js.map +1 -1
  19. package/build-module/components/global-styles/font-library-modal/index.js +2 -2
  20. package/build-module/components/global-styles/font-library-modal/index.js.map +1 -1
  21. package/build-module/components/global-styles/font-library-modal/installed-fonts.js +5 -2
  22. package/build-module/components/global-styles/font-library-modal/installed-fonts.js.map +1 -1
  23. package/build-module/components/global-styles/font-library-modal/utils/index.js +20 -2
  24. package/build-module/components/global-styles/font-library-modal/utils/index.js.map +1 -1
  25. package/build-module/hooks/push-changes-to-global-styles/index.js +1 -38
  26. package/build-module/hooks/push-changes-to-global-styles/index.js.map +1 -1
  27. package/build-module/utils/set-nested-value.js +38 -0
  28. package/build-module/utils/set-nested-value.js.map +1 -0
  29. package/package.json +16 -16
  30. package/src/components/global-styles/font-library-modal/context.js +95 -41
  31. package/src/components/global-styles/font-library-modal/font-collection.js +1 -0
  32. package/src/components/global-styles/font-library-modal/index.js +2 -2
  33. package/src/components/global-styles/font-library-modal/installed-fonts.js +4 -1
  34. package/src/components/global-styles/font-library-modal/utils/index.js +17 -4
  35. package/src/hooks/push-changes-to-global-styles/index.js +1 -40
  36. package/src/utils/set-nested-value.js +39 -0
@@ -35,12 +35,12 @@ import {
35
35
  checkFontFaceInstalled,
36
36
  } from './utils';
37
37
  import { toggleFont } from './utils/toggleFont';
38
+ import setNestedValue from '../../../utils/set-nested-value';
38
39
 
39
40
  export const FontLibraryContext = createContext( {} );
40
41
 
41
42
  function FontLibraryProvider( { children } ) {
42
- const { __experimentalSaveSpecifiedEntityEdits: saveSpecifiedEntityEdits } =
43
- useDispatch( coreStore );
43
+ const { saveEntityRecord } = useDispatch( coreStore );
44
44
  const { globalStylesId } = useSelect( ( select ) => {
45
45
  const { __experimentalGetCurrentGlobalStylesId } = select( coreStore );
46
46
  return { globalStylesId: __experimentalGetCurrentGlobalStylesId() };
@@ -94,29 +94,61 @@ function FontLibraryProvider( { children } ) {
94
94
  'base'
95
95
  );
96
96
 
97
- // Save font families to the global styles post in the database.
98
- const saveFontFamilies = () => {
99
- saveSpecifiedEntityEdits( 'root', 'globalStyles', globalStylesId, [
100
- 'settings.typography.fontFamilies',
101
- ] );
97
+ /*
98
+ * Save the font families to the database.
99
+
100
+ * This function is called when the user activates or deactivates a font family.
101
+ * It only updates the global styles post content in the database for new font families.
102
+ * This avoids saving other styles/settings changed by the user using other parts of the editor.
103
+ *
104
+ * It uses the font families from the param to avoid using the font families from an outdated state.
105
+ *
106
+ * @param {Array} fonts - The font families that will be saved to the database.
107
+ */
108
+ const saveFontFamilies = async ( fonts ) => {
109
+ // Gets the global styles database post content.
110
+ const updatedGlobalStyles = globalStyles.record;
111
+
112
+ // Updates the database version of global styles with the edited font families in the client.
113
+ setNestedValue(
114
+ updatedGlobalStyles,
115
+ [ 'settings', 'typography', 'fontFamilies' ],
116
+ fonts
117
+ );
118
+
119
+ // Saves a new version of the global styles in the database.
120
+ await saveEntityRecord( 'root', 'globalStyles', updatedGlobalStyles );
102
121
  };
103
122
 
104
123
  // Library Fonts
105
124
  const [ modalTabOpen, setModalTabOpen ] = useState( false );
106
125
  const [ libraryFontSelected, setLibraryFontSelected ] = useState( null );
107
126
 
108
- const baseThemeFonts = baseFontFamilies?.theme
109
- ? baseFontFamilies.theme
110
- .map( ( f ) => setUIValuesNeeded( f, { source: 'theme' } ) )
111
- .sort( ( a, b ) => a.name.localeCompare( b.name ) )
112
- : [];
113
-
127
+ // Themes Fonts are the fonts defined in the global styles (database persisted theme.json data).
114
128
  const themeFonts = fontFamilies?.theme
115
129
  ? fontFamilies.theme
116
130
  .map( ( f ) => setUIValuesNeeded( f, { source: 'theme' } ) )
117
131
  .sort( ( a, b ) => a.name.localeCompare( b.name ) )
118
132
  : [];
119
133
 
134
+ const themeFontsSlugs = new Set( themeFonts.map( ( f ) => f.slug ) );
135
+
136
+ /*
137
+ * Base Theme Fonts are the fonts defined in the theme.json *file*.
138
+ *
139
+ * Uses the fonts from global styles + the ones from the theme.json file that hasn't repeated slugs.
140
+ * Avoids incosistencies with the fonts listed in the font library modal as base (unactivated).
141
+ * These inconsistencies can happen when the active theme fonts in global styles aren't defined in theme.json file as when a theme style variation is applied.
142
+ */
143
+ const baseThemeFonts = baseFontFamilies?.theme
144
+ ? themeFonts.concat(
145
+ baseFontFamilies.theme
146
+ .filter( ( f ) => ! themeFontsSlugs.has( f.slug ) )
147
+ .map( ( f ) => setUIValuesNeeded( f, { source: 'theme' } ) )
148
+ .sort( ( a, b ) => a.name.localeCompare( b.name ) )
149
+ )
150
+ : [];
151
+
120
152
  const customFonts = fontFamilies?.custom
121
153
  ? fontFamilies.custom
122
154
  .map( ( f ) => setUIValuesNeeded( f, { source: 'custom' } ) )
@@ -144,8 +176,7 @@ function FontLibraryProvider( { children } ) {
144
176
  return;
145
177
  }
146
178
 
147
- const fonts =
148
- font.source === 'theme' ? baseThemeFonts : baseCustomFonts;
179
+ const fonts = font.source === 'theme' ? themeFonts : baseCustomFonts;
149
180
 
150
181
  // Tries to find the font in the installed fonts
151
182
  const fontSelected = fonts.find( ( f ) => f.slug === font.slug );
@@ -269,11 +300,13 @@ function FontLibraryProvider( { children } ) {
269
300
  sucessfullyInstalledFontFaces?.length > 0 ||
270
301
  alreadyInstalledFontFaces?.length > 0
271
302
  ) {
272
- fontFamilyToInstall.fontFace = [
303
+ // Use font data from REST API not from client to ensure
304
+ // correct font information is used.
305
+ installedFontFamily.fontFace = [
273
306
  ...sucessfullyInstalledFontFaces,
274
- ...alreadyInstalledFontFaces,
275
307
  ];
276
- fontFamiliesToActivate.push( fontFamilyToInstall );
308
+
309
+ fontFamiliesToActivate.push( installedFontFamily );
277
310
  }
278
311
 
279
312
  // If it's a system font but was installed successfully, activate it.
@@ -308,15 +341,11 @@ function FontLibraryProvider( { children } ) {
308
341
 
309
342
  if ( fontFamiliesToActivate.length > 0 ) {
310
343
  // Activate the font family (add the font family to the global styles).
311
- activateCustomFontFamilies( fontFamiliesToActivate );
312
-
313
- // Save the global styles to the database.
314
- await saveSpecifiedEntityEdits(
315
- 'root',
316
- 'globalStyles',
317
- globalStylesId,
318
- [ 'settings.typography.fontFamilies' ]
344
+ const activeFonts = activateCustomFontFamilies(
345
+ fontFamiliesToActivate
319
346
  );
347
+ // Save the global styles to the database.
348
+ await saveFontFamilies( activeFonts );
320
349
 
321
350
  refreshLibrary();
322
351
  }
@@ -346,14 +375,11 @@ function FontLibraryProvider( { children } ) {
346
375
  // Deactivate the font family if delete request is successful
347
376
  // (Removes the font family from the global styles).
348
377
  if ( uninstalledFontFamily.deleted ) {
349
- deactivateFontFamily( fontFamilyToUninstall );
350
- // Save the global styles to the database.
351
- await saveSpecifiedEntityEdits(
352
- 'root',
353
- 'globalStyles',
354
- globalStylesId,
355
- [ 'settings.typography.fontFamilies' ]
378
+ const activeFonts = deactivateFontFamily(
379
+ fontFamilyToUninstall
356
380
  );
381
+ // Save the global styles to the database.
382
+ await saveFontFamilies( activeFonts );
357
383
  }
358
384
 
359
385
  // Refresh the library (the library font families from database).
@@ -377,27 +403,54 @@ function FontLibraryProvider( { children } ) {
377
403
  const newCustomFonts = initialCustomFonts.filter(
378
404
  ( f ) => f.slug !== font.slug
379
405
  );
380
- setFontFamilies( {
406
+ const activeFonts = {
381
407
  ...fontFamilies,
382
408
  [ font.source ]: newCustomFonts,
383
- } );
409
+ };
410
+ setFontFamilies( activeFonts );
384
411
 
385
412
  if ( font.fontFace ) {
386
413
  font.fontFace.forEach( ( face ) => {
387
414
  unloadFontFaceInBrowser( face, 'all' );
388
415
  } );
389
416
  }
417
+ return activeFonts;
390
418
  };
391
419
 
392
420
  const activateCustomFontFamilies = ( fontsToAdd ) => {
393
- // Merge the existing custom fonts with the new fonts.
394
- // Activate the fonts by set the new custom fonts array.
395
- setFontFamilies( {
421
+ const fontsToActivate = cleanFontsForSave( fontsToAdd );
422
+
423
+ const activeFonts = {
396
424
  ...fontFamilies,
397
- custom: mergeFontFamilies( fontFamilies?.custom, fontsToAdd ),
398
- } );
425
+ // Merge the existing custom fonts with the new fonts.
426
+ custom: mergeFontFamilies( fontFamilies?.custom, fontsToActivate ),
427
+ };
428
+
429
+ // Activate the fonts by set the new custom fonts array.
430
+ setFontFamilies( activeFonts );
431
+
432
+ loadFontsInBrowser( fontsToActivate );
433
+
434
+ return activeFonts;
435
+ };
436
+
437
+ // Removes the id from the families and faces to avoid saving that to global styles post content.
438
+ const cleanFontsForSave = ( fonts ) => {
439
+ return fonts.map( ( { id: _familyDbId, fontFace, ...font } ) => ( {
440
+ ...font,
441
+ ...( fontFace && fontFace.length > 0
442
+ ? {
443
+ fontFace: fontFace.map(
444
+ ( { id: _faceDbId, ...face } ) => face
445
+ ),
446
+ }
447
+ : {} ),
448
+ } ) );
449
+ };
450
+
451
+ const loadFontsInBrowser = ( fonts ) => {
399
452
  // Add custom fonts to the browser.
400
- fontsToAdd.forEach( ( font ) => {
453
+ fonts.forEach( ( font ) => {
401
454
  if ( font.fontFace ) {
402
455
  font.fontFace.forEach( ( face ) => {
403
456
  // Load font faces just in the iframe because they already are in the document.
@@ -489,6 +542,7 @@ function FontLibraryProvider( { children } ) {
489
542
  value={ {
490
543
  libraryFontSelected,
491
544
  handleSetLibraryFontSelected,
545
+ fontFamilies,
492
546
  themeFonts,
493
547
  baseThemeFonts,
494
548
  customFonts,
@@ -361,6 +361,7 @@ function FontCollection( { slug } ) {
361
361
  isSmall
362
362
  onClick={ () => {
363
363
  setSelectedFont( null );
364
+ setNotice( null );
364
365
  } }
365
366
  aria-label={ __( 'Navigate to the previous view' ) }
366
367
  />
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { __ } from '@wordpress/i18n';
4
+ import { __, _x } from '@wordpress/i18n';
5
5
  import {
6
6
  Modal,
7
7
  privateApis as componentsPrivateApis,
@@ -23,7 +23,7 @@ const { Tabs } = unlock( componentsPrivateApis );
23
23
 
24
24
  const DEFAULT_TAB = {
25
25
  id: 'installed-fonts',
26
- title: __( 'Library' ),
26
+ title: _x( 'Library', 'Font library' ),
27
27
  };
28
28
 
29
29
  const UPLOAD_TAB = {
@@ -49,6 +49,7 @@ function InstalledFonts() {
49
49
  fontFamiliesHasChanges,
50
50
  notice,
51
51
  setNotice,
52
+ fontFamilies,
52
53
  } = useContext( FontLibraryContext );
53
54
  const [ isConfirmDeleteOpen, setIsConfirmDeleteOpen ] = useState( false );
54
55
  const customFontFamilyId =
@@ -262,7 +263,9 @@ function InstalledFonts() {
262
263
  ) }
263
264
  <Button
264
265
  variant="primary"
265
- onClick={ saveFontFamilies }
266
+ onClick={ () => {
267
+ saveFontFamilies( fontFamilies );
268
+ } }
266
269
  disabled={ ! fontFamiliesHasChanges }
267
270
  __experimentalIsFocusable
268
271
  >
@@ -234,10 +234,23 @@ export function makeFontFacesFormData( font ) {
234
234
  }
235
235
 
236
236
  export async function batchInstallFontFaces( fontFamilyId, fontFacesData ) {
237
- const promises = fontFacesData.map( ( faceData ) =>
238
- fetchInstallFontFace( fontFamilyId, faceData )
239
- );
240
- const responses = await Promise.allSettled( promises );
237
+ const responses = [];
238
+
239
+ /*
240
+ * Uses the same response format as Promise.allSettled, but executes requests in sequence to work
241
+ * around a race condition that can cause an error when the fonts directory doesn't exist yet.
242
+ */
243
+ for ( const faceData of fontFacesData ) {
244
+ try {
245
+ const response = await fetchInstallFontFace(
246
+ fontFamilyId,
247
+ faceData
248
+ );
249
+ responses.push( { status: 'fulfilled', value: response } );
250
+ } catch ( error ) {
251
+ responses.push( { status: 'rejected', reason: error } );
252
+ }
253
+ }
241
254
 
242
255
  const results = {
243
256
  errors: [],
@@ -26,6 +26,7 @@ import { store as coreStore } from '@wordpress/core-data';
26
26
  */
27
27
  import { useSupportedStyles } from '../../components/global-styles/hooks';
28
28
  import { unlock } from '../../lock-unlock';
29
+ import setNestedValue from '../../utils/set-nested-value';
29
30
 
30
31
  const { cleanEmptyObject, GlobalStylesContext } = unlock(
31
32
  blockEditorPrivateApis
@@ -235,46 +236,6 @@ function useChangesToPush( name, attributes, userConfig ) {
235
236
  }, [ supports, attributes, blockUserConfig ] );
236
237
  }
237
238
 
238
- /**
239
- * Sets the value at path of object.
240
- * If a portion of path doesn’t exist, it’s created.
241
- * Arrays are created for missing index properties while objects are created
242
- * for all other missing properties.
243
- *
244
- * This function intentionally mutates the input object.
245
- *
246
- * Inspired by _.set().
247
- *
248
- * @see https://lodash.com/docs/4.17.15#set
249
- *
250
- * @todo Needs to be deduplicated with its copy in `@wordpress/core-data`.
251
- *
252
- * @param {Object} object Object to modify
253
- * @param {Array} path Path of the property to set.
254
- * @param {*} value Value to set.
255
- */
256
- function setNestedValue( object, path, value ) {
257
- if ( ! object || typeof object !== 'object' ) {
258
- return object;
259
- }
260
-
261
- path.reduce( ( acc, key, idx ) => {
262
- if ( acc[ key ] === undefined ) {
263
- if ( Number.isInteger( path[ idx + 1 ] ) ) {
264
- acc[ key ] = [];
265
- } else {
266
- acc[ key ] = {};
267
- }
268
- }
269
- if ( idx === path.length - 1 ) {
270
- acc[ key ] = value;
271
- }
272
- return acc[ key ];
273
- }, object );
274
-
275
- return object;
276
- }
277
-
278
239
  function cloneDeep( object ) {
279
240
  return ! object ? {} : JSON.parse( JSON.stringify( object ) );
280
241
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Sets the value at path of object.
3
+ * If a portion of path doesn’t exist, it’s created.
4
+ * Arrays are created for missing index properties while objects are created
5
+ * for all other missing properties.
6
+ *
7
+ * This function intentionally mutates the input object.
8
+ *
9
+ * Inspired by _.set().
10
+ *
11
+ * @see https://lodash.com/docs/4.17.15#set
12
+ *
13
+ * @todo Needs to be deduplicated with its copy in `@wordpress/core-data`.
14
+ *
15
+ * @param {Object} object Object to modify
16
+ * @param {Array} path Path of the property to set.
17
+ * @param {*} value Value to set.
18
+ */
19
+ export default function setNestedValue( object, path, value ) {
20
+ if ( ! object || typeof object !== 'object' ) {
21
+ return object;
22
+ }
23
+
24
+ path.reduce( ( acc, key, idx ) => {
25
+ if ( acc[ key ] === undefined ) {
26
+ if ( Number.isInteger( path[ idx + 1 ] ) ) {
27
+ acc[ key ] = [];
28
+ } else {
29
+ acc[ key ] = {};
30
+ }
31
+ }
32
+ if ( idx === path.length - 1 ) {
33
+ acc[ key ] = value;
34
+ }
35
+ return acc[ key ];
36
+ }, object );
37
+
38
+ return object;
39
+ }