mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
258 lines
7.0 KiB
JavaScript
258 lines
7.0 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
function IsRightTocEnabled() {
|
|
var meta = document.querySelector('meta[name="ffu-right-toc"]');
|
|
if (meta && meta.content && meta.content.toLowerCase() === 'false') {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function GetHeadings(container) {
|
|
var headings = container.querySelectorAll('h2, h3');
|
|
var results = [];
|
|
|
|
for (var i = 0; i < headings.length; i++) {
|
|
var heading = headings[i];
|
|
|
|
if (heading.classList.contains('no_toc')) {
|
|
continue;
|
|
}
|
|
|
|
var id = heading.getAttribute('id');
|
|
if (!id) {
|
|
continue;
|
|
}
|
|
|
|
var text = (heading.textContent || '').trim();
|
|
if (!text) {
|
|
continue;
|
|
}
|
|
|
|
results.push({
|
|
level: heading.tagName.toLowerCase(),
|
|
id: id,
|
|
text: text
|
|
});
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
function BuildToc(headings) {
|
|
var nav = document.createElement('nav');
|
|
nav.className = 'page-toc';
|
|
nav.setAttribute('aria-label', 'On this page');
|
|
|
|
var title = document.createElement('div');
|
|
title.className = 'page-toc__title';
|
|
title.textContent = 'In this article';
|
|
nav.appendChild(title);
|
|
|
|
var list = document.createElement('ul');
|
|
list.className = 'page-toc__list';
|
|
|
|
for (var i = 0; i < headings.length; i++) {
|
|
var item = headings[i];
|
|
|
|
var li = document.createElement('li');
|
|
li.className = 'page-toc__item page-toc__item--' + item.level;
|
|
|
|
var a = document.createElement('a');
|
|
a.className = 'page-toc__link';
|
|
a.href = '#' + item.id;
|
|
a.textContent = item.text;
|
|
|
|
li.appendChild(a);
|
|
list.appendChild(li);
|
|
}
|
|
|
|
nav.appendChild(list);
|
|
return nav;
|
|
}
|
|
|
|
function SetActiveTocLink(toc, activeId) {
|
|
if (!toc) {
|
|
return;
|
|
}
|
|
|
|
var links = toc.querySelectorAll('.page-toc__link');
|
|
for (var i = 0; i < links.length; i++) {
|
|
var link = links[i];
|
|
var href = link.getAttribute('href') || '';
|
|
var isActive = ('#' + activeId) === href;
|
|
|
|
if (isActive) {
|
|
link.classList.add('is-active');
|
|
|
|
/* Keep the active item visible inside the TOC panel */
|
|
try {
|
|
link.scrollIntoView({ block: 'nearest' });
|
|
} catch (e) {
|
|
link.scrollIntoView();
|
|
}
|
|
} else {
|
|
link.classList.remove('is-active');
|
|
}
|
|
}
|
|
}
|
|
|
|
function SetupScrollSpy(main, toc, headings) {
|
|
if (!main || !toc || !headings || headings.length < 1) {
|
|
return;
|
|
}
|
|
|
|
var headingElements = [];
|
|
for (var i = 0; i < headings.length; i++) {
|
|
var el = document.getElementById(headings[i].id);
|
|
if (el) {
|
|
headingElements.push(el);
|
|
}
|
|
}
|
|
|
|
if (headingElements.length < 1) {
|
|
return;
|
|
}
|
|
|
|
var activeId = null;
|
|
var ticking = false;
|
|
var lockActiveUntilMs = 0;
|
|
|
|
function IsNearBottomOfPage() {
|
|
var thresholdPx = 24;
|
|
var scrollY = window.scrollY || window.pageYOffset || 0;
|
|
var viewportBottom = scrollY + window.innerHeight;
|
|
var pageHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
|
|
|
|
return viewportBottom >= (pageHeight - thresholdPx);
|
|
}
|
|
|
|
function GetCurrentHeadingId() {
|
|
/* If we're at the bottom, force the last heading active (Learn-like behavior) */
|
|
if (IsNearBottomOfPage()) {
|
|
return headingElements[headingElements.length - 1].getAttribute('id');
|
|
}
|
|
|
|
/* Choose the heading closest to the top "activation line" */
|
|
var activationLine = 16;
|
|
var current = null;
|
|
|
|
for (var i = 0; i < headingElements.length; i++) {
|
|
var rectTop = headingElements[i].getBoundingClientRect().top;
|
|
|
|
if (rectTop <= activationLine) {
|
|
current = headingElements[i];
|
|
continue;
|
|
}
|
|
|
|
if (null === current) {
|
|
current = headingElements[i];
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (null === current) {
|
|
current = headingElements[0];
|
|
}
|
|
|
|
return current.getAttribute('id');
|
|
}
|
|
|
|
function Update() {
|
|
ticking = false;
|
|
|
|
if (Date.now() < lockActiveUntilMs) {
|
|
return;
|
|
}
|
|
|
|
var currentId = GetCurrentHeadingId();
|
|
if (!currentId || currentId === activeId) {
|
|
return;
|
|
}
|
|
|
|
activeId = currentId;
|
|
SetActiveTocLink(toc, activeId);
|
|
}
|
|
|
|
function OnScrollOrResize() {
|
|
if (ticking) {
|
|
return;
|
|
}
|
|
|
|
ticking = true;
|
|
window.requestAnimationFrame(Update);
|
|
}
|
|
|
|
window.addEventListener('scroll', OnScrollOrResize, { passive: true });
|
|
window.addEventListener('resize', OnScrollOrResize);
|
|
|
|
/* Update immediately and also when clicking TOC links */
|
|
toc.addEventListener('click', function (evt) {
|
|
var target = evt.target;
|
|
if (!target || !target.classList || !target.classList.contains('page-toc__link')) {
|
|
return;
|
|
}
|
|
|
|
var href = target.getAttribute('href') || '';
|
|
if (href.charAt(0) !== '#') {
|
|
return;
|
|
}
|
|
|
|
var id = href.substring(1);
|
|
if (!id) {
|
|
return;
|
|
}
|
|
|
|
/* Prevent scrollspy from immediately overriding the clicked section */
|
|
lockActiveUntilMs = Date.now() + 800;
|
|
|
|
activeId = id;
|
|
SetActiveTocLink(toc, activeId);
|
|
});
|
|
|
|
Update();
|
|
}
|
|
|
|
function InitRightToc() {
|
|
if (!IsRightTocEnabled()) {
|
|
return;
|
|
}
|
|
|
|
var main = document.querySelector('.main-content main');
|
|
if (!main) {
|
|
return;
|
|
}
|
|
|
|
var headings = GetHeadings(main);
|
|
if (headings.length < 2) {
|
|
return;
|
|
}
|
|
|
|
var wrap = document.querySelector('.main-content-wrap');
|
|
var content = document.querySelector('.main-content');
|
|
if (!wrap || !content) {
|
|
return;
|
|
}
|
|
|
|
if (wrap.querySelector('.page-toc')) {
|
|
return;
|
|
}
|
|
|
|
wrap.classList.add('has-page-toc');
|
|
|
|
var toc = BuildToc(headings);
|
|
wrap.appendChild(toc);
|
|
|
|
SetupScrollSpy(main, toc, headings);
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', InitRightToc);
|
|
return;
|
|
}
|
|
|
|
InitRightToc();
|
|
})(); |