feat: 将目录(TOC)移至侧边栏并优化滚动高亮逻辑
- 重构TOC组件,移至SideBar中显示 - 改进IntersectionObserver实现,优化章节高亮行为 - 添加toc国际化翻译(中/英/繁) - 更新Swup容器配置,支持文章页侧边栏刷新 - 移除原MainGridLayout中的独立TOC容器
This commit is contained in:
@@ -1,22 +1,38 @@
|
||||
---
|
||||
import type { MarkdownHeading } from "astro";
|
||||
import { siteConfig } from "@/config";
|
||||
import Categories from "./Categories.astro";
|
||||
import Profile from "./Profile.astro";
|
||||
import Tag from "./Tags.astro";
|
||||
import TOC from "./TOC.astro";
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
headings?: MarkdownHeading[];
|
||||
}
|
||||
|
||||
const className = Astro.props.class;
|
||||
const { class: className, headings } = Astro.props;
|
||||
const currentPagePath = Astro.url.pathname;
|
||||
const isPostPage = currentPagePath.startsWith("/posts/");
|
||||
---
|
||||
|
||||
<div id="sidebar" class:list={[className, "w-full"]}>
|
||||
<div class="flex flex-col w-full gap-4 mb-4">
|
||||
<Profile></Profile>
|
||||
</div>
|
||||
{
|
||||
!isPostPage && (
|
||||
<div class="flex flex-col w-full gap-4 mb-4">
|
||||
<Profile />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div id="sidebar-sticky" class="transition-all duration-700 flex flex-col w-full gap-4 top-4 sticky top-4">
|
||||
<Categories class="onload-animation" style="animation-delay: 150ms"></Categories>
|
||||
<Tag class="onload-animation" style="animation-delay: 200ms"></Tag>
|
||||
{
|
||||
siteConfig.toc.enable && headings && headings.length > 0 && (
|
||||
<div id="toc-wrapper" class="onload-animation card-base" style="animation-delay: 250ms">
|
||||
<TOC headings={headings} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<Categories class="onload-animation" style="animation-delay: 150ms" />
|
||||
<Tag class="onload-animation" style="animation-delay: 200ms" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
---
|
||||
import type { MarkdownHeading } from "astro";
|
||||
import { siteConfig } from "../../config";
|
||||
import I18nKey from "../../i18n/i18nKey";
|
||||
import { i18n } from "../../i18n/translation";
|
||||
import { url } from "../../utils/url-utils";
|
||||
import WidgetLayout from "./WidgetLayout.astro";
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
@@ -31,238 +34,249 @@ let heading1Count = 1;
|
||||
|
||||
const maxLevel = siteConfig.toc.depth;
|
||||
---
|
||||
{isPostsRoute &&
|
||||
<table-of-contents class:list={[className, "group"]}>
|
||||
{headings.filter((heading) => heading.depth < minDepth + maxLevel).map((heading) =>
|
||||
<a href={`#${heading.slug}`} class="px-2 flex gap-2 relative transition w-full min-h-9 rounded-xl
|
||||
hover:bg-[var(--toc-btn-hover)] active:bg-[var(--toc-btn-active)] py-2
|
||||
">
|
||||
<div class:list={["transition w-5 h-5 shrink-0 rounded-lg text-xs flex items-center justify-center font-bold",
|
||||
{
|
||||
"bg-[var(--toc-badge-bg)] text-[var(--btn-content)]": heading.depth == minDepth,
|
||||
"ml-4": heading.depth == minDepth + 1,
|
||||
"ml-8": heading.depth == minDepth + 2,
|
||||
}
|
||||
]}
|
||||
|
||||
<table-of-contents>
|
||||
<WidgetLayout name={i18n(I18nKey.toc)} id="toc" class={className}>
|
||||
<div class="flex flex-col pb-4">
|
||||
<div
|
||||
id="toc-wrapper"
|
||||
class="onload-animation card-base"
|
||||
style="animation-delay: 250ms"
|
||||
>
|
||||
<div
|
||||
id="toc-inner-wrapper"
|
||||
class="overflow-y-auto max-h-[50vh] hide-scrollbar pr-2"
|
||||
>
|
||||
{heading.depth == minDepth && heading1Count++}
|
||||
{heading.depth == minDepth + 1 && <div class="transition w-2 h-2 rounded-[0.1875rem] bg-[var(--toc-badge-bg)]"></div>}
|
||||
{heading.depth == minDepth + 2 && <div class="transition w-1.5 h-1.5 rounded-sm bg-black/5 dark:bg-white/10"></div>}
|
||||
{
|
||||
headings
|
||||
.filter(
|
||||
(heading) =>
|
||||
heading.depth < minDepth + maxLevel,
|
||||
)
|
||||
.map((heading) => (
|
||||
<a
|
||||
href={`#${heading.slug}`}
|
||||
class="px-2 flex gap-2 relative transition-colors duration-200 w-full min-h-9 rounded-xl hover:bg-[var(--toc-btn-hover)] active:bg-[var(--toc-btn-active)] py-2"
|
||||
>
|
||||
<div
|
||||
class:list={[
|
||||
"transition w-5 h-5 shrink-0 rounded-lg text-xs flex items-center justify-center font-bold",
|
||||
{
|
||||
"bg-[var(--toc-badge-bg)] text-[var(--btn-content)]":
|
||||
heading.depth == minDepth,
|
||||
"ml-4":
|
||||
heading.depth ==
|
||||
minDepth + 1,
|
||||
"ml-8":
|
||||
heading.depth ==
|
||||
minDepth + 2,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{heading.depth == minDepth &&
|
||||
heading1Count++}
|
||||
{heading.depth == minDepth + 1 && (
|
||||
<div class="transition w-2 h-2 rounded-[0.1875rem] bg-[var(--toc-badge-bg)]" />
|
||||
)}
|
||||
{heading.depth == minDepth + 2 && (
|
||||
<div class="transition w-1.5 h-1.5 rounded-sm bg-black/5 dark:bg-white/10" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
class:list={[
|
||||
"transition text-sm",
|
||||
{
|
||||
"text-90":
|
||||
heading.depth == minDepth ||
|
||||
heading.depth ==
|
||||
minDepth + 1,
|
||||
"text-75":
|
||||
heading.depth ==
|
||||
minDepth + 2,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{removeTailingHash(heading.text)}
|
||||
</div>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div class:list={["transition text-sm", {
|
||||
"text-50": heading.depth == minDepth || heading.depth == minDepth + 1,
|
||||
"text-30": heading.depth == minDepth + 2,
|
||||
}]}>{removeTailingHash(heading.text)}</div>
|
||||
</a>
|
||||
)}
|
||||
<div id="active-indicator" style="opacity: 0" class:list={[{'hidden': headings.length == 0}, "-z-10 absolute bg-[var(--toc-btn-hover)] left-0 right-0 rounded-xl transition-all " +
|
||||
"group-hover:bg-transparent border-2 border-[var(--toc-btn-hover)] group-hover:border-[var(--toc-btn-active)] border-dashed"]}></div>
|
||||
</table-of-contents>}
|
||||
</div>
|
||||
</div>
|
||||
</WidgetLayout>
|
||||
</table-of-contents>
|
||||
|
||||
<style>
|
||||
table-of-contents a.visible {
|
||||
background-color: var(--toc-btn-active) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
class TableOfContents extends HTMLElement {
|
||||
tocEl: HTMLElement | null = null;
|
||||
visibleClass = "visible";
|
||||
observer: IntersectionObserver;
|
||||
anchorNavTarget: HTMLElement | null = null;
|
||||
headingIdxMap = new Map<string, number>();
|
||||
headings: HTMLElement[] = [];
|
||||
sections: HTMLElement[] = [];
|
||||
tocEntries: HTMLAnchorElement[] = [];
|
||||
active: boolean[] = [];
|
||||
activeIndicator: HTMLElement | null = null;
|
||||
class TableOfContents extends HTMLElement {
|
||||
tocEl: HTMLElement | null = null;
|
||||
visibleClass = "visible";
|
||||
observer: IntersectionObserver;
|
||||
anchorNavTarget: HTMLElement | null = null;
|
||||
headingIdxMap = new Map<string, number>();
|
||||
sections: HTMLElement[] = [];
|
||||
tocEntries: HTMLAnchorElement[] = [];
|
||||
// active 数组不再需要,我们将直接追踪 activeIndex
|
||||
activeIndex: number | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.observer = new IntersectionObserver(
|
||||
this.markVisibleSection, { threshold: 0 }
|
||||
);
|
||||
};
|
||||
|
||||
markVisibleSection = (entries: IntersectionObserverEntry[]) => {
|
||||
entries.forEach((entry) => {
|
||||
const id = entry.target.children[0]?.getAttribute("id");
|
||||
const idx = id ? this.headingIdxMap.get(id) : undefined;
|
||||
if (idx != undefined)
|
||||
this.active[idx] = entry.isIntersecting;
|
||||
|
||||
if (entry.isIntersecting && this.anchorNavTarget == entry.target.firstChild)
|
||||
this.anchorNavTarget = null;
|
||||
});
|
||||
|
||||
if (!this.active.includes(true))
|
||||
this.fallback();
|
||||
this.update();
|
||||
};
|
||||
|
||||
toggleActiveHeading = () => {
|
||||
let i = this.active.length - 1;
|
||||
let min = this.active.length - 1, max = -1;
|
||||
while (i >= 0 && !this.active[i]) {
|
||||
this.tocEntries[i].classList.remove(this.visibleClass);
|
||||
i--;
|
||||
constructor() {
|
||||
super();
|
||||
// 调整 IntersectionObserver 的 rootMargin,使其在章节顶部进入视口时就触发
|
||||
this.observer = new IntersectionObserver(
|
||||
this.handleIntersection,
|
||||
{ rootMargin: "0px 0px -80% 0px" }, // 关键调整
|
||||
);
|
||||
}
|
||||
while (i >= 0 && this.active[i]) {
|
||||
this.tocEntries[i].classList.add(this.visibleClass);
|
||||
min = Math.min(min, i);
|
||||
max = Math.max(max, i);
|
||||
i--;
|
||||
}
|
||||
while (i >= 0) {
|
||||
this.tocEntries[i].classList.remove(this.visibleClass);
|
||||
i--;
|
||||
}
|
||||
if (min > max) {
|
||||
this.activeIndicator?.setAttribute("style", `opacity: 0`);
|
||||
} else {
|
||||
let parentOffset = this.tocEl?.getBoundingClientRect().top || 0;
|
||||
let scrollOffset = this.tocEl?.scrollTop || 0;
|
||||
let top = this.tocEntries[min].getBoundingClientRect().top - parentOffset + scrollOffset;
|
||||
let bottom = this.tocEntries[max].getBoundingClientRect().bottom - parentOffset + scrollOffset;
|
||||
this.activeIndicator?.setAttribute("style", `top: ${top}px; height: ${bottom - top}px`);
|
||||
}
|
||||
};
|
||||
|
||||
scrollToActiveHeading = () => {
|
||||
// If the TOC widget can accommodate both the topmost
|
||||
// and bottommost items, scroll to the topmost item.
|
||||
// Otherwise, scroll to the bottommost one.
|
||||
// --- 核心修改点 1: 新的 IntersectionObserver 回调 ---
|
||||
handleIntersection = (entries: IntersectionObserverEntry[]) => {
|
||||
// 当用户点击链接导航时,忽略 IntersectionObserver 的结果,防止冲突
|
||||
if (this.anchorNavTarget) return;
|
||||
|
||||
if (this.anchorNavTarget || !this.tocEl) return;
|
||||
const activeHeading =
|
||||
document.querySelectorAll<HTMLDivElement>(`#toc .${this.visibleClass}`);
|
||||
if (!activeHeading.length) return;
|
||||
let latestVisibleEntry: IntersectionObserverEntry | undefined;
|
||||
|
||||
const topmost = activeHeading[0];
|
||||
const bottommost = activeHeading[activeHeading.length - 1];
|
||||
const tocHeight = this.tocEl.clientHeight;
|
||||
|
||||
let top;
|
||||
if (bottommost.getBoundingClientRect().bottom -
|
||||
topmost.getBoundingClientRect().top < 0.9 * tocHeight)
|
||||
top = topmost.offsetTop - 32;
|
||||
else
|
||||
top = bottommost.offsetTop - tocHeight * 0.8;
|
||||
|
||||
this.tocEl.scrollTo({
|
||||
top,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
};
|
||||
|
||||
update = () => {
|
||||
requestAnimationFrame(() => {
|
||||
this.toggleActiveHeading();
|
||||
// requestAnimationFrame(() => {
|
||||
this.scrollToActiveHeading();
|
||||
// });
|
||||
});
|
||||
};
|
||||
|
||||
fallback = () => {
|
||||
if (!this.sections.length) return;
|
||||
|
||||
for (let i = 0; i < this.sections.length; i++) {
|
||||
let offsetTop = this.sections[i].getBoundingClientRect().top;
|
||||
let offsetBottom = this.sections[i].getBoundingClientRect().bottom;
|
||||
|
||||
if (this.isInRange(offsetTop, 0, window.innerHeight)
|
||||
|| this.isInRange(offsetBottom, 0, window.innerHeight)
|
||||
|| (offsetTop < 0 && offsetBottom > window.innerHeight)) {
|
||||
this.markActiveHeading(i);
|
||||
// 找到进入视口的、最新的(在文档中位置最靠后)的条目
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
latestVisibleEntry = entry;
|
||||
}
|
||||
}
|
||||
else if (offsetTop > window.innerHeight) break;
|
||||
}
|
||||
};
|
||||
|
||||
markActiveHeading = (idx: number)=> {
|
||||
this.active[idx] = true;
|
||||
};
|
||||
if (latestVisibleEntry) {
|
||||
const id =
|
||||
latestVisibleEntry.target.children[0]?.getAttribute("id");
|
||||
const idx = id ? this.headingIdxMap.get(id) : undefined;
|
||||
if (idx !== undefined) {
|
||||
this.setActiveIndex(idx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleAnchorClick = (event: Event) => {
|
||||
const anchor = event
|
||||
.composedPath()
|
||||
.find((element) => element instanceof HTMLAnchorElement);
|
||||
// --- 核心修改点 2: 统一设置活动索引并更新UI ---
|
||||
setActiveIndex = (index: number | null) => {
|
||||
// 如果索引没有变化,则不执行任何操作
|
||||
if (this.activeIndex === index) return;
|
||||
|
||||
this.activeIndex = index;
|
||||
|
||||
// 更新UI
|
||||
this.tocEntries.forEach((entry, i) => {
|
||||
if (i === this.activeIndex) {
|
||||
entry.classList.add(this.visibleClass);
|
||||
} else {
|
||||
entry.classList.remove(this.visibleClass);
|
||||
}
|
||||
});
|
||||
|
||||
// 只有在非用户点击导航时才自动滚动
|
||||
if (!this.anchorNavTarget) {
|
||||
this.scrollToActiveHeading();
|
||||
}
|
||||
};
|
||||
|
||||
scrollToActiveHeading = () => {
|
||||
if (this.activeIndex === null || !this.tocEl) return;
|
||||
|
||||
const activeEntry = this.tocEntries[this.activeIndex];
|
||||
if (!activeEntry) return;
|
||||
|
||||
const tocHeight = this.tocEl.clientHeight;
|
||||
const entryTop = activeEntry.offsetTop;
|
||||
const entryHeight = activeEntry.offsetHeight;
|
||||
|
||||
// 检查活动项是否在可视区域内
|
||||
if (
|
||||
entryTop < this.tocEl.scrollTop ||
|
||||
entryTop + entryHeight > this.tocEl.scrollTop + tocHeight
|
||||
) {
|
||||
this.tocEl.scrollTo({
|
||||
top: entryTop - tocHeight / 2 + entryHeight / 2, // 尝试滚动到中间位置
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// --- 核心修改点 3: 处理点击事件 ---
|
||||
handleAnchorClick = (event: Event) => {
|
||||
// 找到被点击的 <a> 标签
|
||||
const anchor = event.currentTarget as HTMLAnchorElement;
|
||||
|
||||
if (anchor) {
|
||||
const id = decodeURIComponent(anchor.hash?.substring(1));
|
||||
const idx = this.headingIdxMap.get(id);
|
||||
|
||||
if (idx !== undefined) {
|
||||
this.anchorNavTarget = this.headings[idx];
|
||||
} else {
|
||||
this.anchorNavTarget = null;
|
||||
// 标记正在进行锚点导航,以暂时禁用 IntersectionObserver 的更新
|
||||
const targetHeading = document.getElementById(id);
|
||||
if (targetHeading) {
|
||||
this.anchorNavTarget = targetHeading;
|
||||
// 在导航完成后重置标志
|
||||
setTimeout(() => {
|
||||
this.anchorNavTarget = null;
|
||||
}, 1000); // 1秒后自动清除,以防万一
|
||||
}
|
||||
// 立即更新高亮
|
||||
this.setActiveIndex(idx);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
isInRange(value: number, min: number, max: number) {
|
||||
return min < value && value < max;
|
||||
};
|
||||
|
||||
connectedCallback() {
|
||||
// wait for the onload animation to finish, which makes the `getBoundingClientRect` return correct values
|
||||
const element = document.querySelector('.prose');
|
||||
if (element) {
|
||||
element.addEventListener('animationend', () => {
|
||||
connectedCallback() {
|
||||
requestAnimationFrame(() => {
|
||||
this.init();
|
||||
}, { once: true });
|
||||
} else {
|
||||
console.debug('Animation element not found');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
init() {
|
||||
this.tocEl = document.getElementById(
|
||||
"toc-inner-wrapper"
|
||||
);
|
||||
init() {
|
||||
this.tocEl = document.getElementById("toc-inner-wrapper") || this;
|
||||
|
||||
if (!this.tocEl) return;
|
||||
this.tocEntries = Array.from(
|
||||
this.querySelectorAll<HTMLAnchorElement>("a[href^='#']"),
|
||||
);
|
||||
|
||||
this.tocEl.addEventListener("click", this.handleAnchorClick, {
|
||||
capture: true,
|
||||
});
|
||||
if (this.tocEntries.length === 0) return;
|
||||
|
||||
this.activeIndicator = document.getElementById("active-indicator");
|
||||
this.sections = new Array(this.tocEntries.length);
|
||||
for (let i = 0; i < this.tocEntries.length; i++) {
|
||||
const entry = this.tocEntries[i];
|
||||
const id = decodeURIComponent(entry.hash?.substring(1));
|
||||
const heading = document.getElementById(id);
|
||||
const section = heading?.parentElement;
|
||||
|
||||
this.tocEntries = Array.from(
|
||||
document.querySelectorAll<HTMLAnchorElement>("#toc a[href^='#']")
|
||||
);
|
||||
|
||||
if (this.tocEntries.length === 0) return;
|
||||
|
||||
this.sections = new Array(this.tocEntries.length);
|
||||
this.headings = new Array(this.tocEntries.length);
|
||||
for (let i = 0; i < this.tocEntries.length; i++) {
|
||||
const id = decodeURIComponent(this.tocEntries[i].hash?.substring(1));
|
||||
const heading = document.getElementById(id);
|
||||
const section = heading?.parentElement;
|
||||
if (heading instanceof HTMLElement && section instanceof HTMLElement) {
|
||||
this.headings[i] = heading;
|
||||
this.sections[i] = section;
|
||||
this.headingIdxMap.set(id, i);
|
||||
if (
|
||||
heading instanceof HTMLElement &&
|
||||
section instanceof HTMLElement
|
||||
) {
|
||||
this.sections[i] = section;
|
||||
this.headingIdxMap.set(id, i);
|
||||
// 为每个链接单独添加点击事件监听器
|
||||
entry.addEventListener("click", this.handleAnchorClick);
|
||||
}
|
||||
}
|
||||
|
||||
this.sections.forEach((section) => this.observer.observe(section));
|
||||
|
||||
// 页面加载时,设置第一个标题为活动状态
|
||||
this.setActiveIndex(0);
|
||||
}
|
||||
this.active = new Array(this.tocEntries.length).fill(false);
|
||||
|
||||
this.sections.forEach((section) =>
|
||||
this.observer.observe(section)
|
||||
);
|
||||
disconnectedCallback() {
|
||||
this.sections.forEach((section) =>
|
||||
this.observer.unobserve(section),
|
||||
);
|
||||
this.observer.disconnect();
|
||||
this.tocEntries.forEach((entry) => {
|
||||
entry.removeEventListener("click", this.handleAnchorClick);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.fallback();
|
||||
this.update();
|
||||
};
|
||||
|
||||
disconnectedCallback() {
|
||||
this.sections.forEach((section) =>
|
||||
this.observer.unobserve(section)
|
||||
);
|
||||
this.observer.disconnect();
|
||||
this.tocEl?.removeEventListener("click", this.handleAnchorClick);
|
||||
};
|
||||
}
|
||||
|
||||
if (!customElements.get("table-of-contents")) {
|
||||
customElements.define("table-of-contents", TableOfContents);
|
||||
}
|
||||
</script>
|
||||
if (!customElements.get("table-of-contents")) {
|
||||
customElements.define("table-of-contents", TableOfContents);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -37,6 +37,7 @@ enum I18nKey {
|
||||
notFoundDesc = "notFoundDesc",
|
||||
backToHome = "backToHome",
|
||||
pinned = "pinned",
|
||||
toc = "toc",
|
||||
}
|
||||
|
||||
export default I18nKey;
|
||||
|
||||
@@ -40,4 +40,5 @@ export const en: Translation = {
|
||||
[Key.notFoundDesc]: "The link is broken, the page has gone missing",
|
||||
[Key.backToHome]: "Back to Home",
|
||||
[Key.pinned]: "Pinned",
|
||||
[Key.toc]: "Toc",
|
||||
};
|
||||
|
||||
@@ -40,4 +40,5 @@ export const zh_CN: Translation = {
|
||||
[Key.notFoundDesc]: "你访问的链接已断开,页面走丢了",
|
||||
[Key.backToHome]: "返回首页",
|
||||
[Key.pinned]: "置顶",
|
||||
[Key.toc]: "目录",
|
||||
};
|
||||
|
||||
@@ -40,4 +40,5 @@ export const zh_TW: Translation = {
|
||||
[Key.notFoundDesc]: "你訪問的連結已斷開,頁面走丟了",
|
||||
[Key.backToHome]: "返回首頁",
|
||||
[Key.pinned]: "置頂",
|
||||
[Key.toc]: "目錄",
|
||||
};
|
||||
|
||||
@@ -405,7 +405,19 @@ const setup = () => {
|
||||
}
|
||||
})
|
||||
window.swup.hooks.on('content:replace', initCustomScrollbar)
|
||||
window.swup.hooks.on('visit:start', (visit: {to: {url: string}}) => {
|
||||
window.swup.hooks.on('visit:start', (visit: {to: {url: string};from: { url: string };containers: string[]}) => {
|
||||
|
||||
const postUrlPattern = "/posts/";
|
||||
const isToPost = visit.to.url.includes(postUrlPattern);
|
||||
const isFromPost = visit.from.url.includes(postUrlPattern);
|
||||
|
||||
if (isToPost || isFromPost) {
|
||||
visit.containers = ["main", "#sidebar"]; // 刷新两者
|
||||
} else {
|
||||
// 否则(例如,从首页导航到关于页面),
|
||||
// 仅刷新主内容区域。
|
||||
visit.containers = ["main"];
|
||||
}
|
||||
// change banner height immediately when a link is clicked
|
||||
const bodyElement = document.querySelector('body')
|
||||
if (pathsEqual(visit.to.url, url('/'))) {
|
||||
@@ -419,12 +431,6 @@ const setup = () => {
|
||||
if (heightExtend) {
|
||||
heightExtend.classList.remove('hidden')
|
||||
}
|
||||
|
||||
// Hide the TOC while scrolling back to top
|
||||
let toc = document.getElementById('toc-wrapper');
|
||||
if (toc) {
|
||||
toc.classList.add('toc-not-ready')
|
||||
}
|
||||
});
|
||||
window.swup.hooks.on('page:view', () => {
|
||||
// hide the temp high element when the transition is done
|
||||
@@ -439,12 +445,6 @@ const setup = () => {
|
||||
if (heightExtend) {
|
||||
heightExtend.classList.add('hidden')
|
||||
}
|
||||
|
||||
// Just make the transition looks better
|
||||
const toc = document.getElementById('toc-wrapper');
|
||||
if (toc) {
|
||||
toc.classList.remove('toc-not-ready')
|
||||
}
|
||||
}, 200)
|
||||
});
|
||||
}
|
||||
@@ -455,7 +455,6 @@ if (window?.swup?.hooks) {
|
||||
}
|
||||
|
||||
let backToTopBtn = document.getElementById('back-to-top-btn');
|
||||
let toc = document.getElementById('toc-wrapper');
|
||||
let navbar = document.getElementById('navbar-wrapper')
|
||||
function scrollFunction() {
|
||||
let bannerHeight = window.innerHeight * (BANNER_HEIGHT / 100)
|
||||
@@ -468,14 +467,6 @@ function scrollFunction() {
|
||||
}
|
||||
}
|
||||
|
||||
if (bannerEnabled && toc) {
|
||||
if (document.body.scrollTop > bannerHeight || document.documentElement.scrollTop > bannerHeight) {
|
||||
toc.classList.remove('toc-hide')
|
||||
} else {
|
||||
toc.classList.add('toc-hide')
|
||||
}
|
||||
}
|
||||
|
||||
if (!bannerEnabled) return
|
||||
if (navbar) {
|
||||
const NAVBAR_HEIGHT = 72
|
||||
|
||||
@@ -6,7 +6,6 @@ import SideBar from "@components/widget/SideBar.astro";
|
||||
import type { MarkdownHeading } from "astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import ImageWrapper from "../components/misc/ImageWrapper.astro";
|
||||
import TOC from "../components/widget/TOC.astro";
|
||||
import { siteConfig } from "../config";
|
||||
import {
|
||||
BANNER_HEIGHT,
|
||||
@@ -102,24 +101,4 @@ const mainPanelTop = siteConfig.banner.enable
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- The things that should be under the banner, only the TOC for now -->
|
||||
<div class="absolute w-full z-0 hidden 2xl:block">
|
||||
<div class="relative max-w-[var(--page-width)] mx-auto">
|
||||
<!-- TOC component -->
|
||||
{siteConfig.toc.enable && <div id="toc-wrapper" class:list={["hidden lg:block transition absolute top-0 -right-[var(--toc-width)] w-[var(--toc-width)] items-center",
|
||||
{"toc-hide": siteConfig.banner.enable}]}
|
||||
>
|
||||
<div id="toc-inner-wrapper" class="fixed top-14 w-[var(--toc-width)] h-[calc(100vh_-_20rem)] overflow-y-scroll overflow-x-hidden hide-scrollbar">
|
||||
<div id="toc" class="w-full h-full transition-swup-fade ">
|
||||
<div class="h-8 w-full"></div>
|
||||
<TOC headings={headings}></TOC>
|
||||
<div class="h-8 w-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<!-- #toc needs to exist for Swup to work normally -->
|
||||
{!siteConfig.toc.enable && <div id="toc"></div>}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
@@ -65,9 +65,9 @@
|
||||
@apply opacity-0 pointer-events-none
|
||||
}
|
||||
|
||||
#toc-inner-wrapper {
|
||||
/* #toc-inner-wrapper {
|
||||
mask-image: linear-gradient(to bottom, transparent 0%, black 2rem, black calc(100% - 2rem), transparent 100%);
|
||||
}
|
||||
} */
|
||||
|
||||
.hide-scrollbar {
|
||||
scrollbar-width: none;
|
||||
|
||||
Reference in New Issue
Block a user