refactor(side toolbar): Use Vue on the TOC Button

This will fix bug on toc button, and close #6
This commit is contained in:
HPCesia 2025-04-01 19:07:34 +08:00
parent 28b672503f
commit e4f1dff4c1
2 changed files with 76 additions and 40 deletions

View File

@ -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();

View File

@ -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>