Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This plugin allows you to:
- Create a new style variation
- Export a theme
- Save user changed templates and styles to the active theme
- Expand the typography controls in the block inspector while authoring a theme (opt-in, under Editor preferences)

All newly created themes or style variations will include changes made within the WordPress Editor.

Expand Down
72 changes: 72 additions & 0 deletions src/editor-enhancements/expand-typography-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* WordPress dependencies
*/
// eslint-disable-next-line import/no-unresolved -- Provided as an external at build time via @wordpress/dependency-extraction-webpack-plugin.
import { addFilter } from '@wordpress/hooks';
import { select } from '@wordpress/data';
import { store as preferencesStore } from '@wordpress/preferences';

// Mapping verified against
// gutenberg/packages/block-editor/src/components/global-styles/typography-panel.js
// fontWeight and fontStyle collapse into the single "fontAppearance" control.
export const TYPOGRAPHY_SUPPORT_TO_CONTROL = {
fontSize: 'fontSize',
lineHeight: 'lineHeight',
textAlign: 'textAlign',
textColumns: 'textColumns',
textIndent: 'textIndent',
__experimentalFontFamily: 'fontFamily',
__experimentalLetterSpacing: 'letterSpacing',
__experimentalTextDecoration: 'textDecoration',
__experimentalTextTransform: 'textTransform',
__experimentalWritingMode: 'writingMode',
__experimentalFontWeight: 'fontAppearance',
__experimentalFontStyle: 'fontAppearance',
};

export const PREFERENCE_SCOPE = 'create-block-theme';
export const PREFERENCE_KEY = 'expandAllTypographyControls';

export function expandTypographyDefaults( typographySupport ) {
if ( ! typographySupport || typeof typographySupport !== 'object' ) {
return typographySupport;
}
const defaults = {};
for ( const [ supportKey, controlKey ] of Object.entries(
TYPOGRAPHY_SUPPORT_TO_CONTROL
) ) {
if ( typographySupport[ supportKey ] ) {
defaults[ controlKey ] = true;
}
}
return {
...typographySupport,
__experimentalDefaultControls: {
...( typographySupport.__experimentalDefaultControls ?? {} ),
...defaults,
},
};
}

addFilter(
'blocks.registerBlockType',
'create-block-theme/expand-typography-controls',
( settings ) => {
const enabled = select( preferencesStore )?.get?.(
PREFERENCE_SCOPE,
PREFERENCE_KEY
);
if ( ! enabled || ! settings?.supports?.typography ) {
return settings;
}
return {
...settings,
supports: {
...settings.supports,
typography: expandTypographyDefaults(
settings.supports.typography
),
},
};
}
);
72 changes: 72 additions & 0 deletions src/editor-sidebar/editor-preferences-panel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useSelect, useDispatch } from '@wordpress/data';
import { store as preferencesStore } from '@wordpress/preferences';
import {
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalVStack as VStack,
Card,
CardBody,
CheckboxControl,
} from '@wordpress/components';

/**
* Internal dependencies
*/
import ScreenHeader from './screen-header';
import {
PREFERENCE_SCOPE,
PREFERENCE_KEY,
} from '../editor-enhancements/expand-typography-controls';

export const EditorPreferencesPanel = () => {
const expandAllTypographyControls = useSelect(
( select ) =>
!! select( preferencesStore ).get(
PREFERENCE_SCOPE,
PREFERENCE_KEY
),
[]
);

const { set: setPreference } = useDispatch( preferencesStore );

const handleToggle = ( value ) => {
setPreference( PREFERENCE_SCOPE, PREFERENCE_KEY, value );
// eslint-disable-next-line no-alert
window.alert(
__(
'Preference updated. The editor will now reload.',
'create-block-theme'
)
);
window.location.reload();
};

return (
<Card size="small" isBorderless>
<ScreenHeader
title={ __( 'Editor preferences', 'create-block-theme' ) }
/>
<CardBody>
<VStack spacing={ 4 }>
<CheckboxControl
__nextHasNoMarginBottom
label={ __(
'Expand typography controls in the block inspector',
'create-block-theme'
) }
help={ __(
'Shows the controls in the typography panel by default instead of hiding them behind the ellipsis menu. Applies in the Site Editor only. The editor will reload when you change this.',
'create-block-theme'
) }
checked={ expandAllTypographyControls }
onChange={ handleToggle }
/>
</VStack>
</CardBody>
</Card>
);
};
27 changes: 27 additions & 0 deletions src/plugin-sidebar.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/**
* Internal dependencies
*/
// Side-effect import — must load before block registration so the
// registerBlockType filter is in place when blocks register.
import './editor-enhancements/expand-typography-controls';

/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -39,6 +46,7 @@ import {
blockMeta,
help,
trash,
cog,
} from '@wordpress/icons';

/**
Expand All @@ -55,6 +63,7 @@ import { downloadExportedTheme } from './resolvers';
import downloadFile from './utils/download-file';
import AboutPlugin from './editor-sidebar/about';
import ResetTheme from './editor-sidebar/reset-theme';
import { EditorPreferencesPanel } from './editor-sidebar/editor-preferences-panel';
import './plugin-styles.scss';

function PluginSidebarItem( { icon, path, children, onClick } ) {
Expand Down Expand Up @@ -229,6 +238,20 @@ const CreateBlockThemePlugin = () => {
</VStack>
</CardBody>
<CardDivider />
<CardBody>
<VStack spacing={ 0 }>
<PluginSidebarItem
path="/editor-preferences"
icon={ cog }
>
{ __(
'Editor preferences',
'create-block-theme'
) }
</PluginSidebarItem>
</VStack>
</CardBody>
<CardDivider />
<CardBody>
<VStack spacing={ 0 }>
<PluginSidebarItem
Expand Down Expand Up @@ -329,6 +352,10 @@ const CreateBlockThemePlugin = () => {
<NavigatorScreen path="/reset">
<ResetTheme />
</NavigatorScreen>

<NavigatorScreen path="/editor-preferences">
<EditorPreferencesPanel />
</NavigatorScreen>
</NavigatorProvider>
</PluginSidebar>

Expand Down
173 changes: 173 additions & 0 deletions src/test/expand-typography-controls.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// The module under test imports WordPress packages that are provided as
// externals at build time and are not installed as npm dependencies. Mock them
// so Jest can resolve the module to test the pure mapping function.
jest.mock( '@wordpress/hooks', () => ( { addFilter: jest.fn() } ), {
virtual: true,
} );
jest.mock( '@wordpress/data', () => ( { select: jest.fn() } ), {
virtual: true,
} );
jest.mock( '@wordpress/preferences', () => ( { store: 'core/preferences' } ), {
virtual: true,
} );

/**
* Internal dependencies
*/
// eslint-disable-next-line import/first
import {
expandTypographyDefaults,
TYPOGRAPHY_SUPPORT_TO_CONTROL,
} from '../editor-enhancements/expand-typography-controls';

describe( 'expandTypographyDefaults', () => {
it( 'returns input unchanged when null', () => {
expect( expandTypographyDefaults( null ) ).toBe( null );
} );

it( 'returns input unchanged when undefined', () => {
expect( expandTypographyDefaults( undefined ) ).toBe( undefined );
} );

it( 'returns input unchanged when not an object', () => {
expect( expandTypographyDefaults( true ) ).toBe( true );
} );

it( 'yields an empty defaults map when no supports are enabled', () => {
expect( expandTypographyDefaults( {} ) ).toEqual( {
__experimentalDefaultControls: {},
} );
} );

it( 'maps fontSize to fontSize', () => {
expect( expandTypographyDefaults( { fontSize: true } ) ).toEqual( {
fontSize: true,
__experimentalDefaultControls: { fontSize: true },
} );
} );

it( 'maps __experimentalFontFamily to fontFamily', () => {
expect(
expandTypographyDefaults( { __experimentalFontFamily: true } )
).toEqual( {
__experimentalFontFamily: true,
__experimentalDefaultControls: { fontFamily: true },
} );
} );

it( 'maps __experimentalFontWeight alone to fontAppearance', () => {
expect(
expandTypographyDefaults( { __experimentalFontWeight: true } )
).toEqual( {
__experimentalFontWeight: true,
__experimentalDefaultControls: { fontAppearance: true },
} );
} );

it( 'maps __experimentalFontStyle alone to fontAppearance', () => {
expect(
expandTypographyDefaults( { __experimentalFontStyle: true } )
).toEqual( {
__experimentalFontStyle: true,
__experimentalDefaultControls: { fontAppearance: true },
} );
} );

it( 'collapses both weight and style into a single fontAppearance entry', () => {
const result = expandTypographyDefaults( {
__experimentalFontWeight: true,
__experimentalFontStyle: true,
} );
expect( result.__experimentalDefaultControls ).toEqual( {
fontAppearance: true,
} );
expect(
Object.keys( result.__experimentalDefaultControls ).length
).toBe( 1 );
} );

it( 'ignores support keys that are falsy', () => {
expect(
expandTypographyDefaults( {
fontSize: true,
lineHeight: false,
textAlign: undefined,
} )
).toEqual( {
fontSize: true,
lineHeight: false,
textAlign: undefined,
__experimentalDefaultControls: { fontSize: true },
} );
} );

it( 'maps a full realistic supports.typography shape to every expected control', () => {
const fullShape = {
fontSize: true,
lineHeight: true,
textAlign: true,
textColumns: true,
textIndent: true,
__experimentalFontFamily: true,
__experimentalLetterSpacing: true,
__experimentalTextDecoration: true,
__experimentalTextTransform: true,
__experimentalWritingMode: true,
__experimentalFontWeight: true,
__experimentalFontStyle: true,
};
expect(
expandTypographyDefaults( fullShape ).__experimentalDefaultControls
).toEqual( {
fontSize: true,
lineHeight: true,
textAlign: true,
textColumns: true,
textIndent: true,
fontFamily: true,
letterSpacing: true,
textDecoration: true,
textTransform: true,
writingMode: true,
fontAppearance: true,
} );
} );

it( 'preserves unknown keys already set in __experimentalDefaultControls', () => {
const input = {
fontSize: true,
__experimentalDefaultControls: {
fontSize: false,
someFutureControl: true,
},
};
const result = expandTypographyDefaults( input );
expect( result.__experimentalDefaultControls ).toEqual( {
fontSize: true,
someFutureControl: true,
} );
} );

it( 'preserves other keys on the typography supports object', () => {
const input = {
fontSize: true,
customKey: 'untouched',
};
const result = expandTypographyDefaults( input );
expect( result.customKey ).toBe( 'untouched' );
expect( result.__experimentalDefaultControls ).toEqual( {
fontSize: true,
} );
} );
} );

describe( 'TYPOGRAPHY_SUPPORT_TO_CONTROL', () => {
it( 'merges fontWeight and fontStyle into fontAppearance', () => {
expect( TYPOGRAPHY_SUPPORT_TO_CONTROL.__experimentalFontWeight ).toBe(
'fontAppearance'
);
expect( TYPOGRAPHY_SUPPORT_TO_CONTROL.__experimentalFontStyle ).toBe(
'fontAppearance'
);
} );
} );
Loading