Skip to content

Commit

Permalink
(Prelude) Factor out function updateFootnotes
Browse files Browse the repository at this point in the history
  • Loading branch information
mcsf committed Jul 12, 2023
1 parent a508633 commit 60d9dbc
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 125 deletions.
127 changes: 2 additions & 125 deletions packages/core-data/src/entity-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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 =
/(<sup[^>]+data-fn="([^"]+)"[^>]*><a[^>]*>)[\d*]*<\/a><\/sup>/g;

attributes[ key ] = value.replace(
regex,
( match, opening, fnId ) => {
const index = newOrder.indexOf( fnId );
return `${ opening }${ index + 1 }</a></sup>`;
}
);

const compatRegex =
/<a[^>]+data-fn="([^"]+)"[^>]*>\*<\/a>/g;

attributes[ key ] = attributes[ key ].replace(
compatRegex,
( match, fnId ) => {
const index = newOrder.indexOf( fnId );
return `<sup data-fn="${ fnId }" class="fn"><a href="#${ fnId }" id="${ fnId }-link">${
index + 1
}</a></sup>`;
}
);
}

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 ]
);

Expand Down
128 changes: 128 additions & 0 deletions packages/core-data/src/footnotes/index.js
Original file line number Diff line number Diff line change
@@ -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 =
/(<sup[^>]+data-fn="([^"]+)"[^>]*><a[^>]*>)[\d*]*<\/a><\/sup>/g;

attributes[ key ] = value.replace(
regex,
( match, opening, fnId ) => {
const index = newOrder.indexOf( fnId );
return `${ opening }${ index + 1 }</a></sup>`;
}
);

const compatRegex = /<a[^>]+data-fn="([^"]+)"[^>]*>\*<\/a>/g;

attributes[ key ] = attributes[ key ].replace(
compatRegex,
( match, fnId ) => {
const index = newOrder.indexOf( fnId );
return `<sup data-fn="${ fnId }" class="fn"><a href="#${ fnId }" id="${ fnId }-link">${
index + 1
}</a></sup>`;
}
);
}

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,
};
}

0 comments on commit 60d9dbc

Please sign in to comment.