UNPKG

@bookbox/view-html

Version:

Bookbox view for html

161 lines (160 loc) 5.94 kB
const contentsSelector = '.book-box_layout-settings-contents'; const currentHeaderClass = 'book-box_layout-settings-contents-item-current'; const currentHeaderSelector = `.${currentHeaderClass}`; const getHeadersNavigationCallback = (current) => (entries) => { const headersElems = document.querySelectorAll(contentsSelector); headersElems.forEach(headersElem => { const listHeaders = Array.from(headersElem.querySelectorAll('[data-name="header"]')); const listParentHeaders = listHeaders.map(e => e.parentElement); const listHeadersMap = new Map(listHeaders.map((elem, i) => [elem.dataset.key, i])); const { targetIndex } = getCurrentItemIndex({ listMap: listHeadersMap, current, entries, }); const targetElem = listParentHeaders[targetIndex]; // возможно заменить на прямой индекс const currentElems = headersElem.querySelectorAll(currentHeaderSelector); currentElems.forEach(currentElem => { currentElem.classList.remove(currentHeaderClass); }); targetElem.classList.add(currentHeaderClass); targetElem.parentElement.scrollTop = targetElem.offsetTop - 50; current.index = targetIndex; }); }; const getPagesNavigationCallback = (listPagesMap, current) => (entries) => { const { targetIndex } = getCurrentItemIndex({ listMap: listPagesMap, current, entries, }); if (targetIndex === current.index) { return; } replaceHistory({ hash: `page-${targetIndex}` }); current.index = targetIndex; }; function getUrl(url, options) { const targetUrl = typeof url === 'string' ? new URL(url) : url; Object.assign(targetUrl, options !== null && options !== void 0 ? options : {}); return targetUrl; } export function replaceHistory(options) { window.history.replaceState(null, '', getUrl(window.location.toString(), options)); } export function pushHistory(options) { window.history.pushState(null, '', getUrl(window.location.toString(), options)); } function getCurrentItemIndex({ listMap, current, entries, }) { var _a, _b; let overTopMaximum = -Infinity; let overBottomMinimum = Infinity; for (const entry of entries) { const { isIntersecting, target, boundingClientRect, intersectionRect } = entry; const border = getBorder({ boundingClientRect, intersectionRect }); const key = (_a = target.dataset.key) !== null && _a !== void 0 ? _a : ''; const i = (_b = listMap.get(key)) !== null && _b !== void 0 ? _b : 0; if (isIntersecting) { current.visible.add(i); } else { current.visible.delete(i); if (border === 'top') { // исчезание сверху if (i > overTopMaximum) { overTopMaximum = i; } } else if (border === 'bottom') { // исчезание снизу if (i < overBottomMinimum) { overBottomMinimum = i; } } } } let targetIndex = current.index; if (current.visible.size > 0) { targetIndex = Math.min(...current.visible); } else { const vars = []; if (targetIndex !== null) { vars.push(targetIndex); } if (overBottomMinimum !== Infinity) { vars.push(overBottomMinimum - 1); } if (overTopMaximum !== -Infinity) { vars.push(overTopMaximum); } // TODO: оптимизировать targetIndex = Math.max(0, Math.min(...vars)); } return { targetIndex }; } function getBorder({ boundingClientRect, intersectionRect, }) { const { top, bottom } = boundingClientRect; const { top: itop, bottom: ibottom } = intersectionRect; if ((top ^ 0) === (itop ^ 0)) { return 'bottom'; } return 'top'; } const PAGE_PREFIX = 'page-'.length; export function getCurrentPage() { const pageHash = window.location.hash; const page = +pageHash.slice(PAGE_PREFIX + 1); return Number.isNaN(page) ? null : page; } // headers navigation export function getNavigation(bookHtmlContainer = document.body) { const contentHeaders = bookHtmlContainer.querySelectorAll('.book-box_content [data-name="header"]'); const headersCurrent = { index: null, visible: new Set(), }; const headersNavigationCallback = getHeadersNavigationCallback(headersCurrent); const headersObserver = new IntersectionObserver(headersNavigationCallback, { threshold: 0.5, }); const observeHeaders = () => { for (const header of Array.from(contentHeaders)) { headersObserver.observe(header); } }; // page navigation const contentPages = Array.from(bookHtmlContainer.querySelectorAll('.book-box_content [data-name=".page"]')); const listPageMap = new Map(contentPages .map(elem => elem.dataset.key) .filter((key) => Boolean(key)) .map(key => [key, +key.slice(PAGE_PREFIX)])); const pagesCurrent = { index: null, visible: new Set(), }; const pagesNavigationCallback = getPagesNavigationCallback(listPageMap, pagesCurrent); const pageObserver = new IntersectionObserver(pagesNavigationCallback, { threshold: 0.5, }); const observePages = () => { for (const page of contentPages) { pageObserver.observe(page); } }; const observeNavigation = () => { observeHeaders(); observePages(); }; const disconnectNavigation = () => { headersObserver.disconnect(); pageObserver.disconnect(); }; return { headersObserver, pageObserver, observeNavigation, disconnectNavigation, }; }