Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show toc on mobile screens #1964

Merged
merged 17 commits into from
Oct 11, 2024
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
10 changes: 10 additions & 0 deletions _includes/toc-status.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% comment %}
Determine TOC state and return it through variable "enable_toc"
{% endcomment %}

{% assign enable_toc = false %}
{% if site.toc and page.toc %}
{% if page.content contains '<h2' or page.content contains '<h3' %}
{% assign enable_toc = true %}
{% endif %}
{% endif %}
9 changes: 2 additions & 7 deletions _includes/toc.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
{% assign enable_toc = false %}
{% if site.toc and page.toc %}
{% if page.content contains '<h2' or page.content contains '<h3' %}
{% assign enable_toc = true %}
{% endif %}
{% endif %}
{% include toc-status.html %}

{% if enable_toc %}
<section id="toc-wrapper" class="d-none ps-0 pe-4">
<section id="toc-wrapper" class="ps-0 pe-4">
<h2 class="panel-heading ps-3 mb-2">{{- site.data.locales[include.lang].panel.toc -}}</h2>
<nav id="toc"></nav>
</section>
Expand Down
41 changes: 28 additions & 13 deletions _javascript/modules/components/toc.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
export function toc() {
if (document.querySelector('main h2, main h3')) {
// see: https://github.com/tscanlin/tocbot#usage
tocbot.init({
tocSelector: '#toc',
contentSelector: '.content',
ignoreSelector: '[data-toc-skip]',
headingSelector: 'h2, h3, h4',
orderedList: false,
scrollSmooth: false
});

document.getElementById('toc-wrapper').classList.remove('d-none');
import { TocMobile as mobile } from './toc/toc-mobile';
import { TocDesktop as desktop } from './toc/toc-desktop';

const desktopMode = matchMedia('(min-width: 1200px)');

function refresh(e) {
if (e.matches) {
mobile.hidePopup();
desktop.refresh();
cotes2020 marked this conversation as resolved.
Show resolved Hide resolved
} else {
mobile.refresh();
}
}

function init() {
if (document.querySelector('main>article[data-toc="true"]') === null) {
return;
}

// Avoid create multiple instances of Tocbot. Ref: <https://github.com/tscanlin/tocbot/issues/203>
if (desktopMode.matches) {
desktop.init();
} else {
mobile.init();
}

desktopMode.onchange = refresh;
}

export { init as initToc };
22 changes: 22 additions & 0 deletions _javascript/modules/components/toc/toc-desktop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export class TocDesktop {
/* Tocbot options Ref: https://github.com/tscanlin/tocbot#usage */
static options = {
kungfux marked this conversation as resolved.
Show resolved Hide resolved
tocSelector: '#toc',
contentSelector: '.content',
ignoreSelector: '[data-toc-skip]',
headingSelector: 'h2, h3, h4',
orderedList: false,
scrollSmooth: false,
headingsOffset: 16 * 2 // 2rem
};

static refresh() {
tocbot.refresh(this.options);
}

static init() {
if (document.getElementById('toc-wrapper')) {
tocbot.init(this.options);
}
}
}
117 changes: 117 additions & 0 deletions _javascript/modules/components/toc/toc-mobile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* TOC button, topbar and popup for mobile devices
*/

const $tocBar = document.getElementById('toc-bar');
const $soloTrigger = document.getElementById('toc-solo-trigger');
const $triggers = document.getElementsByClassName('toc-trigger');
const $popup = document.getElementById('toc-popup');
const $btnClose = document.getElementById('toc-popup-close');

const SCROLL_LOCK = 'overflow-hidden';
const CLOSING = 'closing';

export class TocMobile {
static invisible = true;
static barHeight = 16 * 3; // 3rem

static options = {
tocSelector: '#toc-popup-content',
contentSelector: '.content',
ignoreSelector: '[data-toc-skip]',
headingSelector: 'h2, h3, h4',
orderedList: false,
scrollSmooth: false,
collapseDepth: 4,
headingsOffset: this.barHeight
};

static initBar() {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
$tocBar.classList.toggle('invisible', entry.isIntersecting);
});
},
{ rootMargin: `-${this.barHeight}px 0px 0px 0px` }
);

observer.observe($soloTrigger);
this.invisible = false;
}

static listenAnchors() {
const $anchors = document.getElementsByClassName('toc-link');
[...$anchors].forEach((anchor) => {
anchor.onclick = this.hidePopup;
});
}

static refresh() {
if (this.invisible) {
this.initComponents();
}
tocbot.refresh(this.options);
this.listenAnchors();
}

static showPopup() {
TocMobile.lockScroll(true);
$popup.showModal();
const activeItem = $popup.querySelector('li.is-active-li');
activeItem.scrollIntoView({ block: 'center' });
}

static hidePopup() {
if (!$popup.open) {
return;
}

$popup.toggleAttribute(CLOSING);

$popup.addEventListener(
'animationend',
() => {
$popup.toggleAttribute(CLOSING);
$popup.close();
},
{ once: true }
);

TocMobile.lockScroll(false);
}

static lockScroll(enable) {
document.documentElement.classList.toggle(SCROLL_LOCK, enable);
document.body.classList.toggle(SCROLL_LOCK, enable);
}

static clickBackdrop(event) {
const rect = event.target.getBoundingClientRect();
if (
event.clientX < rect.left ||
event.clientX > rect.right ||
event.clientY < rect.top ||
event.clientY > rect.bottom
) {
TocMobile.hidePopup();
}
}

static initComponents() {
this.initBar();

[...$triggers].forEach((trigger) => {
trigger.onclick = this.showPopup;
});

$popup.onclick = this.clickBackdrop;
$btnClose.onclick = $popup.oncancel = this.hidePopup;
}

static init() {
tocbot.init(this.options);
this.listenAnchors();
this.initComponents();
}
}
2 changes: 1 addition & 1 deletion _javascript/modules/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ export { initClipboard } from './components/clipboard';
export { loadImg } from './components/img-loading';
export { imgPopup } from './components/img-popup';
export { initLocaleDatetime } from './components/locale-datetime';
export { toc } from './components/toc';
export { initToc } from './components/toc';
7 changes: 4 additions & 3 deletions _javascript/post.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { basic, initSidebar, initTopbar } from './modules/layouts';
import { basic, initTopbar, initSidebar } from './modules/layouts';

import {
loadImg,
imgPopup,
initLocaleDatetime,
initClipboard,
toc
initToc
} from './modules/plugins';

loadImg();
toc();
initToc();
imgPopup();
initSidebar();
initLocaleDatetime();
Expand Down
28 changes: 27 additions & 1 deletion _layouts/post.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

{% include lang.html %}

<article class="px-1">
{% include toc-status.html %}

<article class="px-1" data-toc="{{ enable_toc }}">
<header>
<h1 data-toc-skip>{{ page.title }}</h1>
{% if page.description %}
Expand Down Expand Up @@ -95,6 +97,30 @@ <h1 data-toc-skip>{{ page.title }}</h1>
</div>
</header>

{% if enable_toc %}
<div id="toc-bar" class="d-flex align-items-center justify-content-between invisible">
<span class="label text-truncate">{{ page.title }}</span>
<button type="button" class="toc-trigger btn btn-link me-1">
<i class="fa-solid fa-list-ul fa-fw"></i>
</button>
</div>

<button id="toc-solo-trigger" type="button" class="toc-trigger btn btn-outline-secondary btn-sm">
<span class="label ps-2 pe-1">{{- site.data.locales[lang].panel.toc -}}</span>
<i class="fa-solid fa-angle-right fa-fw"></i>
</button>

<dialog id="toc-popup" class="p-0">
<div class="header d-flex flex-row align-items-center justify-content-between">
<div class="label text-truncate py-2 ms-4">{{- page.title -}}</div>
<button id="toc-popup-close" type="button" class="btn btn-link">
<i class="fas fa-close fa-fw"></i>
</button>
</div>
<div id="toc-popup-content" class="px-4 py-3 pb-4"></div>
</dialog>
{% endif %}

<div class="content">
{{ content }}
</div>
Expand Down
4 changes: 1 addition & 3 deletions _sass/addon/commons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -908,9 +908,7 @@ $btn-mb: 0.5rem;
}

#topbar {
button i {
color: #999999;
}
@extend %btn-color;

#breadcrumb {
font-size: 1rem;
Expand Down
7 changes: 7 additions & 0 deletions _sass/addon/module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
color: var(--heading-color);
font-weight: 400;
font-family: $font-family-heading;
scroll-margin-top: 3.5rem;
}

%anchor {
Expand Down Expand Up @@ -134,6 +135,12 @@
}
}

%btn-color {
button i {
color: #999999;
}
}

/* ---------- scss mixin --------- */

@mixin mt-mb($value) {
Expand Down
2 changes: 1 addition & 1 deletion _sass/colors/typography-dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
--btn-border-color: #2e2f31;
--btn-backtotop-color: var(--text-color);
--btn-backtotop-border-color: #212122;
--btn-box-shadow: var(--main-bg);
--card-header-bg: #292929;
--checkbox-color: rgb(118, 120, 121);
--checkbox-checked-color: var(--link-color);
Expand Down Expand Up @@ -60,6 +59,7 @@

/* Posts */
--toc-highlight: rgb(116, 178, 243);
--toc-popup-border-color: #373737;
--tag-hover: rgb(43, 56, 62);
--tb-odd-bg: #252526; /* odd rows of the posts' table */
--tb-even-bg: rgb(31, 31, 34); /* even rows of the posts' table */
Expand Down
2 changes: 1 addition & 1 deletion _sass/colors/typography-light.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
--btn-border-color: #e9ecef;
--btn-backtotop-color: #686868;
--btn-backtotop-border-color: #f1f1f1;
--btn-box-shadow: #eaeaea;
--checkbox-color: #c5c5c5;
--checkbox-checked-color: #07a8f7;
--img-bg: radial-gradient(
Expand Down Expand Up @@ -63,6 +62,7 @@

/* Posts */
--toc-highlight: #0550ae;
--toc-popup-border-color: lightgray;
--btn-share-color: gray;
--btn-share-hover-color: #0d6efd;
--card-bg: white;
Expand Down
Loading