Commit e4f1dff
Changed files (2)
src
components
widgets
SideToolBar
src/components/widgets/SideToolBar/TocButton.vue
@@ -0,0 +1,72 @@
+<script setup lang="ts">
+import { onMounted, onUnmounted, ref } from 'vue';
+
+const isOpen = ref(false);
+const isWideScreen = ref(false);
+const hasToc = ref(false);
+const tocWrapper = ref<HTMLElement | null>(null);
+
+const handleResize = () => {
+ if (window.innerWidth > 1280) {
+ isWideScreen.value = true;
+ } else {
+ isWideScreen.value = false;
+ }
+};
+
+onMounted(() => {
+ const setup = () => {
+ const toc = document.getElementById('toc');
+ if (toc && tocWrapper.value) {
+ hasToc.value = true;
+ const remainAttrs = ['class', 'style'];
+ tocWrapper.value.innerHTML = '';
+ tocWrapper.value.appendChild(toc.cloneNode(true));
+ tocWrapper.value.children[0].id = 'stb-toc-content';
+ Array.from(tocWrapper.value.children[0].attributes).forEach((attr) => {
+ if (!remainAttrs.includes(attr.name)) {
+ tocWrapper.value!.children[0].removeAttribute(attr.name);
+ }
+ });
+ }
+ window.addEventListener('resize', handleResize);
+ handleResize();
+ };
+ const cleanup = () => {
+ isOpen.value = false;
+ if (tocWrapper.value) tocWrapper.value.innerHTML = '';
+ hasToc.value = false;
+ window.removeEventListener('resize', handleResize);
+ isWideScreen.value = false;
+ };
+
+ document.addEventListener('astro:page-load', setup);
+ setup();
+ document.addEventListener('astro:before-swap', cleanup);
+});
+</script>
+
+<template>
+ <div
+ :class="{
+ hidden: !hasToc || isWideScreen,
+ }"
+ >
+ <button
+ ref="buttonRef"
+ class="btn btn-circle btn-secondary btn-sm"
+ @click="isOpen = !isOpen"
+ >
+ <slot name="icon" />
+ </button>
+ <div
+ ref="tocWrapper"
+ class="rounded-box absolute w-[calc(100vw-4rem)] -translate-x-1/2 -translate-y-1/2 max-w-72 backdrop-blur-md duration-300 text-base-content text-start"
+ :class="{
+ '-translate-x-[calc(100%+0.5rem)]! -translate-y-[calc(100%-2.5rem)]!':
+ isOpen && !isWideScreen,
+ 'scale-0 opacity-0': !isOpen || isWideScreen,
+ }"
+ />
+ </div>
+</template>
src/components/SideToolBar.astro
@@ -3,6 +3,7 @@ import { articleConfig } from '@/config';
import { Icon } from 'astro-icon/components';
import Button from './widgets/Button.astro';
import DarkModeButton from './widgets/DarkModeButton.astro';
+import TocButton from './widgets/SideToolBar/TocButton.vue';
---
<div id="side-toolbar" class="fixed right-0 bottom-10 z-30 grid grid-cols-1 gap-2">
@@ -20,19 +21,9 @@ import DarkModeButton from './widgets/DarkModeButton.astro';
</Button>
{
articleConfig.toc && (
- <Fragment>
- <Button id="stb-toc" class="btn-circle btn-secondary btn-sm hidden xl:hidden!">
- <input
- type="checkbox"
- class="peer absolute z-10 h-8 w-8 cursor-pointer appearance-none border-0"
- />
- <Icon name="material-symbols:toc-rounded" />
- <div
- id="stb-toc-wrapper"
- class="rounded-box absolute w-[calc(100vw-4rem)] max-w-72 backdrop-blur-md duration-300 peer-checked:-translate-x-[calc(50%+1.5rem)] peer-[:not(:checked)]:scale-0"
- />
- </Button>
- </Fragment>
+ <TocButton client:media="(width <= 80rem)">
+ <Icon name="material-symbols:toc-rounded" slot="icon" />
+ </TocButton>
)
}
<Button id="stb-back-to-top" class="group btn-circle btn-secondary btn-sm">
@@ -63,8 +54,6 @@ import DarkModeButton from './widgets/DarkModeButton.astro';
const stbBackToTop = document.getElementById('stb-back-to-top');
const stbBackToTopIcon = document.getElementById('stb-back-to-top-icon');
const stbReadPercent = document.getElementById('stb-read-percentage');
- const stbToc = document.getElementById('stb-toc');
- const stbTocWrapper = document.getElementById('stb-toc-wrapper');
stbBackToTop?.addEventListener('click', () => {
window.scrollTo({
@@ -73,29 +62,6 @@ import DarkModeButton from './widgets/DarkModeButton.astro';
});
});
- // 清理可能存在的旧目录
- stbTocWrapper!.innerHTML = '';
- stbToc?.classList.add('hidden');
-
- const toc = document.getElementById('toc');
- if (toc && stbTocWrapper) {
- const remainAttrs = ['class', 'style'];
- stbTocWrapper.appendChild(toc.cloneNode(true));
- stbTocWrapper.children[0].id = 'stb-toc-content';
- Array.from(stbTocWrapper.children[0].attributes).forEach((attr) => {
- if (!remainAttrs.includes(attr.name)) {
- stbTocWrapper.children[0].removeAttribute(attr.name);
- }
- });
- stbToc?.classList.remove('hidden');
- }
-
- window.addEventListener('resize', () => {
- if (window.innerWidth > 1280) {
- (stbToc?.children[0] as HTMLInputElement).checked = false;
- }
- });
-
window.addEventListener('scroll', () => {
// 控制工具栏显隐
if (window.scrollY > 0) {
@@ -103,8 +69,6 @@ import DarkModeButton from './widgets/DarkModeButton.astro';
} else {
stbShow?.classList.add('translate-x-full');
stbShowMore!.querySelector('input')!.checked = true;
- stbTocWrapper?.classList.add('scale-0');
- stbTocWrapper?.classList.remove('-translate-x-full');
}
// 控制进度条
const scrolledPercentage = getReadingProgress();