UNPKG

@eeacms/volto-anchors

Version:

@eeacms/volto-anchors: Volto add-on

132 lines (115 loc) 3.63 kB
import { isArray } from 'lodash'; import Slugger from 'github-slugger'; import config from '@plone/volto/registry'; import { serializeNodes } from '@plone/volto-slate/editor/render'; import { Link } from 'react-router-dom'; import { getBlocks } from '@plone/volto/helpers/Blocks/Blocks'; import linkSVG from '@plone/volto/icons/link.svg'; import './less/slate-anchors.less'; export const createSlateParagraph = (text) => { return isArray(text) ? text : config.settings.slate.defaultValue(); }; export const serializeText = (text) => { return isArray(text) ? serializeNodes(text) : text; }; export const toSlug = (url) => Slugger.slug(url); export const waitForElm = (selector) => { return new Promise((resolve) => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver((mutations) => { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true, }); }); }; export const scrollToTarget = (target, offsetHeight = 0) => { const bodyRect = document.body.getBoundingClientRect().top; const targetRect = target.getBoundingClientRect().top; const targetPosition = targetRect - bodyRect - offsetHeight; window.scrollTo({ top: targetPosition, behavior: 'smooth', }); return; }; const isValidSelector = (selector) => { try { document.createElement('div').querySelector(selector); return true; } catch (e) { return false; } }; export const openAccordionIfContainsAnchors = (anchor) => { if (!isValidSelector(anchor)) { return; } waitForElm(anchor).then((elm) => { if (elm.closest('.accordion')) { const comp = elm.closest('.accordion')?.querySelector('.title'); if (!comp?.className?.includes('active')) { comp.click(); setTimeout(() => scrollToTarget(elm), 300); } } }); return; }; //post order traversal of blocks content export const visitBlocks = (content, callback) => { const stack = getBlocks(content); while (stack.length > 0) { const [id, blockdata] = stack.pop(); const wantBreak = callback([id, blockdata]); if (wantBreak) break; // assumes that a block value is like: {blocks, blocks_layout} or // { data: {blocks, blocks_layout}} if (Object.keys(blockdata || {}).indexOf('blocks') > -1) { stack.push(...getBlocks(blockdata)); } if (Object.keys(blockdata?.data || {}).indexOf('blocks') > -1) { stack.push(...getBlocks(blockdata.data)); } } }; export const renderLinkElement = (tagName) => { function LinkElement({ attributes, children, mode = 'edit', className }) { const Tag = tagName; const slug = attributes.id || ''; const { slate = {} } = config.settings; return slate.useLinkedHeadings === false ? ( <Tag className={className} {...attributes}> {children} </Tag> ) : ( <Tag className={className} {...attributes}> {mode === 'view' && slug && ( <Link className="anchor" aria-hidden="true" tabIndex={-1} to={`#${slug}`} > <svg {...linkSVG.attributes} dangerouslySetInnerHTML={{ __html: linkSVG.content }} width="2em" height={null} ></svg> </Link> )} {children} </Tag> ); } LinkElement.displayName = `${tagName}LinkElement`; return LinkElement; };