Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC: Try/autogen heading anchors #31174

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
700c240
auto-generate anchors for heading blocks
aristath Apr 13, 2021
2cd77a5
Merge branch 'trunk' into add/autogenerate-heading-anchors
aristath Apr 14, 2021
aba4a64
Don't use "we" & "us" in comments
aristath Apr 14, 2021
6bb9ab4
account for non-latin anchors
aristath Apr 14, 2021
bf0517c
remove lodash dependency
aristath Apr 14, 2021
9d59e7b
Revert "remove lodash dependency"
aristath Apr 14, 2021
4edac7e
add dom-ready dependency
aristath Apr 14, 2021
6f8a4d6
refactor everything
aristath Apr 15, 2021
279b0e6
Merge branch 'trunk' into add/autogenerate-heading-anchors
aristath Apr 16, 2021
e706aaf
use useSelect
aristath Apr 16, 2021
361ca48
Merge branch 'trunk' into add/autogenerate-heading-anchors
aristath Apr 16, 2021
d2d38d3
This was not removed on purpose
aristath Apr 16, 2021
0492340
Move dummyElement inside getTextWithoutMarkup
aristath Apr 16, 2021
cb80801
Merge branch 'trunk' into add/autogenerate-heading-anchors
aristath Apr 19, 2021
17595a4
Update e2e tests
aristath Apr 19, 2021
6d241e0
Don't use a plain "wp-" as anchor
aristath Apr 19, 2021
483693b
typo
aristath Apr 19, 2021
d85658d
Improve empty string handling
aristath Apr 19, 2021
1c9749f
Improve inline comments
aristath Apr 19, 2021
46bec83
Bugfix anchor generation for empty headings
aristath Apr 19, 2021
71c1632
Update packages/block-library/src/heading/edit.js
aristath Apr 20, 2021
9e722ae
Use blockEditorStore
aristath Apr 20, 2021
84ad8b9
cs
aristath Apr 20, 2021
7260b74
refactor hook
aristath Apr 20, 2021
51a6c68
Merge branch 'trunk' into add/autogenerate-heading-anchors
aristath Apr 20, 2021
3099a0c
Merge branch 'trunk' into add/autogenerate-heading-anchors
aristath Apr 22, 2021
58c060c
Update packages/block-library/src/heading/edit.js
aristath Apr 22, 2021
5effaf3
no need for lodash here, these are pretty simple
aristath Apr 22, 2021
51f7c73
Merge remote-tracking branch 'origin/add/autogenerate-heading-anchors…
aristath Apr 22, 2021
f7e9347
refactor - no prefix
aristath Apr 22, 2021
792cfad
update e2e
aristath Apr 23, 2021
6eccf8c
Merge branch 'trunk' into add/autogenerate-heading-anchors
aristath Apr 23, 2021
6574737
bugfix
aristath Apr 23, 2021
3714ef8
simplify
aristath Apr 23, 2021
b45f050
fix for block transforms
aristath Apr 23, 2021
e2698fa
test
ntsekouras Apr 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
143 changes: 143 additions & 0 deletions packages/block-library/src/heading/autogenerate-anchors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/**
* External dependencies
*/
import { deburr, trim } from 'lodash';

/**
* WordPress dependencies
*/
import { cleanForSlug } from '@wordpress/url';
import { useSelect } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';

/**
* Runs a callback over all blocks, including nested blocks.
*
* @param {Object[]} blocks The blocks.
* @param {Function} callback The callback.
*
* @return {void}
*/
const recurseOverBlocks = ( blocks, callback ) => {
for ( const block of blocks ) {
// eslint-disable-next-line callback-return
callback( block );
if ( block.innerBlocks ) {
recurseOverBlocks( block.innerBlocks, callback );
}
}
};

/**
* Returns the text without markup.
*
* @param {string} text The text.
*
* @return {string} The text without markup.
*/
const getTextWithoutMarkup = ( text ) => {
const dummyElement = document.createElement( 'div' );
dummyElement.innerHTML = text;
return dummyElement.innerText;
};

/**
* Get all heading anchors.
*
* @param {Object} blockList An object containing all blocks.
* @param {string} excludeId A block ID we want to exclude.
*
* @return {string[]} Return an array of anchors.
*/
const getAllHeadingAnchors = ( blockList, excludeId ) => {
const anchors = [];

recurseOverBlocks( blockList, ( block ) => {
if (
block.name === 'core/heading' &&
( ! excludeId || block.clientId !== excludeId ) &&
block.attributes.anchor
) {
anchors.push( block.attributes.anchor );
}
} );

return anchors;
};

/**
* Get the slug from the content.
*
* @param {string} content The block content.
*
* @return {string} Returns the slug.
*/
const getSlug = ( content ) => {
content = getTextWithoutMarkup( content );

// Get the slug.
let slug = cleanForSlug( content );

// If slug is empty, then there is no content, or content is using non-latin characters.
// Try non-latin first.
if ( '' === slug ) {
slug = trim(
deburr( content )
.replace( /[\s\./]+/g, '-' )
.toLowerCase(),
'-'
);
}

return slug;
};

/**
* Generate the anchor for a heading.
*
* @param {string} anchor The heading anchor.
* @param {string} content The block content.
* @param {string[]} allHeadingAnchors An array containing all headings anchors.
*
* @return {string|null} Return the heading anchor.
*/
const generateAnchor = ( anchor, content, allHeadingAnchors ) => {
const slug = getSlug( content );
// If slug is empty, then return null.
// Returning null instead of an empty string allows us to check again when the content changes.
if ( '' === slug ) {
return null;
}

const baseAnchor = slug;
anchor = baseAnchor;
let i = 0;

// If the anchor already exists in another heading, append -i.
while ( allHeadingAnchors.includes( anchor ) ) {
i += 1;
anchor = baseAnchor + '-' + i;
}

return anchor;
};

/**
* Updates the anchor if required.
*
* @param {string} clientId The block's client-ID.
* @param {string} anchor The heading anchor.
* @param {string} content The block content.
*
* @return {string} The anchor.
*/
export default function useGeneratedAnchor( clientId, anchor, content ) {
const allHeadingAnchors = useSelect(
( select ) => {
const allBlocks = select( blockEditorStore ).getBlocks();
return getAllHeadingAnchors( allBlocks, clientId );
},
[ clientId ]
);
return generateAnchor( anchor, content, allHeadingAnchors );
}
30 changes: 29 additions & 1 deletion packages/block-library/src/heading/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ import {
RichText,
useBlockProps,
} from '@wordpress/block-editor';
import { useEffect, useState } from '@wordpress/element';
import { usePrevious } from '@wordpress/compose';

/**
* Internal dependencies
*/
import HeadingLevelDropdown from './heading-level-dropdown';
import useGeneratedAnchor from './autogenerate-anchors';

function HeadingEdit( {
attributes,
Expand All @@ -28,14 +31,39 @@ function HeadingEdit( {
mergedStyle,
clientId,
} ) {
const { textAlign, content, level, placeholder } = attributes;
const { textAlign, content, level, placeholder, anchor } = attributes;
const tagName = 'h' + level;
const blockProps = useBlockProps( {
className: classnames( {
[ `has-text-align-${ textAlign }` ]: textAlign,
} ),
style: mergedStyle,
} );
const updateAnchor = ( newAnchor ) =>
setAttributes( { anchor: newAnchor } );

const generatedAnchor = useGeneratedAnchor(
clientId,
attributes.anchor,
content
);
const [ autoGen, setAutoGen ] = useState(
! anchor || generatedAnchor === anchor
);
const prevAnchor = usePrevious( anchor );

useEffect( () => {
// Here the anchor has been changed from the sidebar input.
setAutoGen( anchor === prevAnchor );
}, [ anchor, prevAnchor ] );

useEffect( () => {
// No generated so do nothing.
if ( ! generatedAnchor ) return;
if ( autoGen && anchor !== generatedAnchor ) {
updateAnchor( generatedAnchor );
}
}, [ autoGen, anchor, generatedAnchor ] );

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@

exports[`Heading can be created by prefixing existing content with number signs and a space 1`] = `
"<!-- wp:heading {\\"level\\":4} -->
<h4>4</h4>
<h4 id=\\"4\\">4</h4>
<!-- /wp:heading -->"
`;

exports[`Heading can be created by prefixing number sign and a space 1`] = `
"<!-- wp:heading {\\"level\\":3} -->
<h3>3</h3>
<h3 id=\\"3\\">3</h3>
<!-- /wp:heading -->"
`;

exports[`Heading should correctly apply custom colors 1`] = `
"<!-- wp:heading {\\"level\\":3,\\"style\\":{\\"color\\":{\\"text\\":\\"#7700ff\\"}}} -->
<h3 class=\\"has-text-color\\" style=\\"color:#7700ff\\">Heading</h3>
<h3 class=\\"has-text-color\\" id=\\"heading\\" style=\\"color:#7700ff\\">Heading</h3>
<!-- /wp:heading -->"
`;

exports[`Heading should correctly apply named colors 1`] = `
"<!-- wp:heading {\\"textColor\\":\\"luminous-vivid-orange\\"} -->
<h2 class=\\"has-luminous-vivid-orange-color has-text-color\\">Heading</h2>
<h2 class=\\"has-luminous-vivid-orange-color has-text-color\\" id=\\"heading\\">Heading</h2>
<!-- /wp:heading -->"
`;

Expand All @@ -30,13 +30,13 @@ exports[`Heading should create a paragraph block above when pressing enter at th
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>a</h2>
<h2 id=\\"a\\">a</h2>
<!-- /wp:heading -->"
`;

exports[`Heading should create a paragraph block below when pressing enter at the end 1`] = `
"<!-- wp:heading -->
<h2>a</h2>
<h2 id=\\"a\\">a</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
Expand All @@ -46,12 +46,12 @@ exports[`Heading should create a paragraph block below when pressing enter at th

exports[`Heading should not work with the list input rule 1`] = `
"<!-- wp:heading -->
<h2>1. H</h2>
<h2 id=\\"1-h\\">1. H</h2>
<!-- /wp:heading -->"
`;

exports[`Heading should work with the format input rules 1`] = `
"<!-- wp:heading -->
<h2><code>code</code></h2>
<h2 id=\\"code\\"><code>code</code></h2>
<!-- /wp:heading -->"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ exports[`Quote can be converted to paragraphs and renders only one paragraph for

exports[`Quote can be created by converting a heading 1`] = `
"<!-- wp:quote -->
<blockquote class=\\"wp-block-quote\\"><p>test</p></blockquote>
<blockquote class=\\"wp-block-quote\\" id=\\"test\\"><p>test</p></blockquote>
<!-- /wp:quote -->"
`;

Expand Down Expand Up @@ -144,7 +144,7 @@ exports[`Quote can be split in the middle and merged back 4`] = `

exports[`Quote is transformed to a heading and a quote if the quote contains a citation 1`] = `
"<!-- wp:heading -->
<h2>one</h2>
<h2 id=\\"one\\">one</h2>
<!-- /wp:heading -->

<!-- wp:quote -->
Expand All @@ -154,7 +154,7 @@ exports[`Quote is transformed to a heading and a quote if the quote contains a c

exports[`Quote is transformed to a heading and a quote if the quote contains multiple paragraphs 1`] = `
"<!-- wp:heading -->
<h2>one</h2>
<h2 id=\\"one\\">one</h2>
<!-- /wp:heading -->

<!-- wp:quote -->
Expand All @@ -164,7 +164,7 @@ exports[`Quote is transformed to a heading and a quote if the quote contains mul

exports[`Quote is transformed to a heading if the quote just contains one paragraph 1`] = `
"<!-- wp:heading -->
<h2>one</h2>
<h2 id=\\"one\\">one</h2>
<!-- /wp:heading -->"
`;

Expand All @@ -176,7 +176,7 @@ exports[`Quote is transformed to an empty heading if the quote is empty 1`] = `

exports[`Quote the resuling quote after transforming to a heading can be transformed again 1`] = `
"<!-- wp:heading -->
<h2>one</h2>
<h2 id=\\"one\\">one</h2>
<!-- /wp:heading -->

<!-- wp:quote -->
Expand All @@ -186,11 +186,11 @@ exports[`Quote the resuling quote after transforming to a heading can be transfo

exports[`Quote the resuling quote after transforming to a heading can be transformed again 2`] = `
"<!-- wp:heading -->
<h2>one</h2>
<h2 id=\\"one\\">one</h2>
<!-- /wp:heading -->

<!-- wp:heading -->
<h2>two</h2>
<h2 id=\\"two\\">two</h2>
<!-- /wp:heading -->

<!-- wp:quote -->
Expand All @@ -200,14 +200,14 @@ exports[`Quote the resuling quote after transforming to a heading can be transfo

exports[`Quote the resuling quote after transforming to a heading can be transformed again 3`] = `
"<!-- wp:heading -->
<h2>one</h2>
<h2 id=\\"one\\">one</h2>
<!-- /wp:heading -->

<!-- wp:heading -->
<h2>two</h2>
<h2 id=\\"two\\">two</h2>
<!-- /wp:heading -->

<!-- wp:heading -->
<h2>cite</h2>
<h2 id=\\"cite\\">cite</h2>
<!-- /wp:heading -->"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`Block Grouping Group creation creates a group from multiple blocks of different types via block transforms 1`] = `
"<!-- wp:group -->
<div class=\\"wp-block-group\\"><!-- wp:heading -->
<h2>Group Heading</h2>
<h2 id=\\"group-heading\\">Group Heading</h2>
<!-- /wp:heading -->

<!-- wp:image -->
Expand Down Expand Up @@ -51,7 +51,7 @@ exports[`Block Grouping Group creation creates a group from multiple blocks of t
exports[`Block Grouping Group creation groups and ungroups multiple blocks of different types via options toolbar 1`] = `
"<!-- wp:group -->
<div class=\\"wp-block-group\\"><!-- wp:heading -->
<h2>Group Heading</h2>
<h2 id=\\"group-heading\\">Group Heading</h2>
<!-- /wp:heading -->

<!-- wp:image -->
Expand All @@ -66,7 +66,7 @@ exports[`Block Grouping Group creation groups and ungroups multiple blocks of di

exports[`Block Grouping Group creation groups and ungroups multiple blocks of different types via options toolbar 2`] = `
"<!-- wp:heading -->
<h2>Group Heading</h2>
<h2 id=\\"group-heading\\">Group Heading</h2>
<!-- /wp:heading -->

<!-- wp:image -->
Expand All @@ -81,7 +81,7 @@ exports[`Block Grouping Group creation groups and ungroups multiple blocks of di
exports[`Block Grouping Preserving selected blocks attributes preserves width alignment settings of selected blocks 1`] = `
"<!-- wp:group {\\"align\\":\\"full\\"} -->
<div class=\\"wp-block-group alignfull\\"><!-- wp:heading -->
<h2>Group Heading</h2>
<h2 id=\\"group-heading\\">Group Heading</h2>
<!-- /wp:heading -->

<!-- wp:image {\\"align\\":\\"full\\"} -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ exports[`Inserting blocks inserts a block in proper place after having clicked \
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>Heading</h2>
<h2 id=\\"heading\\">Heading</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
Expand All @@ -93,7 +93,7 @@ exports[`Inserting blocks inserts a block in proper place after having clicked \
<!-- /wp:cover -->

<!-- wp:heading -->
<h2>Heading</h2>
<h2 id=\\"heading\\">Heading</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
Expand Down