@wordpress/core-data
Version:
Access to and manipulation of core WordPress entities.
144 lines (121 loc) • 3.34 kB
JavaScript
/**
* WordPress dependencies
*/
import { RichTextData, create, toHTMLString } from '@wordpress/rich-text';
/**
* Internal dependencies
*/
import getFootnotesOrder from './get-footnotes-order';
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 newOrder = getFootnotesOrder( blocks );
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 ) {
// Only attempt to update attributes, if attributes is an object.
if (
! attributes ||
Array.isArray( attributes ) ||
typeof attributes !== 'object'
) {
return attributes;
}
attributes = { ...attributes };
for ( const key in attributes ) {
const value = attributes[ key ];
if ( Array.isArray( value ) ) {
attributes[ key ] = value.map( updateAttributes );
continue;
}
// To do, remove support for string values?
if (
typeof value !== 'string' &&
! ( value instanceof RichTextData )
) {
continue;
}
const richTextValue =
typeof value === 'string'
? RichTextData.fromHTMLString( value )
: new RichTextData( value );
let hasFootnotes = false;
richTextValue.replacements.forEach( ( replacement ) => {
if ( replacement.type === 'core/footnote' ) {
const id = replacement.attributes[ 'data-fn' ];
const index = newOrder.indexOf( id );
// The innerHTML contains the count wrapped in a link.
const countValue = create( {
html: replacement.innerHTML,
} );
countValue.text = String( index + 1 );
countValue.formats = Array.from(
{ length: countValue.text.length },
() => countValue.formats[ 0 ]
);
countValue.replacements = Array.from(
{ length: countValue.text.length },
() => countValue.replacements[ 0 ]
);
replacement.innerHTML = toHTMLString( {
value: countValue,
} );
hasFootnotes = true;
}
} );
if ( hasFootnotes ) {
attributes[ key ] =
typeof value === 'string'
? richTextValue.toHTMLString()
: richTextValue;
}
}
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 attributes 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,
};
}