Make navigation mode switchable
This commit is contained in:
parent
a22fed7728
commit
05dd90ec80
1
public/static/svg/change.svg
Normal file
1
public/static/svg/change.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M480-40q-112 0-216-66T100-257v137H40v-240h240v60H143q51 77 145.5 138.5T480-100q78 0 147.5-30t121-81.5Q800-263 830-332.5T860-480h60q0 91-34.5 171T791-169q-60 60-140 94.5T480-40ZM40-480q0-91 34.5-171T169-791q60-60 140-94.5T480-920q112 0 216 66t164 151v-137h60v240H680v-60h137q-51-77-145-138.5T480-860q-78 0-147.5 30t-121 81.5Q160-697 130-627.5T100-480H40Zm440 175-54-121-121-54 121-55 54-121 55 121 121 55-121 54-55 121Z"/></svg>
|
After Width: | Height: | Size: 524 B |
49
src/components/tree/Headings.astro
Normal file
49
src/components/tree/Headings.astro
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
---
|
||||||
|
import type { MarkdownHeading } from 'astro';
|
||||||
|
import { Maybe } from 'purify-ts';
|
||||||
|
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
headings: Maybe<MarkdownHeading[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Nested = MarkdownHeading & { children?: MarkdownHeading[] };
|
||||||
|
|
||||||
|
|
||||||
|
const { headings } = Astro.props;
|
||||||
|
|
||||||
|
function fold(headings: MarkdownHeading[]) {
|
||||||
|
const toc = [] as Nested[];
|
||||||
|
const map = new Map<number, Nested>();
|
||||||
|
for (const h of headings) {
|
||||||
|
const heading = { ...h };
|
||||||
|
map.set(heading.depth, heading);
|
||||||
|
if (heading.depth === 2)
|
||||||
|
toc.push(heading)
|
||||||
|
else {
|
||||||
|
const backref = map.get(heading.depth - 1)!;
|
||||||
|
backref.children
|
||||||
|
? backref.children.push(heading)
|
||||||
|
: backref.children = [heading];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toc;
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
{headings
|
||||||
|
.map(fold)
|
||||||
|
.map(headings =>
|
||||||
|
<ul class="link-tree__nav-list">
|
||||||
|
{headings.map(heading =>
|
||||||
|
<li class="link-tree__nav-list-item">
|
||||||
|
<a class="link-tree__nav-list-text link" href={`#${heading.slug}`}>
|
||||||
|
{heading.text}
|
||||||
|
</a>
|
||||||
|
<Astro.self headings={Maybe.fromNullable(heading.children)}/>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
.extract()
|
||||||
|
}
|
|
@ -3,12 +3,14 @@ import { PageTree, pathify } from "@utils/tree";
|
||||||
import { Maybe } from "purify-ts";
|
import { Maybe } from "purify-ts";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
export interface PagesProps {
|
||||||
tree: PageTree;
|
tree: PageTree;
|
||||||
slug: Maybe<string>;
|
slug: Maybe<string>;
|
||||||
prefix: Maybe<string>;
|
prefix: Maybe<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Props = PagesProps;
|
||||||
const { tree, slug, prefix } = Astro.props;
|
const { tree, slug, prefix } = Astro.props;
|
||||||
|
|
||||||
function compare(a: {title: string}, b: {title: string}) {
|
function compare(a: {title: string}, b: {title: string}) {
|
||||||
|
@ -37,12 +39,11 @@ function checkCurrent(checked: Maybe<string>) {
|
||||||
<li class="link-tree__nav-list-item">
|
<li class="link-tree__nav-list-item">
|
||||||
{page.slug
|
{page.slug
|
||||||
.chain(slug => prefix.map(prefix => pathify(prefix, slug)))
|
.chain(slug => prefix.map(prefix => pathify(prefix, slug)))
|
||||||
.mapOrDefault(href =>
|
.map(href => (page.current)
|
||||||
<a class="link-tree__nav-list-text link" class:list={{ current: page.current }} href={href}>
|
? <button class="link-tree__nav-list-text current">{page.title}</button>
|
||||||
{page.title}
|
: <a class="link-tree__nav-list-text link" href={href}>{page.title}</a>
|
||||||
</a>,
|
)
|
||||||
<span class="link-tree__nav-list-text">{page.title}</span>
|
.orDefault(<span class="link-tree__nav-list-text">{page.title}</span>)}
|
||||||
)}
|
|
||||||
<Astro.self tree={page} slug={slug} prefix={prefix} />
|
<Astro.self tree={page} slug={slug} prefix={prefix} />
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
|
@ -1,27 +1,65 @@
|
||||||
---
|
---
|
||||||
import List from "./List.astro";
|
import Pages from "./Pages.astro";
|
||||||
import { PageTree, pathify } from "@utils/tree";
|
import Headings from "./Headings.astro";
|
||||||
|
import { pathify } from "@utils/tree";
|
||||||
import type { Maybe } from "purify-ts";
|
import type { Maybe } from "purify-ts";
|
||||||
|
import type { MarkdownHeading } from "astro";
|
||||||
|
import type { PagesProps } from "./Pages.astro";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
heading: string;
|
heading: string;
|
||||||
tree: PageTree;
|
pages: Maybe<PagesProps>;
|
||||||
slug: Maybe<string>;
|
headings: Maybe<MarkdownHeading[]>;
|
||||||
prefix: Maybe<string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { heading, tree, slug, prefix } = Astro.props;
|
const { heading, pages, headings } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="link-tree">
|
<section class="link-tree">
|
||||||
<h2 class="link-tree__heading">
|
<!-- Nav mode switch -->
|
||||||
{prefix.map(pathify).mapOrDefault(href =>
|
<input id="link-tree-mode" type="checkbox" hidden />
|
||||||
<a class="link-tree__heading-text" href={href}>{heading}</a>,
|
<label id="link-tree-switch" class="link-tree__switch"
|
||||||
<span class="link-tree__heading-text">{heading}</span>
|
for="link-tree-mode"
|
||||||
)}
|
role="button"
|
||||||
</h2>
|
tabindex="0"
|
||||||
<nav class="link-tree__nav">
|
title="Switch navigation mode">
|
||||||
<List tree={tree} slug={slug} prefix={prefix} />
|
<img src="/static/svg/change.svg" alt="Switch navigation mode" width="24" height="24"/>
|
||||||
</nav>
|
</label>
|
||||||
|
|
||||||
|
<!-- Primary view displaying current headings -->
|
||||||
|
<div class="v-alt">
|
||||||
|
<h2 class="link-tree__heading">
|
||||||
|
<span class="link-tree__heading-text">Herein</span>
|
||||||
|
</h2>
|
||||||
|
<nav class="link-tree__nav">
|
||||||
|
<Headings headings={headings} />
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Optional view displaying page tree -->
|
||||||
|
<div class="v-prime">
|
||||||
|
<h2 class="link-tree__heading">
|
||||||
|
{pages.chain(x => x.prefix)
|
||||||
|
.map(pathify)
|
||||||
|
.mapOrDefault(href =>
|
||||||
|
<a class="link-tree__heading-text" href={href}>{heading}</a>,
|
||||||
|
<span class="link-tree__heading-text">{heading}</span>
|
||||||
|
)}
|
||||||
|
</h2>
|
||||||
|
<nav class="link-tree__nav">
|
||||||
|
{pages.map(pages => <Pages {...pages} />).extract()}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const button = document.getElementById("link-tree-switch");
|
||||||
|
|
||||||
|
button?.addEventListener("keypress", e => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
button!.click();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -4,9 +4,11 @@ import Tree from "@components/tree/Tree.astro";
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import { Maybe, MaybeAsync } from "purify-ts";
|
import { Maybe, MaybeAsync } from "purify-ts";
|
||||||
import { collapse, type PageTree } from "@utils/tree";
|
import { collapse, type PageTree } from "@utils/tree";
|
||||||
|
import type { MarkdownHeading } from "astro";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
headings?: MarkdownHeading[];
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
@ -14,11 +16,7 @@ interface Props {
|
||||||
tree?: PageTree;
|
tree?: PageTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { frontmatter, slug } = Astro.props;
|
||||||
frontmatter,
|
|
||||||
slug,
|
|
||||||
tree,
|
|
||||||
} = Astro.props;
|
|
||||||
|
|
||||||
|
|
||||||
function constructTree(tree?: PageTree) {
|
function constructTree(tree?: PageTree) {
|
||||||
|
@ -29,19 +27,21 @@ function constructTree(tree?: PageTree) {
|
||||||
.map(collapse));
|
.map(collapse));
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageTree = (await constructTree(tree))
|
const headings = Maybe.fromNullable(Astro.props.headings);
|
||||||
.orDefaultLazy(() => { throw new Error("Couldn't load page tree") });
|
const pages = (await constructTree(Astro.props.tree))
|
||||||
|
.map(tree => ({
|
||||||
|
tree,
|
||||||
|
slug: Maybe.fromNullable(slug),
|
||||||
|
prefix: Maybe.of("/wiki/")
|
||||||
|
}));
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Base>
|
<Base>
|
||||||
<main class="wiki-main">
|
<main class="wiki-main">
|
||||||
<Tree heading="Personal Wiki"
|
<Tree heading="Personal Wiki" pages={pages} headings={headings} />
|
||||||
tree={pageTree}
|
|
||||||
slug={Maybe.fromNullable(slug)}
|
|
||||||
prefix={Maybe.of("/wiki/")}
|
|
||||||
/>
|
|
||||||
<article class="wiki-article markdown">
|
<article class="wiki-article markdown">
|
||||||
<h1>{frontmatter.title}</h1>
|
<h1>{frontmatter?.title}</h1>
|
||||||
<slot />
|
<slot />
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
|
import Wiki from '@layouts/Wiki.astro';
|
||||||
import type { InferGetStaticPropsType } from 'astro';
|
import type { InferGetStaticPropsType } from 'astro';
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import { collapse } from '@utils/tree';
|
import { collapse } from '@utils/tree';
|
||||||
import Wiki from '@layouts/Wiki.astro';
|
|
||||||
|
|
||||||
|
|
||||||
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
|
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
|
||||||
|
@ -14,9 +14,14 @@ export async function getStaticPaths() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tree, entry } = Astro.props;
|
const { tree, entry } = Astro.props;
|
||||||
const { Content } = await entry.render();
|
const { headings, Content } = await entry.render();
|
||||||
---
|
---
|
||||||
|
|
||||||
<Wiki slug={entry.slug} frontmatter={entry.data} tree={tree}>
|
<Wiki
|
||||||
|
frontmatter={entry.data}
|
||||||
|
headings={headings}
|
||||||
|
slug={entry.slug}
|
||||||
|
tree={tree}
|
||||||
|
>
|
||||||
<Content />
|
<Content />
|
||||||
</Wiki>
|
</Wiki>
|
||||||
|
|
|
@ -1,8 +1,21 @@
|
||||||
.link-tree {
|
.link-tree {
|
||||||
|
position: relative;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&__switch {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em;
|
||||||
|
left: 0.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__heading {
|
&__heading {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
border-bottom: 1px solid;
|
border-bottom: 1px solid;
|
||||||
border-image: linear-gradient(to right, transparent, lightgray, transparent) 1;
|
border-image: linear-gradient(to right, transparent, lightgray, transparent) 1;
|
||||||
|
|
||||||
|
@ -43,9 +56,21 @@
|
||||||
|
|
||||||
&.current {
|
&.current {
|
||||||
background-color: var(--c-primary);
|
background-color: var(--c-primary);
|
||||||
|
border: unset;
|
||||||
border-radius: 0.33em;
|
border-radius: 0.33em;
|
||||||
color: white;
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#link-tree-mode {
|
||||||
|
&:checked ~ .v-prime {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:not(:checked) ~ .v-alt {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue