2 changed files with 154 additions and 0 deletions
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
<script lang="ts"> |
||||
import { |
||||
Button, |
||||
Sidebar, |
||||
SidebarGroup, |
||||
SidebarItem, |
||||
SidebarWrapper, |
||||
Skeleton, |
||||
TextPlaceholder, |
||||
Tooltip, |
||||
} from "flowbite-svelte"; |
||||
import { onMount } from "svelte"; |
||||
import { BookOutline } from "flowbite-svelte-icons"; |
||||
import { pharosInstance } from "$lib/parser"; |
||||
import { page } from "$app/state"; |
||||
|
||||
let { rootId } = $props<{ rootId: string }>(); |
||||
|
||||
if (rootId !== $pharosInstance.getRootIndexId()) { |
||||
console.error("Root ID does not match parser root index ID"); |
||||
} |
||||
|
||||
const tocBreakpoint = 1140; |
||||
|
||||
let activeHash = $state(page.url.hash); |
||||
let showToc: boolean = $state(true); |
||||
let showTocButton: boolean = $state(false); |
||||
|
||||
function normalizeHashPath(str: string): string { |
||||
return str |
||||
.toLowerCase() |
||||
.replace(/\s+/g, "-") |
||||
.replace(/[^\w-]/g, ""); |
||||
} |
||||
|
||||
function scrollToElementWithOffset() { |
||||
const hash = window.location.hash; |
||||
if (hash) { |
||||
const targetElement = document.querySelector(hash); |
||||
if (targetElement) { |
||||
const headerOffset = 80; |
||||
const elementPosition = targetElement.getBoundingClientRect().top; |
||||
const offsetPosition = elementPosition + window.scrollY - headerOffset; |
||||
|
||||
window.scrollTo({ |
||||
top: offsetPosition, |
||||
behavior: "auto", |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Hides the table of contents sidebar when the window shrinks below a certain size. This |
||||
* prevents the sidebar from occluding the article content. |
||||
*/ |
||||
function setTocVisibilityOnResize() { |
||||
showToc = window.innerWidth >= tocBreakpoint; |
||||
showTocButton = window.innerWidth < tocBreakpoint; |
||||
} |
||||
|
||||
/** |
||||
* Hides the table of contents sidebar when the user clicks outside of it. |
||||
*/ |
||||
function hideTocOnClick(ev: MouseEvent) { |
||||
const target = ev.target as HTMLElement; |
||||
|
||||
if (target.closest(".sidebar-leather") || target.closest(".btn-leather")) { |
||||
return; |
||||
} |
||||
|
||||
if (showToc) { |
||||
showToc = false; |
||||
} |
||||
} |
||||
|
||||
onMount(() => { |
||||
// Always check whether the TOC sidebar should be visible. |
||||
setTocVisibilityOnResize(); |
||||
|
||||
window.addEventListener("hashchange", scrollToElementWithOffset); |
||||
// Also handle the case where the user lands on the page with a hash in the URL |
||||
scrollToElementWithOffset(); |
||||
|
||||
window.addEventListener("resize", setTocVisibilityOnResize); |
||||
window.addEventListener("click", hideTocOnClick); |
||||
|
||||
return () => { |
||||
window.removeEventListener("hashchange", scrollToElementWithOffset); |
||||
window.removeEventListener("resize", setTocVisibilityOnResize); |
||||
window.removeEventListener("click", hideTocOnClick); |
||||
}; |
||||
}); |
||||
</script> |
||||
|
||||
{#if showTocButton && !showToc} |
||||
<Button |
||||
class="btn-leather h-6 !w-auto" |
||||
outline={true} |
||||
on:click={(ev) => { |
||||
showToc = true; |
||||
ev.stopPropagation(); |
||||
}} |
||||
> |
||||
<BookOutline class="!fill-none mr-1"/> |
||||
Table of Contents |
||||
</Button> |
||||
{/if} |
||||
<!-- TODO: Get TOC from parser. --> |
||||
<!-- {#if showToc} |
||||
<Sidebar class='sidebar-leather fixed top-20 left-0 px-4 w-60' {activeHash}> |
||||
<SidebarWrapper> |
||||
<SidebarGroup class='sidebar-group-leather overflow-y-scroll'> |
||||
{#each events as event} |
||||
<SidebarItem |
||||
class='sidebar-item-leather' |
||||
label={event.getMatchingTags('title')[0][1]} |
||||
href={`${$page.url.pathname}#${normalizeHashPath(event.getMatchingTags('title')[0][1])}`} |
||||
/> |
||||
{/each} |
||||
</SidebarGroup> |
||||
</SidebarWrapper> |
||||
</Sidebar> |
||||
{/if} --> |
||||
Loading…
Reference in new issue