UNPKG

@plone/volto

Version:
203 lines (181 loc) 5.4 kB
/** * View toc block. * @module components/manage/Blocks/ToC/View */ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import cx from 'classnames'; import { Message } from 'semantic-ui-react'; import config from '@plone/volto/registry'; import { withBlockExtensions } from '@plone/volto/helpers/Extensions'; import { getBlocksFieldname, getBlocksLayoutFieldname, } from '@plone/volto/helpers/Blocks/Blocks'; export const getBlocksTocEntries = (properties, tocData) => { const blocksFieldName = getBlocksFieldname(properties); const blocksLayoutFieldname = getBlocksLayoutFieldname(properties); const blocks = properties[blocksFieldName]; const blocks_layout = properties[blocksLayoutFieldname]; const levels = tocData.levels?.length > 0 ? tocData.levels.map((l) => parseInt(l.slice(1))) : [1, 2, 3, 4, 5, 6]; let rootLevel = Infinity; let blocksFormEntries = []; let tocEntries = {}; let tocEntriesLayout = []; blocks_layout.items.forEach((id) => { const block = blocks[id]; const blockConfig = config.blocks.blocksConfig[block['@type']]; if (!block || !blockConfig) { return null; } if (!blockConfig.tocEntries && !blockConfig.tocEntry) { return null; } const blockTocEntry = blockConfig.tocEntry?.(block, tocData); const blockTocEntries = [ ...(blockConfig.tocEntries?.(block, tocData) || (blockTocEntry ? [blockTocEntry] : [])), ]; blocksFormEntries = [...blocksFormEntries, ...blockTocEntries]; blockTocEntries.forEach((entry, index) => { const i = `${id}-${index}`; const level = entry[0]; const title = entry[1]; const items = []; if (!level || !levels.includes(level)) return; tocEntriesLayout.push(i); tocEntries[i] = { level, title: title || block.plaintext, items, id: i, }; if (level < rootLevel) { rootLevel = level; } }); }); return { rootLevel, blocksFormEntries, tocEntries, tocEntriesLayout, }; }; /** * View toc block class. * @class View * @extends Component */ const View = (props) => { const { data } = props; const title = data.title ? data.title : ''; const { variation } = props; const metadata = props.metadata || props.properties; const blocksFieldname = getBlocksFieldname(metadata); const levels = React.useMemo( () => data.levels?.length > 0 ? data.levels.map((l) => parseInt(l.slice(1))) : [1, 2, 3, 4, 5, 6], [data], ); const tocEntries = React.useMemo(() => { let entries = []; let prevEntry = {}; const { rootLevel, tocEntries, tocEntriesLayout } = getBlocksTocEntries( metadata, data, ); tocEntriesLayout.forEach((id) => { const block = metadata[blocksFieldname][id]; if (typeof block === 'undefined') { return null; } if (!config.blocks.blocksConfig[block['@type']]?.tocEntry) return null; const entry = config.blocks.blocksConfig[block['@type']]?.tocEntry( block, data, ); if (entry) { const level = entry[0]; const title = entry[1]; const items = []; if (!title?.trim() && !block.plaintext?.trim()) return; if (!level || !levels.includes(level)) return; tocEntriesLayout.push(id); tocEntries[id] = { level, title: title || block.plaintext, items, id, override_toc: block.override_toc, plaintext: block.plaintext, }; } }); tocEntriesLayout.forEach((id) => { const entry = tocEntries[id]; if (entry.level === rootLevel) { entries.push(entry); prevEntry = entry; return; } if (!prevEntry.id) return; if (entry.level > prevEntry.level) { entry.parentId = prevEntry.id; prevEntry.items.push(entry); prevEntry = entry; } else if (entry.level < prevEntry.level) { let parent = tocEntries[prevEntry.parentId]; while (entry.level <= parent.level) { parent = tocEntries[parent.parentId]; } entry.parentId = parent.id; parent.items.push(entry); prevEntry = entry; } else { entry.parentId = prevEntry.parentId; tocEntries[prevEntry.parentId].items.push(entry); prevEntry = entry; } }); return entries; }, [data, levels, metadata, blocksFieldname]); const Renderer = variation?.view; return ( <nav aria-label={title && !data.hide_title ? title : ''} className={cx('table-of-contents', variation?.id)} > {props.mode === 'edit' && !title && !tocEntries.length && ( <Message> { <FormattedMessage id="Table of Contents" defaultMessage="Table of Contents" /> } </Message> )} {Renderer ? ( <Renderer {...props} tocEntries={tocEntries} metadata={metadata} /> ) : ( <div>View extension not found</div> )} </nav> ); }; /** * Property types. * @property {Object} propTypes Property types. * @static */ View.propTypes = { properties: PropTypes.objectOf(PropTypes.any).isRequired, }; export default withBlockExtensions(View);