@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.
- package/build/components/block-editor/use-site-editor-settings.js +1 -1
- package/build/components/block-editor/use-site-editor-settings.js.map +1 -1
- package/build/components/global-styles/font-library-modal/context.js +63 -59
- package/build/components/global-styles/font-library-modal/context.js.map +1 -1
- package/build/components/global-styles/font-library-modal/font-collection.js +6 -6
- package/build/components/global-styles/font-library-modal/font-collection.js.map +1 -1
- package/build/components/global-styles/font-library-modal/index.js +17 -4
- package/build/components/global-styles/font-library-modal/index.js.map +1 -1
- package/build/components/global-styles/font-library-modal/installed-fonts.js +10 -1
- package/build/components/global-styles/font-library-modal/installed-fonts.js.map +1 -1
- package/build/components/global-styles/font-library-modal/upload-fonts.js +46 -20
- package/build/components/global-styles/font-library-modal/upload-fonts.js.map +1 -1
- package/build/components/global-styles/font-library-modal/utils/index.js +39 -5
- package/build/components/global-styles/font-library-modal/utils/index.js.map +1 -1
- package/build/components/keyboard-shortcuts/global.js +17 -3
- package/build/components/keyboard-shortcuts/global.js.map +1 -1
- package/build/components/resizable-frame/index.js +2 -1
- package/build/components/resizable-frame/index.js.map +1 -1
- package/build-module/components/block-editor/use-site-editor-settings.js +1 -1
- package/build-module/components/block-editor/use-site-editor-settings.js.map +1 -1
- package/build-module/components/global-styles/font-library-modal/context.js +65 -61
- package/build-module/components/global-styles/font-library-modal/context.js.map +1 -1
- package/build-module/components/global-styles/font-library-modal/font-collection.js +6 -6
- package/build-module/components/global-styles/font-library-modal/font-collection.js.map +1 -1
- package/build-module/components/global-styles/font-library-modal/index.js +17 -4
- package/build-module/components/global-styles/font-library-modal/index.js.map +1 -1
- package/build-module/components/global-styles/font-library-modal/installed-fonts.js +10 -1
- package/build-module/components/global-styles/font-library-modal/installed-fonts.js.map +1 -1
- package/build-module/components/global-styles/font-library-modal/upload-fonts.js +46 -20
- package/build-module/components/global-styles/font-library-modal/upload-fonts.js.map +1 -1
- package/build-module/components/global-styles/font-library-modal/utils/index.js +38 -5
- package/build-module/components/global-styles/font-library-modal/utils/index.js.map +1 -1
- package/build-module/components/keyboard-shortcuts/global.js +17 -3
- package/build-module/components/keyboard-shortcuts/global.js.map +1 -1
- package/build-module/components/resizable-frame/index.js +2 -1
- package/build-module/components/resizable-frame/index.js.map +1 -1
- package/build-style/style-rtl.css +20 -11
- package/build-style/style.css +20 -11
- package/package.json +19 -19
- package/src/components/block-editor/use-site-editor-settings.js +0 -2
- package/src/components/global-styles/font-library-modal/context.js +122 -107
- package/src/components/global-styles/font-library-modal/font-collection.js +10 -8
- package/src/components/global-styles/font-library-modal/index.js +21 -14
- package/src/components/global-styles/font-library-modal/installed-fonts.js +18 -1
- package/src/components/global-styles/font-library-modal/upload-fonts.js +56 -21
- package/src/components/global-styles/font-library-modal/utils/index.js +45 -5
- package/src/components/global-styles/font-library-modal/utils/test/getDisplaySrcFromFontFace.spec.js +7 -18
- package/src/components/keyboard-shortcuts/global.js +16 -4
- 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 {
|
|
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
|
-
|
|
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
|
|
64
|
+
return null; // Return null for duplicates.
|
|
55
65
|
}
|
|
56
|
-
//
|
|
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
|
|
70
|
+
return file; // Return the file if it passes all checks.
|
|
61
71
|
}
|
|
62
|
-
return
|
|
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
|
|
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
|
|
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: .
|
|
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
|
-
|
|
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
|
-
//
|
|
136
|
-
if ( src.startsWith( 'file:.' )
|
|
137
|
-
|
|
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:
|
|
262
|
+
message: result.reason.message,
|
|
223
263
|
} );
|
|
224
264
|
}
|
|
225
265
|
} );
|
package/src/components/global-styles/font-library-modal/utils/test/getDisplaySrcFromFontFace.spec.js
CHANGED
|
@@ -21,33 +21,22 @@ describe( 'getDisplaySrcFromFontFace', () => {
|
|
|
21
21
|
);
|
|
22
22
|
} );
|
|
23
23
|
|
|
24
|
-
it( '
|
|
25
|
-
const input = 'file:./font1';
|
|
26
|
-
|
|
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 = '
|
|
30
|
+
const input = 'https://example.org/font one with spaces.ttf';
|
|
42
31
|
expect( getDisplaySrcFromFontFace( input ) ).toBe(
|
|
43
|
-
'
|
|
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 = '
|
|
37
|
+
const input = 'https://example.org/fonts/font%20one.ttf';
|
|
49
38
|
expect( getDisplaySrcFromFontFace( input ) ).toBe(
|
|
50
|
-
'
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
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"
|