-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Footnotes: Add block-level caching when parsing content for footnotes (…
- Loading branch information
Showing
4 changed files
with
186 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import getRichTextValuesCached from './get-rich-text-values-cached'; | ||
|
||
const cache = new WeakMap(); | ||
|
||
function getBlockFootnotesOrder( block ) { | ||
if ( ! cache.has( block ) ) { | ||
const content = getRichTextValuesCached( block ).join( '' ); | ||
const newOrder = []; | ||
|
||
// 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 ] ); | ||
} | ||
} | ||
cache.set( block, newOrder ); | ||
} | ||
|
||
return cache.get( block ); | ||
} | ||
|
||
export default function getFootnotesOrder( blocks ) { | ||
return blocks.flatMap( getBlockFootnotesOrder ); | ||
} |
35 changes: 35 additions & 0 deletions
35
packages/core-data/src/footnotes/get-rich-text-values-cached.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { unlock } from '../private-apis'; | ||
|
||
// TODO: The following line should have been: | ||
// | ||
// const unlockedApis = unlock( blockEditorPrivateApis ); | ||
// | ||
// But there are hidden circular dependencies in RNMobile code, specifically in | ||
// certain native components in the `components` package that depend on | ||
// `block-editor`. What follows is a workaround that defers the `unlock` call | ||
// to prevent native code from failing. | ||
// | ||
// Fix once https://github.com/WordPress/gutenberg/issues/52692 is closed. | ||
let unlockedApis; | ||
|
||
const cache = new WeakMap(); | ||
|
||
export default function getRichTextValuesCached( block ) { | ||
if ( ! unlockedApis ) { | ||
unlockedApis = unlock( blockEditorPrivateApis ); | ||
} | ||
|
||
if ( ! cache.has( block ) ) { | ||
const values = unlockedApis.getRichTextValues( [ block ] ); | ||
cache.set( block, values ); | ||
} | ||
return cache.get( block ); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/** | ||
* 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; | ||
} | ||
|
||
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 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, | ||
}; | ||
} |
8da8987
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flaky tests detected in 8da8987.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.
🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6197215955
📝 Reported issues:
/test/e2e/specs/editor/various/multi-block-selection.spec.js