diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 38ad3e2e11bd1..0d951862f0ec6 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -317,6 +317,15 @@ A submission button for forms. ([Source](https://github.com/WordPress/gutenberg/ - **Supports:** - **Attributes:** +## Formula + +Render mathematical formulæ. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/formula)) + +- **Name:** core/formula +- **Category:** text +- **Supports:** align, anchor, spacing (blockGap, margin, padding), ~~html~~ +- **Attributes:** alt, formulaSource + ## Classic Use the classic WordPress editor. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/freeform)) diff --git a/lib/blocks.php b/lib/blocks.php index e1d4622a0f23d..eb2e29b289b5b 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -22,6 +22,7 @@ function gutenberg_reregister_core_block_types() { 'column', 'columns', 'details', + 'formula', 'form-input', 'form-submit-button', 'group', diff --git a/packages/block-library/src/formula/block.json b/packages/block-library/src/formula/block.json new file mode 100644 index 0000000000000..7e058bca03a02 --- /dev/null +++ b/packages/block-library/src/formula/block.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "core/formula", + "title": "Formula", + "category": "text", + "description": "Render mathematical formulæ.", + "textdomain": "default", + "attributes": { + "formulaSource": { + "type": "string", + "selector": "img", + "source": "attribute", + "attribute": "data-formula" + }, + "alt": { + "type": "string", + "selector": "img", + "source": "attribute", + "attribute": "alt" + } + }, + "supports": { + "anchor": true, + "align": true, + "html": false, + "spacing": { + "padding": true, + "margin": [ "top", "bottom" ], + "blockGap": true, + "__experimentalDefaultControls": { + "padding": true, + "blockGap": true + } + } + }, + "editorStyle": "wp-block-formula", + "style": "wp-block-formula" +} diff --git a/packages/block-library/src/formula/edit.js b/packages/block-library/src/formula/edit.js new file mode 100644 index 0000000000000..f1170dfd864f9 --- /dev/null +++ b/packages/block-library/src/formula/edit.js @@ -0,0 +1,97 @@ +/** + * WordPress dependencies + */ +import { useBlockProps, PlainText } from '@wordpress/block-editor'; + +let loading = false; + +const requireMathJaxScript = () => { + if ( loading ) { + return; + } + + loading = true; + const script = document.createElement( 'script' ); + script.id = 'MathJax-script'; + script.setAttribute( 'async', '' ); + script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js'; + document.head.appendChild( script ); +}; + +document.addEventListener( 'readystatechange', () => { + if ( document.readyState === 'complete' ) { + requireMathJaxScript(); + } +} ); + +/** + * Generate an SVG data URI for embedding in an IMG element src. + * + * @param {Element} svg The input SVG + * @return {string} Data URI for safely embedding SVG as a string. + */ +const svgElementToDataURI = ( svg ) => { + return `data:image/svg+xml,${ encodeURIComponent( + svg.childNodes[ 0 ].outerHTML + ) }`; +}; + +export const Edit = ( { attributes, isSelected, setAttributes } ) => { + const blockProps = useBlockProps(); + + return ( +
+
+ { isSelected && ( + <> + + window.MathJax.tex2svgPromise( value ).then( + ( svg ) => { + const errors = + svg.childNodes[ 1 ].querySelectorAll( + 'merror' + ); + + if ( ! errors.length ) { + setAttributes( { + src: svgElementToDataURI( svg ), + formulaSource: value, + errors: null, + } ); + } else { + setAttributes( { + formulaSource: value, + errors: [ ...errors ].map( + ( error ) => + error.textContent + ), + } ); + } + } + ) + } + placeholder="a^2 = b^2 + c^2" + /> + <PlainText + value={ attributes.alt } + onChange={ ( alt ) => setAttributes( { alt } ) } + placeholder="How would you describe your formula if your reader cannot read the symbols?" + /> + </> + ) } + <img src={ attributes.src } alt={ attributes.alt } /> + { attributes.errors?.length && ( + <ul> + { attributes.errors.map( ( error ) => ( + <li key={ error }>{ error }</li> + ) ) } + </ul> + ) } + </figure> + </div> + ); +}; + +export default Edit; diff --git a/packages/block-library/src/formula/index.js b/packages/block-library/src/formula/index.js new file mode 100644 index 0000000000000..77655074a79ab --- /dev/null +++ b/packages/block-library/src/formula/index.js @@ -0,0 +1,31 @@ +/** + * WordPress dependencies + */ + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import Edit from './edit'; +import Save from './save'; +import initBlock from '../utils/init-block'; + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + apiVersion: 3, + icon: 'edit-page', + example: { + attributes: { + formulaSource: + '\\Delta T_{\\text{heatsink}} = 100W\n\\cdot 0.15 \\frac{K}{W} = 15^\\circ C', + alt: "At 100W, the heatsink's temperature will rise by 15 degrees Celsius.", + }, + }, + save: Save, + edit: Edit, +}; + +export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/formula/index.php b/packages/block-library/src/formula/index.php new file mode 100644 index 0000000000000..fcd0ab7df546d --- /dev/null +++ b/packages/block-library/src/formula/index.php @@ -0,0 +1,31 @@ +<?php +/** + * Server-side rendering of the `core/formula` block. + * + * @package WordPress + */ + +/** + * Renders the `core/formula` block on server. + * + * @param array $attributes The block attributes. + * @param string $content The block rendered content. + * + * @return string Returns the formula block markup. + */ +function render_block_core_formula( $attributes, $content ) { + return '<p>Formula!</p>'; +} + +/** + * Registers the `core/cover` block renderer on server. + */ +function register_block_core_formula() { + register_block_type_from_metadata( + __DIR__ . '/formula', + array( + 'render_callback' => 'formula', + ) + ); +} +add_action( 'init', 'register_block_core_formula' ); diff --git a/packages/block-library/src/formula/save.js b/packages/block-library/src/formula/save.js new file mode 100644 index 0000000000000..9d3e1ded01e89 --- /dev/null +++ b/packages/block-library/src/formula/save.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { useBlockProps } from '@wordpress/block-editor'; + +export const Save = ( { attributes } ) => { + const blockProps = useBlockProps.save(); + + return ( + <figure className="wp-block-formula" { ...blockProps }> + <img + src={ attributes.src } + data-formula-source={ attributes.formulaSource } + alt={ attributes.alt } + /> + </figure> + ); +}; + +export default Save; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index e2e0fd9e414ef..d733678c3f3f6 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -49,6 +49,7 @@ import * as details from './details'; import * as embed from './embed'; import * as file from './file'; import * as form from './form'; +import * as formula from './formula'; import * as formInput from './form-input'; import * as formSubmitButton from './form-submit-button'; import * as formSubmissionNotification from './form-submission-notification'; @@ -154,6 +155,7 @@ const getAllBlocks = () => { details, embed, file, + formula, group, html, latestComments,