Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions toc.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* General TOC container */
#table-of-contents-content {
padding-left: 0;
}

/* Each TOC item */
#table-of-contents-content .toc-item {
list-style: none;
padding: 2px 0;
}

/* Flex container for anchor and toggle */
#table-of-contents-content .toc-row {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}

/* Anchor link styling */
#table-of-contents-content .toc-row a {
flex-grow: 1; /* Allow anchor to take available space */
text-decoration: none;
color: inherit;
word-wrap: break-word;
white-space: normal;
}

/* Toggle button styling */
.toc-toggle {
background: transparent;
border: none;
cursor: pointer;
color: inherit;
padding: 0 0.5rem;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0; /* Prevent toggle from shrinking */
}

.toc-toggle:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4);
border-radius: 4px;
}

/* Chevron icon transition */
.toc-toggle svg {
transition: transform 150ms ease-in-out;
}

/* Rotated state for chevron when collapsed */
.toc-toggle.rotated svg {
transform: rotate(-90deg);
}
135 changes: 135 additions & 0 deletions toc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// This script enhances the Table of Contents (TOC) / right side navigation of Mintliffy by adding collapsible/expandable sections based on header depth.
// It also fixes issues with incorrect depth values for custom anchor tags and ensures proper indentation and visibility of TOC items.
// But custom anchor tags are not used anymore in our repository. So that part is not needed anymore.

// toc-fix-v2.js
(function () {
let observer;

function run() {
const toc = document.getElementById('table-of-contents-content');
if (!toc || toc.dataset.enhanced === 'true') {
return;
}
toc.dataset.enhanced = 'true';

// Disconnect the observer while we modify the DOM to prevent infinite loops
if (observer) observer.disconnect();

const items = Array.from(toc.querySelectorAll('.toc-item'));

// Normalize depths, fix NaN, and set initial styles
items.forEach(li => {
let depthNum = Number(li.getAttribute('data-depth'));
if (!Number.isFinite(depthNum)) {
depthNum = 2; // Fix for h4 headers producing NaN
}
li.dataset.depth = String(depthNum);
li.style.display = 'list-item'; // Ensure all items are visible initially

const a = li.querySelector('a');
if (a) {
const indentStep = 1; // rem per depth level
a.style.marginLeft = `${depthNum * indentStep}rem`;
}
});

// Add toggle buttons and functionality
items.forEach((li, idx) => {
const depth = Number(li.dataset.depth);

// Detect if this item has children
const hasChild = idx + 1 < items.length && Number(items[idx + 1].dataset.depth) > depth;
if (!hasChild) return;

// Prevent adding duplicate toggles if script re-runs
if (li.querySelector('.toc-toggle')) return;

const toggle = document.createElement('button');
toggle.type = 'button';
toggle.className = 'toc-toggle';
toggle.setAttribute('aria-expanded', 'true');
toggle.innerHTML = `
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false">
<path d="M8.5 10.5 L12 14 L15.5 10.5" stroke="currentColor" stroke-width="1.6" fill="none"
stroke-linecap="round" stroke-linejoin="round"/>
</svg>
`;

const anchor = li.querySelector('a');
if (anchor) {
const wrapper = document.createElement('div');
wrapper.className = 'toc-row';
// Insert wrapper before the anchor, then move anchor and toggle into it
anchor.parentNode.insertBefore(wrapper, anchor);
wrapper.appendChild(anchor);
wrapper.appendChild(toggle);
} else {
li.appendChild(toggle);
}

li.dataset.collapsed = 'false';

toggle.addEventListener('click', e => {
e.preventDefault();
const isCollapsed = li.dataset.collapsed === 'true';

// Toggle the state
li.dataset.collapsed = String(!isCollapsed);
toggle.setAttribute('aria-expanded', String(!isCollapsed));
toggle.classList.toggle('rotated', !isCollapsed);

// Update visibility of all descendant items
for (let k = idx + 1; k < items.length; k++) {
const childItem = items[k];
const childDepth = Number(childItem.dataset.depth);

if (childDepth <= depth) {
break; // Exited the subtree of the clicked item
}

if (!isCollapsed) {
// If we are COLLAPSING, hide all descendants
childItem.style.display = 'none';
} else {
// If we are EXPANDING, only reveal direct children
// Deeper children remain hidden if their own parent is collapsed
if (childDepth === depth + 1) {
childItem.style.display = 'list-item';
}
}
}
});
});

// Reconnect the observer after DOM modifications are complete
if (observer) {
observer.observe(document.body, { childList: true, subtree: true });
}
}

function init() {
run(); // Run on initial load

// Set up an observer to re-run the script on SPA navigation
observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length > 0) {
const toc = document.getElementById('table-of-contents-content');
// If a TOC exists and it hasn't been enhanced yet, run the script
if (toc && !toc.dataset.enhanced) {
run();
break; // Found and processed a new TOC, no need to check other mutations
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();