Skip to content

Commit

Permalink
Add AMP compatibility and implement rendering on archive/home pages (#32
Browse files Browse the repository at this point in the history
)
  • Loading branch information
westonruter authored Oct 1, 2020
1 parent 0fa608a commit cdc8138
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 29 deletions.
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() )
||
( 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 ) ) {
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
$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

0 comments on commit cdc8138

Please sign in to comment.