Skip to content

Commit

Permalink
Footnotes: save numbering through the entity provider (WordPress#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 <[email protected]>
  • 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

0 comments on commit e55465b

Please sign in to comment.