diff --git a/packages/block-library/src/footnotes/format.js b/packages/block-library/src/footnotes/format.js
index 7c1b190fc42af9..40de6a132ea99d 100644
--- a/packages/block-library/src/footnotes/format.js
+++ b/packages/block-library/src/footnotes/format.js
@@ -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,
@@ -50,11 +48,9 @@ export const format = {
{
type: formatName,
attributes: {
- href: '#' + id,
- id: `${ id }-link`,
'data-fn': id,
},
- innerHTML: '*',
+ innerHTML: `*`,
},
value.end,
value.end
diff --git a/packages/block-library/src/footnotes/style.scss b/packages/block-library/src/footnotes/style.scss
index ad49bc1cf29110..aa7ab8b6951dd3 100644
--- a/packages/block-library/src/footnotes/style.scss
+++ b/packages/block-library/src/footnotes/style.scss
@@ -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;
@@ -12,7 +14,7 @@
text-indent: -9999999px;
}
-[data-fn].fn::after {
+a[data-fn].fn::after {
content: "[" counter(footnotes) "]";
text-indent: 0;
float: left;
diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js
index da048944f14984..6cc1e021841b4a 100644
--- a/packages/core-data/src/entity-provider.js
+++ b/packages/core-data/src/entity-provider.js
@@ -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( '' ) || '';
@@ -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 ) =>
@@ -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 =
+ /(]+data-fn="([^"]+)"[^>]*>]*>)[\d*]*<\/a><\/sup>/g;
+
+ attributes[ key ] = value.replace(
+ regex,
+ ( match, opening, fnId ) => {
+ const index = newOrder.indexOf( fnId );
+ return `${ opening }${ index + 1 }`;
+ }
+ );
+
+ const compatRegex =
+ /]+data-fn="([^"]+)"[^>]*>\*<\/a>/g;
+
+ attributes[ key ] = attributes[ key ].replace(
+ compatRegex,
+ ( match, fnId ) => {
+ const index = newOrder.indexOf( fnId );
+ return `${
+ index + 1
+ }`;
+ }
+ );
+ }
+
+ 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 ) => {
@@ -241,6 +308,7 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
...meta,
footnotes: JSON.stringify( newFootnotes ),
},
+ blocks: newBlocks,
};
},
[ meta ]
@@ -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 ),
@@ -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 } );
},
diff --git a/packages/rich-text/src/component/use-select-object.js b/packages/rich-text/src/component/use-select-object.js
index 9ecc7ed9f147c6..e5db313494f488 100644
--- a/packages/rich-text/src/component/use-select-object.js
+++ b/packages/rich-text/src/component/use-select-object.js
@@ -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 );
diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js
index 828e3a4e3f6cb5..305eebaf3e4a6e 100644
--- a/packages/rich-text/src/to-dom.js
+++ b/packages/rich-text/src/to-dom.js
@@ -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 );
}
diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js
index b390954b796720..8a1c3ff074a554 100644
--- a/packages/rich-text/src/to-tree.js
+++ b/packages/rich-text/src/to-tree.js
@@ -108,7 +108,7 @@ function fromFormat( {
}
return {
- type: formatType.tagName === '*' ? tagName : formatType.tagName,
+ type: tagName || formatType.tagName,
object: formatType.object,
attributes: restoreOnAttributes( elementAttributes, isEditableTree ),
};
@@ -326,7 +326,11 @@ export function toTree( {
} )
);
- if ( innerHTML ) append( pointer, innerHTML );
+ if ( innerHTML ) {
+ append( pointer, {
+ html: innerHTML,
+ } );
+ }
} else {
pointer = append(
getParent( pointer ),
diff --git a/test/e2e/specs/editor/various/footnotes.spec.js b/test/e2e/specs/editor/various/footnotes.spec.js
index 1f2fd33f23f738..376962b5c99ba7 100644
--- a/test/e2e/specs/editor/various/footnotes.spec.js
+++ b/test/e2e/specs/editor/various/footnotes.spec.js
@@ -48,7 +48,7 @@ test.describe( 'Footnotes', () => {
{
name: 'core/paragraph',
attributes: {
- content: `second paragraph*`,
+ content: `second paragraph1`,
},
},
{
@@ -72,13 +72,13 @@ test.describe( 'Footnotes', () => {
{
name: 'core/paragraph',
attributes: {
- content: `first paragraph*`,
+ content: `first paragraph1`,
},
},
{
name: 'core/paragraph',
attributes: {
- content: `second paragraph*`,
+ content: `second paragraph2`,
},
},
{
@@ -106,13 +106,13 @@ test.describe( 'Footnotes', () => {
{
name: 'core/paragraph',
attributes: {
- content: `second paragraph*`,
+ content: `second paragraph1`,
},
},
{
name: 'core/paragraph',
attributes: {
- content: `first paragraph*`,
+ content: `first paragraph2`,
},
},
{
@@ -138,7 +138,7 @@ test.describe( 'Footnotes', () => {
{
name: 'core/paragraph',
attributes: {
- content: `second paragraph*`,
+ content: `second paragraph1`,
},
},
{
@@ -202,7 +202,7 @@ test.describe( 'Footnotes', () => {
{
name: 'core/list-item',
attributes: {
- content: `1*`,
+ content: `11`,
},
},
],
@@ -242,7 +242,7 @@ test.describe( 'Footnotes', () => {
{
cells: [
{
- content: `1*`,
+ content: `11`,
tag: 'td',
},
{