Browse Source

Add floating table of contents button to article

master
buttercat1791 2 years ago committed by Silberengel
parent
commit
a0743d425a
  1. 81
      src/lib/Article.svelte
  2. 12
      src/lib/components/Navigation.svelte
  3. 6
      src/lib/stores.ts

81
src/lib/Article.svelte

@ -1,11 +1,12 @@ @@ -1,11 +1,12 @@
<script lang="ts">
import { ndk } from '$lib/ndk';
import { idList, isLeftHamburgerMenuInUse, leftHamburgerMenuHrefs, leftHamburgerMenuItems } from '$lib/stores';
import { idList, isLeftMenuMenuInUse, showLeftMenu } from '$lib/stores';
import type { NDKEvent } from '@nostr-dev-kit/ndk';
import { page } from '$app/stores';
import { Heading, Sidebar, SidebarGroup, SidebarItem, SidebarWrapper, Skeleton, TextPlaceholder } from 'flowbite-svelte';
import { Button, Heading, Sidebar, SidebarGroup, SidebarItem, SidebarWrapper, Skeleton, TextPlaceholder, Tooltip } from 'flowbite-svelte';
import showdown from 'showdown';
import { onMount } from 'svelte';
import { BookOutline } from 'flowbite-svelte-icons';
$: activeHash = $page.url.hash;
@ -13,24 +14,7 @@ @@ -13,24 +14,7 @@
const eventPromises = $idList.map(async (id) => await $ndk.fetchEvent(id));
const events = await Promise.all(eventPromises);
const filteredEvents = events.filter((event) => event != null);
const eventNames: string[] = [];
const eventHrefs = new Map<string, string>();
filteredEvents.forEach(event => {
const title = event.getMatchingTags('title')[0][1];
eventNames.push(title);
const normalizedTitle = normalizeHashPath(title);
eventHrefs.set(title, `${$page.url.pathname}#${normalizedTitle}`);
});
leftHamburgerMenuItems.set(eventNames);
leftHamburgerMenuHrefs.set(eventHrefs);
return filteredEvents;
return events.filter((event) => event != null);
}
function normalizeHashPath(str: string): string {
@ -57,21 +41,49 @@ @@ -57,21 +41,49 @@
}
}
const sidebarBreakpointWidth = 1140;
const setLeftHamburgerMenuInUse = () => {
isLeftHamburgerMenuInUse.set(window.innerWidth < sidebarBreakpointWidth ? true : false);
let showToc: boolean = true;
let showTocButton: boolean = false;
const tocBreakpoint = 1140;
/**
* Hides the table of contents sidebar when the window shrinks below a certain size. This
* prevents the sidebar from occluding the article content.
*/
const setTocVisibilityOnResize = () => {
showToc = window.innerWidth >= tocBreakpoint;
showTocButton = window.innerWidth < tocBreakpoint;
};
/**
* Hides the table of contents sidebar when the user clicks outside of it.
*/
const 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', setLeftHamburgerMenuInUse);
window.addEventListener('resize', setTocVisibilityOnResize);
window.addEventListener('click', hideTocOnClick);
return () => {
window.removeEventListener('hashchange', scrollToElementWithOffset);
window.removeEventListener('resize', setLeftHamburgerMenuInUse);
window.removeEventListener('resize', setTocVisibilityOnResize);
window.removeEventListener('click', hideTocOnClick);
};
});
@ -79,16 +91,29 @@ @@ -79,16 +91,29 @@
</script>
{#await getEvents()}
{#if !$isLeftHamburgerMenuInUse}
<Sidebar class='sidebar-leather fixed top-20 left-0 px-4 w-60'>
<SidebarWrapper>
<Skeleton/>
</SidebarWrapper>
</Sidebar>
<TextPlaceholder class='max-w-2xl'/>
{/if}
{:then events}
{#if !$isLeftHamburgerMenuInUse}
{#if showTocButton && !showToc}
<Button
class='btn-leather fixed top-20 left-4 h-6 w-6'
outline={true}
on:click={ev => {
showToc = true;
ev.stopPropagation();
}}
>
<BookOutline />
</Button>
<Tooltip>
Show Table of Contents
</Tooltip>
{/if}
{#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'>

12
src/lib/components/Navigation.svelte

@ -1,21 +1,13 @@ @@ -1,21 +1,13 @@
<script lang="ts">
import { isLeftHamburgerMenuInUse, leftHamburgerMenuHrefs, leftHamburgerMenuItems } from '$lib/stores';
import { DarkMode, Navbar, NavLi, NavUl, NavHamburger, NavBrand } from 'flowbite-svelte';
import { get } from 'svelte/store';
let className: string;
export { className as class };
let leftMenuOpen: boolean = false;
</script>
<Navbar class={`Navbar navbar-leather ${className}`}>
{#if $isLeftHamburgerMenuInUse}
<NavHamburger class='btn-leather' />
<NavUl class='ul-leather'>
{#each $leftHamburgerMenuItems as menuItem}
<NavLi href={$leftHamburgerMenuHrefs.get(menuItem)}>{menuItem}</NavLi>
{/each}
</NavUl>
{/if}
<div class='flex flex-grow justify-between'>
<NavBrand href='./'>
<h1 class='font-serif'>Alexandria</h1>

6
src/lib/stores.ts

@ -2,7 +2,5 @@ import { writable } from "svelte/store"; @@ -2,7 +2,5 @@ import { writable } from "svelte/store";
export let idList = writable<string[]>([]);
export const isLeftHamburgerMenuInUse = writable(false);
export const leftHamburgerMenuItems = writable<string[]>([]);
export const leftHamburgerMenuHrefs = writable<Map<string, string>>(new Map());
export const isLeftMenuMenuInUse = writable(false);
export const showLeftMenu = writable(false);

Loading…
Cancel
Save