diff --git a/includes/class-newspack-newsletters-renderer.php b/includes/class-newspack-newsletters-renderer.php
index 20b7c54cf..b58ac2b68 100644
--- a/includes/class-newspack-newsletters-renderer.php
+++ b/includes/class-newspack-newsletters-renderer.php
@@ -20,6 +20,20 @@ final class Newspack_Newsletters_Renderer {
*/
protected static $color_palette = null;
+ /**
+ * The header font.
+ *
+ * @var String
+ */
+ protected static $font_header = null;
+
+ /**
+ * The body font.
+ *
+ * @var String
+ */
+ protected static $font_body = null;
+
/**
* Convert a list to HTML attributes.
*
@@ -191,6 +205,8 @@ private static function render_mjml_component( $block, $is_in_column = false, $i
)
);
+ $font_family = 'core/heading' === $block_name ? self::$font_header : self::$font_body;
+
switch ( $block_name ) {
/**
* Paragraph, List, Heading blocks.
@@ -204,6 +220,7 @@ private static function render_mjml_component( $block, $is_in_column = false, $i
'padding' => '0',
'line-height' => '1.8',
'font-size' => '16px',
+ 'font-family' => $font_family,
),
$attrs
);
@@ -267,9 +284,10 @@ private static function render_mjml_component( $block, $is_in_column = false, $i
if ( $figcaption ) {
$caption_attrs = array(
- 'align' => 'center',
- 'color' => '#555d66',
- 'font-size' => '13px',
+ 'align' => 'center',
+ 'color' => '#555d66',
+ 'font-size' => '13px',
+ 'font-family' => $font_family,
);
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$markup .= '' . $figcaption->wholeText . '';
@@ -300,6 +318,7 @@ private static function render_mjml_component( $block, $is_in_column = false, $i
'href' => $anchor->getAttribute( 'href' ),
'border-radius' => $border_radius . 'px',
'font-size' => '18px',
+ 'font-family' => $font_family,
// Default color - will be replaced by get_colors if there are colors set.
'color' => $is_outlined ? '#32373c' : '#fff',
);
@@ -517,9 +536,17 @@ private static function render_mjml_component( $block, $is_in_column = false, $i
*/
private static function render_mjml( $post ) {
self::$color_palette = get_post_meta( $post->ID, 'color_palette', true );
- $title = $post->post_title;
- $blocks = parse_blocks( $post->post_content );
- $body = '';
+ self::$font_header = get_post_meta( $post->ID, 'font_header', true );
+ self::$font_body = get_post_meta( $post->ID, 'font_body', true );
+ if ( ! in_array( self::$font_header, Newspack_Newsletters::$supported_fonts ) ) {
+ self::$font_header = 'Arial';
+ }
+ if ( ! in_array( self::$font_body, Newspack_Newsletters::$supported_fonts ) ) {
+ self::$font_body = 'Georgia';
+ }
+ $title = $post->post_title;
+ $blocks = parse_blocks( $post->post_content );
+ $body = '';
foreach ( $blocks as $block ) {
$block_content = self::render_mjml_component( $block );
if ( ! empty( $block_content ) ) {
diff --git a/includes/class-newspack-newsletters.php b/includes/class-newspack-newsletters.php
index 725cbd20f..de5b14127 100644
--- a/includes/class-newspack-newsletters.php
+++ b/includes/class-newspack-newsletters.php
@@ -18,6 +18,22 @@ final class Newspack_Newsletters {
const NEWSPACK_NEWSLETTERS_CPT = 'newspack_nl_cpt';
+ /**
+ * Supported fonts.
+ *
+ * @var array
+ */
+ public static $supported_fonts = [
+ 'Arial, Helvetica, sans-serif',
+ 'Tahoma, sans-serif',
+ 'Trebuchet MS, sans-serif',
+ 'Verdana, sans-serif',
+ 'Georgia, serif',
+ 'Palatino, serif',
+ 'Times New Roman, serif',
+ 'Courier, monospace',
+ ];
+
/**
* The single instance of the class.
*
@@ -103,6 +119,28 @@ public static function register_meta() {
'auth_callback' => '__return_true',
]
);
+ \register_meta(
+ 'post',
+ 'font_header',
+ [
+ 'object_subtype' => self::NEWSPACK_NEWSLETTERS_CPT,
+ 'show_in_rest' => true,
+ 'type' => 'string',
+ 'single' => true,
+ 'auth_callback' => '__return_true',
+ ]
+ );
+ \register_meta(
+ 'post',
+ 'font_body',
+ [
+ 'object_subtype' => self::NEWSPACK_NEWSLETTERS_CPT,
+ 'show_in_rest' => true,
+ 'type' => 'string',
+ 'single' => true,
+ 'auth_callback' => '__return_true',
+ ]
+ );
/**
* The default color palette lives in the editor frontend and is not
* retrievable on the backend. The workaround is to set it as post meta
@@ -292,6 +330,81 @@ public static function rest_api_init() {
],
]
);
+ \register_rest_route(
+ 'newspack-newsletters/v1/',
+ 'typography/(?P[\a-z]+)',
+ [
+ 'methods' => \WP_REST_Server::EDITABLE,
+ 'callback' => [ __CLASS__, 'api_set_typography' ],
+ 'permission_callback' => [ __CLASS__, 'api_administration_permissions_check' ],
+ 'args' => [
+ 'id' => [
+ 'validate_callback' => [ __CLASS__, 'validate_newsletter_id' ],
+ 'sanitize_callback' => 'absint',
+ ],
+ 'key' => [
+ 'validate_callback' => [ __CLASS__, 'validate_newsletter_typography_key' ],
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'value' => [
+ 'validate_callback' => [ __CLASS__, 'validate_newsletter_typography_value' ],
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Set typography meta.
+ * The save_post action fires before post meta is updated.
+ * This causes newsletters to be synced to the ESP before recent changes to custom fields have been recorded,
+ * which leads to incorrect rendering. This is addressed through custom endpoints to update the typography fields
+ * as soon as they are changed in the editor, so that the changes are available the next time sync to ESP occurs.
+ *
+ * @param WP_REST_Request $request API request object.
+ */
+ public static function api_set_typography( $request ) {
+ $id = $request['id'];
+ $key = $request['key'];
+ $value = $request['value'];
+ update_post_meta( $id, $key, $value );
+ }
+
+ /**
+ * Validate ID is a Newsletter post type.
+ *
+ * @param int $id Post ID.
+ */
+ public static function validate_newsletter_id( $id ) {
+ return self::NEWSPACK_NEWSLETTERS_CPT === get_post_type( $id );
+ }
+
+ /**
+ * Validate typography key.
+ *
+ * @param String $key Meta key.
+ */
+ public static function validate_newsletter_typography_key( $key ) {
+ return in_array(
+ $key,
+ [
+ 'font_header',
+ 'font_body',
+ ]
+ );
+ }
+
+ /**
+ * Validate typography value (font name).
+ *
+ * @param String $key Meta value.
+ */
+ public static function validate_newsletter_typography_value( $key ) {
+ return in_array(
+ $key,
+ self::$supported_fonts
+ );
}
/**
diff --git a/src/components/select-control-with-optgroup/index.js b/src/components/select-control-with-optgroup/index.js
new file mode 100644
index 000000000..040763425
--- /dev/null
+++ b/src/components/select-control-with-optgroup/index.js
@@ -0,0 +1,77 @@
+/**
+ * External dependencies
+ */
+import { isEmpty } from 'lodash';
+
+/**
+ * WordPress dependencies
+ */
+import { useInstanceId } from '@wordpress/compose';
+import { BaseControl } from '@wordpress/components';
+
+/**
+ * SelectControl with optgroup support
+ */
+export default function SelectControlWithOptGroup( {
+ help,
+ label,
+ multiple = false,
+ onChange,
+ optgroups = [],
+ className,
+ hideLabelFromVision,
+ ...props
+} ) {
+ const instanceId = useInstanceId( SelectControlWithOptGroup );
+ const id = `inspector-select-control-${ instanceId }`;
+ const onChangeValue = event => {
+ if ( multiple ) {
+ const selectedOptions = [ ...event.target.options ].filter( ( { selected } ) => selected );
+ const newValues = selectedOptions.map( ( { value } ) => value );
+ onChange( newValues );
+ return;
+ }
+ onChange( event.target.value );
+ };
+
+ // Disable reason: A select with an onchange throws a warning
+
+ if ( isEmpty( optgroups ) ) {
+ return null;
+ }
+
+ /* eslint-disable jsx-a11y/no-onchange */
+ return (
+
+
+
+ );
+ /* eslint-enable jsx-a11y/no-onchange */
+}
diff --git a/src/editor/style.scss b/src/editor/style.scss
index 928bb94d0..35c0273ae 100644
--- a/src/editor/style.scss
+++ b/src/editor/style.scss
@@ -1,3 +1,8 @@
+:root {
+ --header-font: arial, sans-serif;
+ --body-font: georgia, serif;
+}
+
.wp-block {
max-width: 600px;
padding: 20px;
@@ -11,7 +16,27 @@
}
*:not( code ) {
- font-family: Ubuntu, Helvetica, Arial, sans-serif !important;
+ font-family: var( --body-font );
+ }
+
+ .newspack-posts-inserter__header span,
+ .components-button,
+ .block-editor-block-list__block .components-placeholder,
+ .block-editor-block-list__block .components-placeholder div {
+ font-family: 'Noto Serif', helvetica, arial, sans-serif;
+ }
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ font-family: var( --header-font );
+ }
+
+ a {
+ font-family: inherit;
}
code {
diff --git a/src/newsletter-editor/index.js b/src/newsletter-editor/index.js
index fc62c0138..a3fe3e267 100644
--- a/src/newsletter-editor/index.js
+++ b/src/newsletter-editor/index.js
@@ -15,6 +15,7 @@ import InitModal from '../components/init-modal';
import Layout from './layout/';
import Sidebar from './sidebar/';
import Testing from './testing/';
+import Typography from './typography/';
import registerEditorPlugin from './editor/';
registerEditorPlugin();
@@ -36,6 +37,12 @@ const NewsletterEdit = ( { layoutId } ) => {
>
+
+
+
{
+ const { editPost } = dispatch( 'core/editor' );
+ return { editPost };
+ } ),
+ withSelect( select => {
+ const { getEditedPostAttribute, getCurrentPostId } = select( 'core/editor' );
+ const meta = getEditedPostAttribute( 'meta' );
+ return {
+ postId: getCurrentPostId(),
+ fontBody: meta.font_body || '',
+ fontHeader: meta.font_header || '',
+ };
+ } ),
+] )( ( { editPost, fontBody, fontHeader, postId } ) => {
+ const updateFontValue = ( key, value ) => {
+ editPost( { meta: { [ key ]: value } } );
+ apiFetch( {
+ data: { key, value },
+ method: 'POST',
+ path: `/newspack-newsletters/v1/typography/${ postId }`,
+ } );
+ };
+ useEffect(() => {
+ document.documentElement.style.setProperty( '--body-font', fontBody );
+ }, [ fontBody ]);
+ useEffect(() => {
+ document.documentElement.style.setProperty( '--header-font', fontHeader );
+ }, [ fontHeader ]);
+ return (
+
+ updateFontValue( 'font_header', value ) }
+ />
+ updateFontValue( 'font_body', value ) }
+ />
+
+ );
+} );