Skip to content

Commit

Permalink
feat(widget): toc expand on smooth scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
ppoffice committed Aug 18, 2020
1 parent 5493e56 commit a42c10a
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 1 deletion.
74 changes: 74 additions & 0 deletions asset/js/toc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
(function (window, document) {
function register($toc) {
const currentInView = new Set();
const headingToMenu = new Map();
const $menus = Array.from($toc.querySelectorAll('.menu-list > li > a'));

for (const $menu of $menus) {
const elementId = $menu.getAttribute('href').trim().slice(1);
const $heading = document.getElementById(elementId);
if ($heading) {
headingToMenu.set($heading, $menu);
}
}

const $headings = Array.from(headingToMenu.keys());

const callback = (entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
currentInView.add(entry.target);
} else {
currentInView.delete(entry.target);
}
}
let $heading;
if (currentInView.size) {
// heading is the first in-view heading
$heading = [...currentInView].sort(($el1, $el2) => $el1.offsetTop - $el2.offsetTop)[0];
} else if ($headings.length) {
// heading is the closest heading above the viewport top
$heading = $headings
.filter(($heading) => $heading.offsetTop < window.scrollY)
.sort(($el1, $el2) => $el2.offsetTop - $el1.offsetTop)[0];
}
if ($heading && headingToMenu.has($heading)) {
$menus.forEach(($menu) => $menu.classList.remove('is-active'));

const $menu = headingToMenu.get($heading);
$menu.classList.add('is-active');
let $menuList = $menu.parentElement.parentElement;
while (
$menuList.classList.contains('menu-list') &&
$menuList.parentElement.tagName.toLowerCase() === 'li'
) {
$menuList.parentElement.children[0].classList.add('is-active');
$menuList = $menuList.parentElement.parentElement;
}
}
};
const observer = new IntersectionObserver(callback, { threshold: 0 });

for (const $heading of $headings) {
observer.observe($heading);
// smooth scroll to the heading
if (headingToMenu.has($heading)) {
const $menu = headingToMenu.get($heading);
$menu.setAttribute('data-href', $menu.getAttribute('href'));
$menu.setAttribute('href', 'javascript:;');
$menu.addEventListener('click', () => {
if (typeof $heading.scrollIntoView === 'function') {
$heading.scrollIntoView({ behavior: 'smooth' });
}
});
$heading.style.scrollMargin = '1em';
}
}
}

if (typeof window.IntersectionObserver === 'undefined') {
return;
}

document.querySelectorAll('#toc').forEach(register);
})(window, document);
10 changes: 9 additions & 1 deletion src/view/widget/toc.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ function getToc(content) {
* @example
* <Toc
* title="Widget title"
* content="HTML content" />
* content="HTML content"
* jsUrl="******" />
*/
class Toc extends Component {
renderToc(toc) {
Expand Down Expand Up @@ -115,6 +116,10 @@ class Toc extends Component {
return null;
}

const css =
'.menu-list > li > a.is-active + .menu-list { display: block; }' +
'.menu-list > li > a + .menu-list { display: none; }';

return (
<div class="card widget" id="toc">
<div class="card-content">
Expand All @@ -123,6 +128,8 @@ class Toc extends Component {
{this.renderToc(toc)}
</div>
</div>
<style dangerouslySetInnerHTML={{ __html: css }}></style>
<script src={this.props.jsUrl} defer={true}></script>
</div>
);
}
Expand Down Expand Up @@ -152,6 +159,7 @@ Toc.Cacheable = cacheComponent(Toc, 'widget.toc', (props) => {
return {
title: helper._p('widget.catalogue', Infinity),
content: encrypt ? origin : content,
jsUrl: helper.url_for('/js/toc.js'),
};
});

Expand Down

2 comments on commit a42c10a

@ppoffice
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#10

@ppoffice
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.