@wordpress/block-library
Version:
Block library for the WordPress editor.
157 lines (145 loc) • 6.16 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useObserveHeadings = useObserveHeadings;
var _es = _interopRequireDefault(require("fast-deep-equal/es6"));
var _data = require("@wordpress/data");
var _dom = require("@wordpress/dom");
var _element = require("@wordpress/element");
var _url = require("@wordpress/url");
var _blockEditor = require("@wordpress/block-editor");
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
function getLatestHeadings(select, clientId) {
var _select$getPermalink, _getBlockAttributes;
const {
getBlockAttributes,
getBlockName,
getBlocksByName,
getClientIdsOfDescendants
} = select(_blockEditor.store);
// FIXME: @wordpress/block-library should not depend on @wordpress/editor.
// Blocks can be loaded into a *non-post* block editor, so to avoid
// declaring @wordpress/editor as a dependency, we must access its
// store by string. When the store is not available, editorSelectors
// will be null, and the block's saved markup will lack permalinks.
// eslint-disable-next-line @wordpress/data-no-store-string-literals
const permalink = (_select$getPermalink = select('core/editor').getPermalink()) !== null && _select$getPermalink !== void 0 ? _select$getPermalink : null;
const isPaginated = getBlocksByName('core/nextpage').length !== 0;
const {
onlyIncludeCurrentPage,
maxLevel
} = (_getBlockAttributes = getBlockAttributes(clientId)) !== null && _getBlockAttributes !== void 0 ? _getBlockAttributes : {};
// Get post-content block client ID.
const [postContentClientId = ''] = getBlocksByName('core/post-content');
// Get the client ids of all blocks in the editor.
const allBlockClientIds = getClientIdsOfDescendants(postContentClientId);
// If onlyIncludeCurrentPage is true, calculate the page (of a paginated post) this block is part of, so we know which headings to include; otherwise, skip the calculation.
let tocPage = 1;
if (isPaginated && onlyIncludeCurrentPage) {
// We can't use getBlockIndex because it only returns the index
// relative to sibling blocks.
const tocIndex = allBlockClientIds.indexOf(clientId);
for (const [blockIndex, blockClientId] of allBlockClientIds.entries()) {
// If we've reached blocks after the Table of Contents, we've
// finished calculating which page the block is on.
if (blockIndex >= tocIndex) {
break;
}
if (getBlockName(blockClientId) === 'core/nextpage') {
tocPage++;
}
}
}
const latestHeadings = [];
/** The page (of a paginated post) a heading will be part of. */
let headingPage = 1;
let headingPageLink = null;
// If the core/editor store is available, we can add permalinks to the
// generated table of contents.
if (typeof permalink === 'string') {
headingPageLink = isPaginated ? (0, _url.addQueryArgs)(permalink, {
page: headingPage
}) : permalink;
}
for (const blockClientId of allBlockClientIds) {
const blockName = getBlockName(blockClientId);
if (blockName === 'core/nextpage') {
headingPage++;
// If we're only including headings from the current page (of
// a paginated post), then exit the loop if we've reached the
// pages after the one with the Table of Contents block.
if (onlyIncludeCurrentPage && headingPage > tocPage) {
break;
}
if (typeof permalink === 'string') {
headingPageLink = (0, _url.addQueryArgs)((0, _url.removeQueryArgs)(permalink, ['page']), {
page: headingPage
});
}
}
// If we're including all headings or we've reached headings on
// the same page as the Table of Contents block, add them to the
// list.
else if (!onlyIncludeCurrentPage || headingPage === tocPage) {
if (blockName === 'core/heading') {
const headingAttributes = getBlockAttributes(blockClientId);
// Skip headings that are deeper than maxLevel
if (maxLevel && headingAttributes.level > maxLevel) {
continue;
}
const canBeLinked = typeof headingPageLink === 'string' && typeof headingAttributes.anchor === 'string' && headingAttributes.anchor !== '';
latestHeadings.push({
// Convert line breaks to spaces, and get rid of HTML tags in the headings.
content: (0, _dom.__unstableStripHTML)(headingAttributes.content.replace(/(<br *\/?>)+/g, ' ')),
level: headingAttributes.level,
link: canBeLinked ? `${headingPageLink}#${headingAttributes.anchor}` : null
});
}
}
}
return latestHeadings;
}
function observeCallback(select, dispatch, clientId) {
const {
getBlockAttributes
} = select(_blockEditor.store);
const {
updateBlockAttributes,
__unstableMarkNextChangeAsNotPersistent
} = dispatch(_blockEditor.store);
/**
* If the block no longer exists in the store, skip the update.
* The "undo" action recreates the block and provides a new `clientId`.
* The hook still might be observing the changes while the old block unmounts.
*/
const attributes = getBlockAttributes(clientId);
if (attributes === null) {
return;
}
const headings = getLatestHeadings(select, clientId);
if (!(0, _es.default)(headings, attributes.headings)) {
// Executing the update in a microtask ensures that the non-persistent marker doesn't affect an attribute triggering the change.
window.queueMicrotask(() => {
__unstableMarkNextChangeAsNotPersistent();
updateBlockAttributes(clientId, {
headings
});
});
}
}
function useObserveHeadings(clientId) {
const registry = (0, _data.useRegistry)();
(0, _element.useEffect)(() => {
// Todo: Limit subscription to block editor store when data no longer depends on `getPermalink`.
// See: https://github.com/WordPress/gutenberg/pull/45513
return registry.subscribe(() => observeCallback(registry.select, registry.dispatch, clientId));
}, [registry, clientId]);
}
//# sourceMappingURL=hooks.js.map
;