From 60d9dbc29da680f728181d48210c8e69be69f105 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca <150562+mcsf@users.noreply.github.com> Date: Wed, 12 Jul 2023 16:07:34 +0100 Subject: [PATCH] (Prelude) Factor out function updateFootnotes --- packages/core-data/src/entity-provider.js | 127 +-------------------- packages/core-data/src/footnotes/index.js | 128 ++++++++++++++++++++++ 2 files changed, 130 insertions(+), 125 deletions(-) create mode 100644 packages/core-data/src/footnotes/index.js diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index 6cc1e021841b4a..16d15679c0c44d 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -9,20 +9,17 @@ import { } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { parse, __unstableSerializeAndClean } from '@wordpress/blocks'; -import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; /** * Internal dependencies */ import { STORE_NAME } from './name'; -import { unlock } from './private-apis'; +import { updateFootnotesFromMeta } from './footnotes'; /** @typedef {import('@wordpress/blocks').WPBlock} WPBlock */ const EMPTY_ARRAY = []; -let oldFootnotes = {}; - /** * Internal dependencies */ @@ -190,127 +187,7 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { }, [ content ] ); const updateFootnotes = useCallback( - ( _blocks ) => { - const output = { blocks: _blocks }; - if ( ! meta ) return output; - // If meta.footnotes is empty, it means the meta is not registered. - if ( meta.footnotes === undefined ) return output; - - const { getRichTextValues } = unlock( blockEditorPrivateApis ); - const _content = getRichTextValues( _blocks ).join( '' ) || ''; - const newOrder = []; - - // This can be avoided when - // https://github.com/WordPress/gutenberg/pull/43204 lands. We can then - // get the order directly from the rich text values. - if ( _content.indexOf( 'data-fn' ) !== -1 ) { - const regex = /data-fn="([^"]+)"/g; - let match; - while ( ( match = regex.exec( _content ) ) !== null ) { - newOrder.push( match[ 1 ] ); - } - } - - const footnotes = meta.footnotes - ? JSON.parse( meta.footnotes ) - : []; - const currentOrder = footnotes.map( ( fn ) => fn.id ); - - if ( currentOrder.join( '' ) === newOrder.join( '' ) ) - return output; - - const newFootnotes = newOrder.map( - ( fnId ) => - footnotes.find( ( fn ) => fn.id === fnId ) || - oldFootnotes[ fnId ] || { - id: fnId, - content: '', - } - ); - - function updateAttributes( attributes ) { - attributes = { ...attributes }; - - for ( const key in attributes ) { - const value = attributes[ key ]; - - if ( Array.isArray( value ) ) { - attributes[ key ] = value.map( updateAttributes ); - continue; - } - - if ( typeof value !== 'string' ) { - continue; - } - - if ( value.indexOf( 'data-fn' ) === -1 ) { - continue; - } - - // When we store rich text values, this would no longer - // require a regex. - const regex = - /(]+data-fn="([^"]+)"[^>]*>]*>)[\d*]*<\/a><\/sup>/g; - - attributes[ key ] = value.replace( - regex, - ( match, opening, fnId ) => { - const index = newOrder.indexOf( fnId ); - return `${ opening }${ index + 1 }`; - } - ); - - const compatRegex = - /]+data-fn="([^"]+)"[^>]*>\*<\/a>/g; - - attributes[ key ] = attributes[ key ].replace( - compatRegex, - ( match, fnId ) => { - const index = newOrder.indexOf( fnId ); - return `${ - index + 1 - }`; - } - ); - } - - return attributes; - } - - function updateBlocksAttributes( __blocks ) { - return __blocks.map( ( block ) => { - return { - ...block, - attributes: updateAttributes( block.attributes ), - innerBlocks: updateBlocksAttributes( - block.innerBlocks - ), - }; - } ); - } - - // We need to go through all block attributs deeply and update the - // footnote anchor numbering (textContent) to match the new order. - const newBlocks = updateBlocksAttributes( _blocks ); - - oldFootnotes = { - ...oldFootnotes, - ...footnotes.reduce( ( acc, fn ) => { - if ( ! newOrder.includes( fn.id ) ) { - acc[ fn.id ] = fn; - } - return acc; - }, {} ), - }; - - return { - meta: { - ...meta, - footnotes: JSON.stringify( newFootnotes ), - }, - blocks: newBlocks, - }; - }, + ( _blocks ) => updateFootnotesFromMeta( _blocks, meta ), [ meta ] ); diff --git a/packages/core-data/src/footnotes/index.js b/packages/core-data/src/footnotes/index.js new file mode 100644 index 00000000000000..211755f5b42aa7 --- /dev/null +++ b/packages/core-data/src/footnotes/index.js @@ -0,0 +1,128 @@ +/** + * WordPress dependencies + */ +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { unlock } from '../private-apis'; + +const { getRichTextValues } = unlock( blockEditorPrivateApis ); + +let oldFootnotes = {}; + +export function updateFootnotesFromMeta( blocks, meta ) { + const output = { blocks }; + if ( ! meta ) return output; + // If meta.footnotes is empty, it means the meta is not registered. + if ( meta.footnotes === undefined ) return output; + + const _content = getRichTextValues( blocks ).join( '' ) || ''; + const newOrder = []; + + // This can be avoided when + // https://github.com/WordPress/gutenberg/pull/43204 lands. We can then + // get the order directly from the rich text values. + if ( _content.indexOf( 'data-fn' ) !== -1 ) { + const regex = /data-fn="([^"]+)"/g; + let match; + while ( ( match = regex.exec( _content ) ) !== null ) { + newOrder.push( match[ 1 ] ); + } + } + + const footnotes = meta.footnotes ? JSON.parse( meta.footnotes ) : []; + const currentOrder = footnotes.map( ( fn ) => fn.id ); + + if ( currentOrder.join( '' ) === newOrder.join( '' ) ) return output; + + const newFootnotes = newOrder.map( + ( fnId ) => + footnotes.find( ( fn ) => fn.id === fnId ) || + oldFootnotes[ fnId ] || { + id: fnId, + content: '', + } + ); + + function updateAttributes( attributes ) { + attributes = { ...attributes }; + + for ( const key in attributes ) { + const value = attributes[ key ]; + + if ( Array.isArray( value ) ) { + attributes[ key ] = value.map( updateAttributes ); + continue; + } + + if ( typeof value !== 'string' ) { + continue; + } + + if ( value.indexOf( 'data-fn' ) === -1 ) { + continue; + } + + // When we store rich text values, this would no longer + // require a regex. + const regex = + /(]+data-fn="([^"]+)"[^>]*>]*>)[\d*]*<\/a><\/sup>/g; + + attributes[ key ] = value.replace( + regex, + ( match, opening, fnId ) => { + const index = newOrder.indexOf( fnId ); + return `${ opening }${ index + 1 }`; + } + ); + + const compatRegex = /]+data-fn="([^"]+)"[^>]*>\*<\/a>/g; + + attributes[ key ] = attributes[ key ].replace( + compatRegex, + ( match, fnId ) => { + const index = newOrder.indexOf( fnId ); + return `${ + index + 1 + }`; + } + ); + } + + return attributes; + } + + function updateBlocksAttributes( __blocks ) { + return __blocks.map( ( block ) => { + return { + ...block, + attributes: updateAttributes( block.attributes ), + innerBlocks: updateBlocksAttributes( block.innerBlocks ), + }; + } ); + } + + // We need to go through all block attributs deeply and update the + // footnote anchor numbering (textContent) to match the new order. + const newBlocks = updateBlocksAttributes( blocks ); + + oldFootnotes = { + ...oldFootnotes, + ...footnotes.reduce( ( acc, fn ) => { + if ( ! newOrder.includes( fn.id ) ) { + acc[ fn.id ] = fn; + } + return acc; + }, {} ), + }; + + return { + meta: { + ...meta, + footnotes: JSON.stringify( newFootnotes ), + }, + blocks: newBlocks, + }; +}