Skip to content

Commit

Permalink
Footnotes: save numbering through the entity provider (#52423)
Browse files Browse the repository at this point in the history
* Footnotes: save numbering through the entity provider

* Add sup so no styling is needed at all

* Migrate old format

* Restore old styles, fix nested attribute queries

* Fix anchor selection

* Migrate markup in entity provider instead

* Fix tests

* Fix typo

* Fix comment

---------

Co-authored-by: Miguel Fonseca <150562+mcsf@users.noreply.github.com>
  • Loading branch information
ellatrix and mcsf authored Jul 10, 2023
1 parent ceba95d commit e55465b
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 24 deletions.
8 changes: 2 additions & 6 deletions packages/block-library/src/footnotes/format.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,9 @@ import { name } from './block.json';
export const formatName = 'core/footnote';
export const format = {
title: __( 'Footnote' ),
tagName: 'a',
tagName: 'sup',
className: 'fn',
attributes: {
id: 'id',
href: 'href',
'data-fn': 'data-fn',
},
contentEditable: false,
Expand All @@ -50,11 +48,9 @@ export const format = {
{
type: formatName,
attributes: {
href: '#' + id,
id: `${ id }-link`,
'data-fn': id,
},
innerHTML: '*',
innerHTML: `<a href="#${ id }" id="${ id }-link">*</a>`,
},
value.end,
value.end
Expand Down
6 changes: 4 additions & 2 deletions packages/block-library/src/footnotes/style.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// These styles are for backwards compatibility with the old footnotes anchors.
// Can be removed in the future.
.editor-styles-wrapper,
.entry-content {
counter-reset: footnotes;
}

[data-fn].fn {
a[data-fn].fn {
vertical-align: super;
font-size: smaller;
counter-increment: footnotes;
Expand All @@ -12,7 +14,7 @@
text-indent: -9999999px;
}

[data-fn].fn::after {
a[data-fn].fn::after {
content: "[" counter(footnotes) "]";
text-indent: 0;
float: left;
Expand Down
77 changes: 72 additions & 5 deletions packages/core-data/src/entity-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,10 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {

const updateFootnotes = useCallback(
( _blocks ) => {
if ( ! meta ) return;
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 {};
if ( meta.footnotes === undefined ) return output;

const { getRichTextValues } = unlock( blockEditorPrivateApis );
const _content = getRichTextValues( _blocks ).join( '' ) || '';
Expand All @@ -215,7 +216,8 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
: [];
const currentOrder = footnotes.map( ( fn ) => fn.id );

if ( currentOrder.join( '' ) === newOrder.join( '' ) ) return;
if ( currentOrder.join( '' ) === newOrder.join( '' ) )
return output;

const newFootnotes = newOrder.map(
( fnId ) =>
Expand All @@ -226,6 +228,71 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
}
);

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 ) => {
Expand All @@ -241,6 +308,7 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
...meta,
footnotes: JSON.stringify( newFootnotes ),
},
blocks: newBlocks,
};
},
[ meta ]
Expand All @@ -258,7 +326,6 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
// to make sure the edit makes the post dirty and creates
// a new undo level.
const edits = {
blocks: newBlocks,
selection,
content: ( { blocks: blocksForSerialization = [] } ) =>
__unstableSerializeAndClean( blocksForSerialization ),
Expand All @@ -282,7 +349,7 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
( newBlocks, options ) => {
const { selection } = options;
const footnotesChanges = updateFootnotes( newBlocks );
const edits = { blocks: newBlocks, selection, ...footnotesChanges };
const edits = { selection, ...footnotesChanges };

editEntityRecord( kind, name, id, edits, { isCached: true } );
},
Expand Down
7 changes: 6 additions & 1 deletion packages/rich-text/src/component/use-select-object.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ export function useSelectObject() {
if ( selection.containsNode( target ) ) return;

const range = ownerDocument.createRange();
// If the target is within a non editable element, select the non
// editable element.
const nodeToSelect = target.isContentEditable
? target
: target.closest( '[contenteditable]' );

range.selectNode( target );
range.selectNode( nodeToSelect );
selection.removeAllRanges();
selection.addRange( range );

Expand Down
4 changes: 4 additions & 0 deletions packages/rich-text/src/to-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ function getNodeByPath( node, path ) {
}

function append( element, child ) {
if ( child.html !== undefined ) {
return ( element.innerHTML += child.html );
}

if ( typeof child === 'string' ) {
child = element.ownerDocument.createTextNode( child );
}
Expand Down
8 changes: 6 additions & 2 deletions packages/rich-text/src/to-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function fromFormat( {
}

return {
type: formatType.tagName === '*' ? tagName : formatType.tagName,
type: tagName || formatType.tagName,
object: formatType.object,
attributes: restoreOnAttributes( elementAttributes, isEditableTree ),
};
Expand Down Expand Up @@ -326,7 +326,11 @@ export function toTree( {
} )
);

if ( innerHTML ) append( pointer, innerHTML );
if ( innerHTML ) {
append( pointer, {
html: innerHTML,
} );
}
} else {
pointer = append(
getParent( pointer ),
Expand Down
16 changes: 8 additions & 8 deletions test/e2e/specs/editor/various/footnotes.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ test.describe( 'Footnotes', () => {
{
name: 'core/paragraph',
attributes: {
content: `second paragraph<a href="#${ id1 }" id="${ id1 }-link" data-fn="${ id1 }" class="fn">*</a>`,
content: `second paragraph<sup data-fn="${ id1 }" class="fn"><a href="#${ id1 }" id="${ id1 }-link">1</a></sup>`,
},
},
{
Expand All @@ -72,13 +72,13 @@ test.describe( 'Footnotes', () => {
{
name: 'core/paragraph',
attributes: {
content: `first paragraph<a href="#${ id2 }" id="${ id2 }-link" data-fn="${ id2 }" class="fn">*</a>`,
content: `first paragraph<sup data-fn="${ id2 }" class="fn"><a href="#${ id2 }" id="${ id2 }-link">1</a></sup>`,
},
},
{
name: 'core/paragraph',
attributes: {
content: `second paragraph<a href="#${ id1 }" id="${ id1 }-link" data-fn="${ id1 }" class="fn">*</a>`,
content: `second paragraph<sup data-fn="${ id1 }" class="fn"><a href="#${ id1 }" id="${ id1 }-link">2</a></sup>`,
},
},
{
Expand Down Expand Up @@ -106,13 +106,13 @@ test.describe( 'Footnotes', () => {
{
name: 'core/paragraph',
attributes: {
content: `second paragraph<a href="#${ id1 }" id="${ id1 }-link" data-fn="${ id1 }" class="fn">*</a>`,
content: `second paragraph<sup data-fn="${ id1 }" class="fn"><a href="#${ id1 }" id="${ id1 }-link">1</a></sup>`,
},
},
{
name: 'core/paragraph',
attributes: {
content: `first paragraph<a href="#${ id2 }" id="${ id2 }-link" data-fn="${ id2 }" class="fn">*</a>`,
content: `first paragraph<sup data-fn="${ id2 }" class="fn"><a href="#${ id2 }" id="${ id2 }-link">2</a></sup>`,
},
},
{
Expand All @@ -138,7 +138,7 @@ test.describe( 'Footnotes', () => {
{
name: 'core/paragraph',
attributes: {
content: `second paragraph<a href="#${ id1 }" id="${ id1 }-link" data-fn="${ id1 }" class="fn">*</a>`,
content: `second paragraph<sup data-fn="${ id1 }" class="fn"><a href="#${ id1 }" id="${ id1 }-link">1</a></sup>`,
},
},
{
Expand Down Expand Up @@ -202,7 +202,7 @@ test.describe( 'Footnotes', () => {
{
name: 'core/list-item',
attributes: {
content: `1<a href="#${ id1 }" id="${ id1 }-link" data-fn="${ id1 }" class="fn">*</a>`,
content: `1<sup data-fn="${ id1 }" class="fn"><a href="#${ id1 }" id="${ id1 }-link">1</a></sup>`,
},
},
],
Expand Down Expand Up @@ -242,7 +242,7 @@ test.describe( 'Footnotes', () => {
{
cells: [
{
content: `1<a href="#${ id1 }" id="${ id1 }-link" data-fn="${ id1 }" class="fn">*</a>`,
content: `1<sup data-fn="${ id1 }" class="fn"><a href="#${ id1 }" id="${ id1 }-link">1</a></sup>`,
tag: 'td',
},
{
Expand Down

1 comment on commit e55465b

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in e55465b.
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/5510049006
📝 Reported issues:

Please sign in to comment.