Skip to content

Commit 4514b96

Browse files
authored
Add quotes to font family names (#476)
* Add quotes to families ending in number on gfonts * Add quotes to uploaded font family name * Check for special characters on form upload * Trim font names * Avoid re-rendering due to function definitions Moves the definition of onFormDataChange inside the effect where it's used so that it won't be re defined on render. Also, wraps isFormValid on useCallback for the same reason. * Add quotes to all font name strings #476 (comment) * Add tests Add dependencies required for test and linting as well: - @wordpress/scripts - @wordpress/eslint-plugin * Temporarily disable eslint rule Out of scope for this PR; follow-up to come. * Add quotes to demo style font name
1 parent 733e4ec commit 4514b96

File tree

8 files changed

+7820
-5021
lines changed

8 files changed

+7820
-5021
lines changed

package-lock.json

Lines changed: 7696 additions & 4976 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@
3030
"@wordpress/browserslist-config": "^5.16.0",
3131
"@wordpress/element": "^5.10.0",
3232
"@wordpress/env": "^8.3.0",
33-
"@wordpress/prettier-config": "^2.16.0",
34-
"@wordpress/scripts": "^26.4.0",
33+
"@wordpress/eslint-plugin": "^17.2.0",
34+
"@wordpress/prettier-config": "^2.25.13",
35+
"@wordpress/scripts": "^26.16.0",
3536
"@wordpress/stylelint-config": "^21.16.0",
3637
"babel-plugin-inline-json-import": "^0.3.2",
3738
"husky": "^8.0.3",
3839
"lint-staged": "^13.2.2",
39-
"prettier": "npm:wp-prettier@2.6.2",
40+
"prettier": "npm:wp-prettier@3.0.3",
4041
"simple-git": "^3.18.0"
4142
},
4243
"scripts": {
@@ -52,11 +53,12 @@
5253
"start": "wp-scripts start src/index.js src/plugin-sidebar.js src/wp-org-theme-directory.js",
5354
"update-version": "node update-version-and-changelog.js",
5455
"prepare": "husky install",
55-
"wp-env": "wp-env"
56+
"wp-env": "wp-env",
57+
"test:unit": "wp-scripts test-unit-js --config test/unit/jest.config.js"
5658
},
5759
"lint-staged": {
5860
"*.{js,json,yml}": [
59-
"npx wp-scripts format"
61+
"wp-scripts format"
6062
],
6163
"*.js": [
6264
"npm run lint:js"
@@ -68,7 +70,7 @@
6870
"npm run lint:php"
6971
],
7072
"package.json": [
71-
"npx wp-scripts lint-pkg-json"
73+
"wp-scripts lint-pkg-json"
7274
]
7375
}
7476
}

src/google-fonts/font-variant.js

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect } from '@wordpress/element';
22
import Demo from '../demo-text-input/demo';
3-
import { localizeFontStyle } from '../utils';
3+
import { addQuotesToName, localizeFontStyle } from '../utils';
44

55
function FontVariant( { font, variant, isSelected, handleToggle } ) {
66
const style = variant.includes( 'italic' ) ? 'italic' : 'normal';
@@ -17,21 +17,30 @@ function FontVariant( { font, variant, isSelected, handleToggle } ) {
1717
};
1818

1919
useEffect( () => {
20-
const newFont = new FontFace( font.family, `url( ${ variantUrl } )`, {
21-
style,
22-
weight,
23-
} );
24-
newFont
25-
.load()
26-
.then( function ( loadedFace ) {
20+
const sanitizedFontFamily = addQuotesToName( font.family );
21+
22+
const newFont = new FontFace(
23+
sanitizedFontFamily,
24+
`url( ${ variantUrl } )`,
25+
{
26+
style,
27+
weight,
28+
}
29+
);
30+
31+
const loadNewFont = async () => {
32+
try {
33+
const loadedFace = await newFont.load();
2734
document.fonts.add( loadedFace );
28-
} )
29-
.catch( function ( error ) {
35+
} catch ( error ) {
3036
// TODO: show error in the UI
3137
// eslint-disable-next-line
3238
console.error( error );
33-
} );
34-
}, [ font, variant ] );
39+
}
40+
};
41+
42+
loadNewFont();
43+
}, [ font, style, variant, variantUrl, weight ] );
3544

3645
const formattedFontFamily = font.family.toLowerCase().replace( ' ', '-' );
3746
const fontId = `${ formattedFontFamily }-${ variant }`;
@@ -55,8 +64,10 @@ function FontVariant( { font, variant, isSelected, handleToggle } ) {
5564
<label htmlFor={ fontId }>{ localizeFontStyle( style ) }</label>
5665
</td>
5766
<td className="demo-cell">
67+
{ /* @TODO: associate label with control for accessibility */ }
68+
{ /* eslint-disable-next-line jsx-a11y/label-has-associated-control */ }
5869
<label htmlFor={ fontId }>
59-
<Demo style={ previewStyles } />
70+
<Demo id={ fontId } style={ previewStyles } />
6071
</label>
6172
</td>
6273
</tr>

src/local-fonts/index.js

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { __ } from '@wordpress/i18n';
2-
import { useEffect, useState } from '@wordpress/element';
2+
import { useCallback, useEffect, useState } from '@wordpress/element';
33
import UploadFontForm from './upload-font-form';
44
import './local-fonts.css';
55
import DemoTextInput from '../demo-text-input';
66
import Demo from '../demo-text-input/demo';
77
import { variableAxesToCss } from '../demo-text-input/utils';
88
import BackButton from '../manage-fonts/back-button';
9+
import { addQuotesToName } from '../utils';
910

1011
const INITIAL_FORM_DATA = {
1112
file: null,
@@ -33,18 +34,26 @@ function LocalFonts() {
3334
setAxes( newAxes );
3435
};
3536

36-
const isFormValid = () => {
37-
return (
38-
formData.file && formData.name && formData.weight && formData.style
39-
);
40-
};
37+
const isFormValid = useCallback( () => {
38+
// Check if font name is present and is alphanumeric.
39+
const alphanumericRegex = /^[a-z0-9 ]+$/i;
40+
41+
if (
42+
! formData.name ||
43+
( formData.name && ! alphanumericRegex.test( formData.name ) )
44+
) {
45+
return false;
46+
}
47+
48+
return formData.file && formData.weight && formData.style;
49+
}, [ formData ] );
4150

4251
const demoStyle = () => {
4352
if ( ! isFormValid() ) {
4453
return {};
4554
}
4655
const style = {
47-
fontFamily: formData.name,
56+
fontFamily: addQuotesToName( formData.name ),
4857
fontWeight: formData.weight,
4958
fontStyle: formData.style,
5059
};
@@ -54,32 +63,32 @@ function LocalFonts() {
5463
return style;
5564
};
5665

57-
// load the local font in the browser to make the preview work
58-
const onFormDataChange = async () => {
59-
if ( ! isFormValid() ) {
60-
return;
61-
}
66+
useEffect( () => {
67+
// load the local font in the browser to make the preview work
68+
const onFormDataChange = async () => {
69+
if ( ! isFormValid() ) {
70+
return;
71+
}
6272

63-
const data = await formData.file.arrayBuffer();
64-
const newFont = new FontFace( formData.name, data, {
65-
style: formData.style,
66-
weight: formData.weight,
67-
} );
68-
newFont
69-
.load()
70-
.then( function ( loadedFace ) {
73+
const data = await formData.file.arrayBuffer();
74+
const sanitizedFontFamily = addQuotesToName( formData.name );
75+
const newFont = new FontFace( sanitizedFontFamily, data, {
76+
style: formData.style,
77+
weight: formData.weight,
78+
} );
79+
80+
try {
81+
const loadedFace = await newFont.load();
7182
document.fonts.add( loadedFace );
72-
} )
73-
.catch( function ( error ) {
83+
} catch ( error ) {
7484
// TODO: show error in the UI
7585
// eslint-disable-next-line
7686
console.error( error );
77-
} );
78-
};
87+
}
88+
};
7989

80-
useEffect( () => {
8190
onFormDataChange();
82-
}, [ formData ] );
91+
}, [ formData, isFormValid ] );
8392

8493
return (
8594
<div className="layout">

src/test/unit.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { addQuotesToName } from '../utils';
2+
3+
describe( 'addQuotesToName', () => {
4+
const easyFonts = [ 'Roboto' ];
5+
const complicatedFonts = [
6+
'Roboto Mono',
7+
'Open Sans Condensed',
8+
'Exo 2',
9+
'Libre Barcode 128 Text',
10+
'Press Start 2P',
11+
'Rock 3D',
12+
'Rubik 80s Fade',
13+
];
14+
15+
it( 'should add quotes to all font names', () => {
16+
[ ...easyFonts, ...complicatedFonts ].forEach( ( font ) => {
17+
expect( addQuotesToName( font ) ).toEqual( `'${ font }'` );
18+
} );
19+
} );
20+
21+
it( 'should avoid FontFace objects with empty font name', () => {
22+
complicatedFonts.forEach( ( font ) => {
23+
const quoted = addQuotesToName( font );
24+
const fontObject = new FontFace( quoted, {} );
25+
26+
expect( fontObject ).toBeInstanceOf( FontFace );
27+
expect( fontObject.family ).toEqual( quoted );
28+
} );
29+
} );
30+
} );

src/utils.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,13 @@ export async function downloadFile( response ) {
7676
}, 100 );
7777
}
7878
}
79+
80+
/*
81+
* Add quotes to font name.
82+
* @param {string} familyName The font family name.
83+
* @return {string} The font family name with quotes.
84+
*/
85+
86+
export function addQuotesToName( familyName ) {
87+
return `'${ familyName }'`.trim();
88+
}

test/unit/jest.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = {
2+
testEnvironment: 'jsdom',
3+
rootDir: '../../',
4+
testMatch: [ '<rootDir>/src/test/**/*.js' ],
5+
moduleFileExtensions: [ 'js' ],
6+
moduleNameMapper: {
7+
'^@/(.*)$': '<rootDir>/src/$1',
8+
},
9+
setupFiles: [ '<rootDir>/test/unit/setup.js' ],
10+
};

test/unit/setup.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Stub out the FontFace class for tests.
2+
global.FontFace = class {
3+
constructor( family, source ) {
4+
this.family = family;
5+
this.source = source;
6+
}
7+
};

0 commit comments

Comments
 (0)