@wordpress/edit-site 5.28.4 → 5.28.6

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 (49) hide show
  1. package/build/components/block-editor/use-site-editor-settings.js +1 -1
  2. package/build/components/block-editor/use-site-editor-settings.js.map +1 -1
  3. package/build/components/global-styles/font-library-modal/context.js +63 -59
  4. package/build/components/global-styles/font-library-modal/context.js.map +1 -1
  5. package/build/components/global-styles/font-library-modal/font-collection.js +6 -6
  6. package/build/components/global-styles/font-library-modal/font-collection.js.map +1 -1
  7. package/build/components/global-styles/font-library-modal/index.js +17 -4
  8. package/build/components/global-styles/font-library-modal/index.js.map +1 -1
  9. package/build/components/global-styles/font-library-modal/installed-fonts.js +10 -1
  10. package/build/components/global-styles/font-library-modal/installed-fonts.js.map +1 -1
  11. package/build/components/global-styles/font-library-modal/upload-fonts.js +46 -20
  12. package/build/components/global-styles/font-library-modal/upload-fonts.js.map +1 -1
  13. package/build/components/global-styles/font-library-modal/utils/index.js +39 -5
  14. package/build/components/global-styles/font-library-modal/utils/index.js.map +1 -1
  15. package/build/components/keyboard-shortcuts/global.js +17 -3
  16. package/build/components/keyboard-shortcuts/global.js.map +1 -1
  17. package/build/components/resizable-frame/index.js +2 -1
  18. package/build/components/resizable-frame/index.js.map +1 -1
  19. package/build-module/components/block-editor/use-site-editor-settings.js +1 -1
  20. package/build-module/components/block-editor/use-site-editor-settings.js.map +1 -1
  21. package/build-module/components/global-styles/font-library-modal/context.js +65 -61
  22. package/build-module/components/global-styles/font-library-modal/context.js.map +1 -1
  23. package/build-module/components/global-styles/font-library-modal/font-collection.js +6 -6
  24. package/build-module/components/global-styles/font-library-modal/font-collection.js.map +1 -1
  25. package/build-module/components/global-styles/font-library-modal/index.js +17 -4
  26. package/build-module/components/global-styles/font-library-modal/index.js.map +1 -1
  27. package/build-module/components/global-styles/font-library-modal/installed-fonts.js +10 -1
  28. package/build-module/components/global-styles/font-library-modal/installed-fonts.js.map +1 -1
  29. package/build-module/components/global-styles/font-library-modal/upload-fonts.js +46 -20
  30. package/build-module/components/global-styles/font-library-modal/upload-fonts.js.map +1 -1
  31. package/build-module/components/global-styles/font-library-modal/utils/index.js +38 -5
  32. package/build-module/components/global-styles/font-library-modal/utils/index.js.map +1 -1
  33. package/build-module/components/keyboard-shortcuts/global.js +17 -3
  34. package/build-module/components/keyboard-shortcuts/global.js.map +1 -1
  35. package/build-module/components/resizable-frame/index.js +2 -1
  36. package/build-module/components/resizable-frame/index.js.map +1 -1
  37. package/build-style/style-rtl.css +20 -11
  38. package/build-style/style.css +20 -11
  39. package/package.json +19 -19
  40. package/src/components/block-editor/use-site-editor-settings.js +0 -2
  41. package/src/components/global-styles/font-library-modal/context.js +122 -107
  42. package/src/components/global-styles/font-library-modal/font-collection.js +10 -8
  43. package/src/components/global-styles/font-library-modal/index.js +21 -14
  44. package/src/components/global-styles/font-library-modal/installed-fonts.js +18 -1
  45. package/src/components/global-styles/font-library-modal/upload-fonts.js +56 -21
  46. package/src/components/global-styles/font-library-modal/utils/index.js +45 -5
  47. package/src/components/global-styles/font-library-modal/utils/test/getDisplaySrcFromFontFace.spec.js +7 -18
  48. package/src/components/keyboard-shortcuts/global.js +16 -4
  49. package/src/components/resizable-frame/index.js +1 -0
@@ -28,7 +28,8 @@ import { unlock } from '../../../lock-unlock';
28
28
  const { ProgressBar } = unlock( componentsPrivateApis );
29
29
 
30
30
  function UploadFonts() {
31
- const { installFont, notice, setNotice } = useContext( FontLibraryContext );
31
+ const { installFonts, notice, setNotice } =
32
+ useContext( FontLibraryContext );
32
33
  const [ isUploading, setIsUploading ] = useState( false );
33
34
 
34
35
  const handleDropZone = ( files ) => {
@@ -44,29 +45,48 @@ function UploadFonts() {
44
45
  * @param {Array} files The files to be filtered
45
46
  * @return {void}
46
47
  */
47
- const handleFilesUpload = ( files ) => {
48
+ const handleFilesUpload = async ( files ) => {
48
49
  setNotice( null );
49
50
  setIsUploading( true );
50
51
  const uniqueFilenames = new Set();
51
52
  const selectedFiles = [ ...files ];
52
- const allowedFiles = selectedFiles.filter( ( file ) => {
53
+ let hasInvalidFiles = false;
54
+
55
+ // Use map to create a promise for each file check, then filter with Promise.all.
56
+ const checkFilesPromises = selectedFiles.map( async ( file ) => {
57
+ const isFont = await isFontFile( file );
58
+ if ( ! isFont ) {
59
+ hasInvalidFiles = true;
60
+ return null; // Return null for invalid files.
61
+ }
62
+ // Check for duplicates
53
63
  if ( uniqueFilenames.has( file.name ) ) {
54
- return false; // Discard duplicates
64
+ return null; // Return null for duplicates.
55
65
  }
56
- // Eliminates files that are not allowed
66
+ // Check if the file extension is allowed.
57
67
  const fileExtension = file.name.split( '.' ).pop().toLowerCase();
58
68
  if ( ALLOWED_FILE_EXTENSIONS.includes( fileExtension ) ) {
59
69
  uniqueFilenames.add( file.name );
60
- return true; // Keep file if the extension is allowed
70
+ return file; // Return the file if it passes all checks.
61
71
  }
62
- return false; // Discard file extension not allowed
72
+ return null; // Return null for disallowed file extensions.
63
73
  } );
74
+
75
+ // Filter out the nulls after all promises have resolved.
76
+ const allowedFiles = ( await Promise.all( checkFilesPromises ) ).filter(
77
+ ( file ) => null !== file
78
+ );
79
+
64
80
  if ( allowedFiles.length > 0 ) {
65
81
  loadFiles( allowedFiles );
66
82
  } else {
83
+ const message = hasInvalidFiles
84
+ ? __( 'Sorry, you are not allowed to upload this file type.' )
85
+ : __( 'No fonts found to install.' );
86
+
67
87
  setNotice( {
68
88
  type: 'error',
69
- message: __( 'No fonts found to install.' ),
89
+ message,
70
90
  } );
71
91
  setIsUploading( false );
72
92
  }
@@ -93,6 +113,23 @@ function UploadFonts() {
93
113
  handleInstall( fontFacesLoaded );
94
114
  };
95
115
 
116
+ /**
117
+ * Checks if a file is a valid Font file.
118
+ *
119
+ * @param {File} file The file to be checked.
120
+ * @return {boolean} Whether the file is a valid font file.
121
+ */
122
+ async function isFontFile( file ) {
123
+ const font = new Font( 'Uploaded Font' );
124
+ try {
125
+ const buffer = await readFileAsArrayBuffer( file );
126
+ await font.fromDataBuffer( buffer, 'font' );
127
+ return true;
128
+ } catch ( error ) {
129
+ return false;
130
+ }
131
+ }
132
+
96
133
  // Create a function to read the file as array buffer
97
134
  async function readFileAsArrayBuffer( file ) {
98
135
  return new Promise( ( resolve, reject ) => {
@@ -143,19 +180,8 @@ function UploadFonts() {
143
180
  const handleInstall = async ( fontFaces ) => {
144
181
  const fontFamilies = makeFamiliesFromFaces( fontFaces );
145
182
 
146
- if ( fontFamilies.length > 1 ) {
147
- setNotice( {
148
- type: 'error',
149
- message: __(
150
- 'Variants from only one font family can be uploaded at a time.'
151
- ),
152
- } );
153
- setIsUploading( false );
154
- return;
155
- }
156
-
157
183
  try {
158
- await installFont( fontFamilies[ 0 ] );
184
+ await installFonts( fontFamilies );
159
185
  setNotice( {
160
186
  type: 'success',
161
187
  message: __( 'Fonts were installed successfully.' ),
@@ -164,6 +190,7 @@ function UploadFonts() {
164
190
  setNotice( {
165
191
  type: 'error',
166
192
  message: error.message,
193
+ errors: error?.installationErrors,
167
194
  } );
168
195
  }
169
196
 
@@ -177,9 +204,17 @@ function UploadFonts() {
177
204
  { notice && (
178
205
  <Notice
179
206
  status={ notice.type }
207
+ __unstableHTML
180
208
  onRemove={ () => setNotice( null ) }
181
209
  >
182
210
  { notice.message }
211
+ { notice.errors && (
212
+ <ul>
213
+ { notice.errors.map( ( error, index ) => (
214
+ <li key={ index }>{ error }</li>
215
+ ) ) }
216
+ </ul>
217
+ ) }
183
218
  </Notice>
184
219
  ) }
185
220
  { isUploading && (
@@ -209,7 +244,7 @@ function UploadFonts() {
209
244
  <Spacer margin={ 2 } />
210
245
  <Text className="font-library-modal__upload-area__text">
211
246
  { __(
212
- 'Uploaded fonts appear in your library and can be used in your theme. Supported formats: .tff, .otf, .woff, and .woff2.'
247
+ 'Uploaded fonts appear in your library and can be used in your theme. Supported formats: .ttf, .otf, .woff, and .woff2.'
213
248
  ) }
214
249
  </Text>
215
250
  </VStack>
@@ -121,7 +121,47 @@ export async function loadFontFaceInBrowser( fontFace, source, addTo = 'all' ) {
121
121
  }
122
122
  }
123
123
 
124
- export function getDisplaySrcFromFontFace( input, urlPrefix ) {
124
+ /*
125
+ * Unloads the font face and remove it from the browser.
126
+ * It also removes it from the iframe document.
127
+ *
128
+ * Note that Font faces that were added to the set using the CSS @font-face rule
129
+ * remain connected to the corresponding CSS, and cannot be deleted.
130
+ *
131
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/delete.
132
+ */
133
+ export function unloadFontFaceInBrowser( fontFace, removeFrom = 'all' ) {
134
+ const unloadFontFace = ( fonts ) => {
135
+ fonts.forEach( ( f ) => {
136
+ if (
137
+ f.family === formatFontFaceName( fontFace.fontFamily ) &&
138
+ f.weight === fontFace.fontWeight &&
139
+ f.style === fontFace.fontStyle
140
+ ) {
141
+ fonts.delete( f );
142
+ }
143
+ } );
144
+ };
145
+
146
+ if ( removeFrom === 'document' || removeFrom === 'all' ) {
147
+ unloadFontFace( document.fonts );
148
+ }
149
+
150
+ if ( removeFrom === 'iframe' || removeFrom === 'all' ) {
151
+ const iframeDocument = document.querySelector(
152
+ 'iframe[name="editor-canvas"]'
153
+ ).contentDocument;
154
+ unloadFontFace( iframeDocument.fonts );
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Retrieves the display source from a font face src.
160
+ *
161
+ * @param {string|string[]} input - The font face src.
162
+ * @return {string|undefined} The display source or undefined if the input is invalid.
163
+ */
164
+ export function getDisplaySrcFromFontFace( input ) {
125
165
  if ( ! input ) {
126
166
  return;
127
167
  }
@@ -132,9 +172,9 @@ export function getDisplaySrcFromFontFace( input, urlPrefix ) {
132
172
  } else {
133
173
  src = input;
134
174
  }
135
- // If it is a theme font, we need to make the url absolute
136
- if ( src.startsWith( 'file:.' ) && urlPrefix ) {
137
- src = src.replace( 'file:.', urlPrefix );
175
+ // It's expected theme fonts will already be loaded in the browser.
176
+ if ( src.startsWith( 'file:.' ) ) {
177
+ return;
138
178
  }
139
179
  if ( ! isUrlEncoded( src ) ) {
140
180
  src = encodeURI( src );
@@ -219,7 +259,7 @@ export async function batchInstallFontFaces( fontFamilyId, fontFacesData ) {
219
259
  // Handle network errors or other fetch-related errors
220
260
  results.errors.push( {
221
261
  data: fontFacesData[ index ],
222
- message: `Fetch error: ${ result.reason.message }`,
262
+ message: result.reason.message,
223
263
  } );
224
264
  }
225
265
  } );
@@ -21,33 +21,22 @@ describe( 'getDisplaySrcFromFontFace', () => {
21
21
  );
22
22
  } );
23
23
 
24
- it( 'makes URL absolute when it starts with file:. and urlPrefix is given', () => {
25
- const input = 'file:./font1';
26
- const urlPrefix = 'http://example.com';
27
- expect( getDisplaySrcFromFontFace( input, urlPrefix ) ).toBe(
28
- 'http://example.com/font1'
29
- );
30
- } );
31
-
32
- it( 'does not modify URL if it does not start with file:.', () => {
33
- const input = [ 'http://some-other-place.com/font1' ];
34
- const urlPrefix = 'http://example.com';
35
- expect( getDisplaySrcFromFontFace( input, urlPrefix ) ).toBe(
36
- 'http://some-other-place.com/font1'
37
- );
24
+ it( 'return undefined when the url starts with file:', () => {
25
+ const input = 'file:./theme/assets/font1.ttf';
26
+ expect( getDisplaySrcFromFontFace( input ) ).toBe( undefined );
38
27
  } );
39
28
 
40
29
  it( 'encodes the URL if it is not encoded', () => {
41
- const input = 'file:./assets/font one with spaces.ttf';
30
+ const input = 'https://example.org/font one with spaces.ttf';
42
31
  expect( getDisplaySrcFromFontFace( input ) ).toBe(
43
- 'file:./assets/font%20one%20with%20spaces.ttf'
32
+ 'https://example.org/font%20one%20with%20spaces.ttf'
44
33
  );
45
34
  } );
46
35
 
47
36
  it( 'does not encode the URL if it is already encoded', () => {
48
- const input = 'file:./font%20one';
37
+ const input = 'https://example.org/fonts/font%20one.ttf';
49
38
  expect( getDisplaySrcFromFontFace( input ) ).toBe(
50
- 'file:./font%20one'
39
+ 'https://example.org/fonts/font%20one.ttf'
51
40
  );
52
41
  } );
53
42
  } );
@@ -4,29 +4,41 @@
4
4
  import { useShortcut } from '@wordpress/keyboard-shortcuts';
5
5
  import { useDispatch, useSelect } from '@wordpress/data';
6
6
  import { store as coreStore } from '@wordpress/core-data';
7
+ import { store as editorStore } from '@wordpress/editor';
7
8
 
8
9
  /**
9
10
  * Internal dependencies
10
11
  */
11
12
  import { store as editSiteStore } from '../../store';
13
+ import { unlock } from '../../lock-unlock';
12
14
 
13
15
  function KeyboardShortcutsGlobal() {
14
16
  const { __experimentalGetDirtyEntityRecords, isSavingEntityRecord } =
15
17
  useSelect( coreStore );
18
+ const { hasNonPostEntityChanges } = useSelect( editorStore );
19
+ const { getCanvasMode } = unlock( useSelect( editSiteStore ) );
16
20
  const { setIsSaveViewOpened } = useDispatch( editSiteStore );
17
21
 
18
22
  useShortcut( 'core/edit-site/save', ( event ) => {
19
23
  event.preventDefault();
20
24
 
21
25
  const dirtyEntityRecords = __experimentalGetDirtyEntityRecords();
22
- const isDirty = !! dirtyEntityRecords.length;
26
+ const hasDirtyEntities = !! dirtyEntityRecords.length;
23
27
  const isSaving = dirtyEntityRecords.some( ( record ) =>
24
28
  isSavingEntityRecord( record.kind, record.name, record.key )
25
29
  );
26
-
27
- if ( ! isSaving && isDirty ) {
28
- setIsSaveViewOpened( true );
30
+ const _hasNonPostEntityChanges = hasNonPostEntityChanges();
31
+ const isViewMode = getCanvasMode() === 'view';
32
+ if (
33
+ ( ! hasDirtyEntities || ! _hasNonPostEntityChanges || isSaving ) &&
34
+ ! isViewMode
35
+ ) {
36
+ return;
29
37
  }
38
+ // At this point, we know that there are dirty entities, other than
39
+ // the edited post, and we're not in the process of saving, so open
40
+ // save view.
41
+ setIsSaveViewOpened( true );
30
42
  } );
31
43
 
32
44
  return null;
@@ -300,6 +300,7 @@ function ResizableFrame( {
300
300
  className={ classnames( 'edit-site-resizable-frame__inner', {
301
301
  'is-resizing': isResizing,
302
302
  } ) }
303
+ showHandle={ false } // Do not show the default handle, as we're using a custom one.
303
304
  >
304
305
  <motion.div
305
306
  className="edit-site-resizable-frame__inner-content"