Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AMP compatibility and implement rendering on archive/home pages #32

Merged
merged 7 commits into from
Oct 1, 2020
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.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* Enables MathML math formulas blocks in the editor.
* Uses the MathJax library to render the formulas: https://www.mathjax.org
* Compatible with the [official AMP plugin](https://amp-wp.org/) by rendering [`amp-mathml`](https://amp.dev/documentation/components/amp-mathml/) on [AMP pages](https://amp.dev/).

### What is MathML?

Expand Down
192 changes: 163 additions & 29 deletions mathml-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,64 @@
*
* @package mathml-block
*/

namespace MathMLBlock;

/**
* Enqueue the admin JavaScript assets.
*/
use WP_Block_Type_Registry;
use WP_Scripts;

const BLOCK_NAME = 'mathml/mathmlblock';

const MATHJAX_SCRIPT_HANDLE = 'mathjax';

const MATHJAX_SCRIPT_URL = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js';

/**
* Determine whether the response will be an AMP page.
*
* @return bool
*/
function is_amp() {
return (
( function_exists( 'amp_is_request' ) && \amp_is_request() )
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@westonruter is this the newer 2.0+ preferred way to detect is_amp?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reality it is identical. We just renamed is_amp_endpoint() to amp_is_request() and then added a soft-deprecated is_amp_endpoint() which calls amp_is_request(). This was done just to start using prefixed functions everywhere. We're not going to hard-deprecate it.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 ah, ok so purely a naming convention based change. thanks for clarifying.

||
( function_exists( 'is_amp_endpoint' ) && \is_amp_endpoint() )
);
}

/**
* Register MathJax script.
*
* @param WP_Scripts $scripts Scripts.
*/
function register_mathjax_script( WP_Scripts $scripts ) {

/**
* Filters the MathJax config string.
*
* @param string $config MathHax config.
*/
$config_string = apply_filters( 'mathml_block_mathjax_config', 'TeX-MML-AM_CHTML' );

$src = add_query_arg(
array(
'config' => rawurlencode( $config_string )
),
MATHJAX_SCRIPT_URL
);

$scripts->add( MATHJAX_SCRIPT_HANDLE, $src, array(), null, false );

// Make JavaScript translatable.
$scripts->set_translations( MATHJAX_SCRIPT_HANDLE, 'mathml-block' );
}
add_action( 'wp_default_scripts', __NAMESPACE__ . '\register_mathjax_script' );

/**
* Enqueue the admin JavaScript assets.
*/
function mathml_block_enqueue_scripts() {
wp_enqueue_script( MATHJAX_SCRIPT_HANDLE );

wp_enqueue_script(
'mathml-block',
Expand All @@ -28,45 +80,127 @@ function mathml_block_enqueue_scripts() {
'',
true
);
}
add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\mathml_block_enqueue_scripts' );

// Maka JavaScript translatable.
wp_set_script_translations( 'mathml-block', 'mathml-block' );
/**
* Register block.
*/
function register_block() {
if ( ! function_exists( 'register_block_type' ) ) {
return;
}

// Filter the MathJax config string.
$config_string = apply_filters( 'mathml_block_mathjax_config', 'TeX-MML-AM_CHTML' );
$registry = WP_Block_Type_Registry::get_instance();

wp_enqueue_script(
'mathjax',
'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=' . $config_string
// @todo This can probably be de-duplicated in the JS code with registerBlockType.
$attributes = array(
'formula' => array(
'source' => 'html',
'selector' => 'div',
'type' => 'string',
),
);

if ( $registry->is_registered( BLOCK_NAME ) ) {
$block = $registry->get_registered( BLOCK_NAME );
$block->render_callback = __NAMESPACE__ . '\render_block';
$block->attributes = array_merge( $block->attributes, $attributes );
} else {
register_block_type(
BLOCK_NAME,
[
'render_callback' => __NAMESPACE__ . '\render_block',
'attributes' => $attributes,
]
);
}
}
add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\mathml_block_enqueue_scripts' );
add_action( 'init', __NAMESPACE__ . '\register_block' );

/**
* Potentially enqueue the front end mathjax script, if any mathml blocks are detected in the content.
* Add async attribute to MathJax script tag.
*
* @param string $tag Script tag.
* @param string $handle Script handle.
*
* @return string Script tag.
*/
function potentially_add_front_end_mathjax_script() {
global $post;

// Only apply on singular pages.
if ( ! is_singular() ) {
return;
function add_async_to_mathjax_script_loader_tag( $tag, $handle ) {
if ( MATHJAX_SCRIPT_HANDLE === $handle ) {
$tag = preg_replace( '/(?<=<script\s)/', ' async ', $tag );
}
return $tag;
}

// Check the content for mathml blocks.
$has_mathml_block = strpos( $post->post_content, 'wp:mathml/mathmlblock' );
$has_mathml_inline = strpos( $post->post_content, '<mathml>' );
if ( false === $has_mathml_block && false === $has_mathml_inline ) {
return;
/**
* Render block.
*
* Creates an <amp-mathml> element on AMP responses.
*
* @param array $attributes Attributes.
* @param string $content Content.
*
* @return string Rendered block.
*/
function render_block( $attributes, $content = '' ) {
if ( is_admin() || ! preg_match( '#^(?P<start_div>\s*<div.*?>)(?P<formula>.+)(?P<end_div></div>\s*)$#s', $content, $matches ) ) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this regex do? The goal is to only enqueue the mathjax script on the front end when there are (inline) tags or mathml blocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex is for parsing the $content, in particular to isolate the formula from the start tag and end tag for the enclosed div. This is done not only to extract the formula to supply into the amp-mathml tag, but also in the non-AMP version to be able to inject the scripts inside the block wrapper. This ensures that styles that target blocks that have a certain sibling won't get tripped up by the injected script. The scripts are printed right away instead of waiting for wp_footer in order to minimize the flash of unstyled content.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently the regex needs to be adapted to support inline <mathml> tags per below.

return $content;
}

// Filter the MathJax config string.
$config_string = apply_filters( 'mathml_block_mathjax_config', 'TeX-MML-AM_CHTML' );
if ( is_amp() ) {
static $printed_style = false;
if ( ! $printed_style ) {
// Add same margins as .MJXc-display.
?>
<style class="amp-mathml">
.wp-block-mathml-mathmlblock amp-mathml { margin: 1em 0; }
</style>
<?php
Comment on lines +154 to +159
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was wrong. It caused a bug in legacy Reader mode. See #38.

$printed_style = true;
}

// Enqueue the MathJax script for front end formula display.
wp_register_script( 'mathjax', 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=' . $config_string );
wp_enqueue_script( 'mathjax' );
return sprintf(
'%s<amp-mathml layout="container" data-formula="%s"><span placeholder>%s</span></amp-mathml>%s',
$matches['start_div'],
esc_attr( $matches['formula'] ),
esc_html( $matches['formula'] ),
$matches['end_div']
);
} elseif ( ! wp_script_is( MATHJAX_SCRIPT_HANDLE, 'done' ) ) {
ob_start();
add_filter( 'script_loader_tag', __NAMESPACE__ . '\add_async_to_mathjax_script_loader_tag', 10, 2 );
wp_scripts()->do_items( MATHJAX_SCRIPT_HANDLE );
remove_filter( 'script_loader_tag', __NAMESPACE__ . '\add_async_to_mathjax_script_loader_tag' );
$scripts = ob_get_clean();

$content = $matches['start_div'] . $matches['formula'] . $scripts . $matches['end_div'];
}
return $content;
}

/**
* Filter content to transform inline math.
*
* @param string $content Content.
* @return string Replaced content.
*/
function filter_content( $content ) {
return preg_replace_callback(
'#(?P<start_tag><mathml>)(?P<formula>.+)(?P<end_tag></mathml>)#s',
static function ( $matches ) {
if ( is_amp() ) {
return sprintf(
'<amp-mathml layout="container" data-formula="%s" inline><span placeholder>%s</span></amp-mathml>',
esc_attr( $matches['formula'] ),
esc_html( $matches['formula'] )
);
} else {
wp_enqueue_script( MATHJAX_SCRIPT_HANDLE );
return $matches['start_tag'] . $matches['formula'] . $matches['end_tag'];
}
},
$content
);
}
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\potentially_add_front_end_mathjax_script' );
add_filter( 'the_content', __NAMESPACE__ . '\filter_content', 20 );
2 changes: 2 additions & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ To test a MathML block and enter a formula, for example: `\[x = {-b \pm \sqrt{b^

To test using math formulas inline, type an formula into a block of text, select it and hit the 'M' icon in the control bar. For example: `\( \cos(θ+φ)=\cos(θ)\cos(φ)−\sin(θ)\sin(φ) \)`. _Note: if you are copying and pasting formulas into the rich text editor, switching to HTML/code editor mode is less likely to reformat your pasted formula._

This plugin is compatible with the [official AMP plugin](https://amp-wp.org/) by rendering [`amp-mathml`](https://amp.dev/documentation/components/amp-mathml/) on [AMP pages](https://amp.dev/).

=== Technical Notes ===

* Requires PHP 5.6+.
Expand Down