refactor: rewrite the sidebar to include API docs
This commit is contained in:
parent
479254aa07
commit
c26251567c
@ -1,58 +0,0 @@
|
||||
---
|
||||
import type { Hierarchy, Post } from "../../layouts/ApiLayout.astro";
|
||||
import { splitAndLast } from "../utils";
|
||||
|
||||
const hierarchy: Hierarchy = Astro.props.hierarchy;
|
||||
|
||||
function postComparison(a: Post, b: Post): number {
|
||||
const s1 = splitAndLast(a.url);
|
||||
const s2 = splitAndLast(b.url);
|
||||
|
||||
return s1 > s2 ? 0 : 1;
|
||||
}
|
||||
|
||||
function appendSlash(s: string): string {
|
||||
if (s.endsWith("/")) {
|
||||
return s;
|
||||
} else {
|
||||
return s + "/";
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
{
|
||||
Object.entries(hierarchy.children).map(
|
||||
([folderName, [folderPost, children]]) => (
|
||||
<>
|
||||
{folderPost !== null ? (
|
||||
<a
|
||||
class="inline-block rounded-2xl w-full hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-3 py-2"
|
||||
href={appendSlash(folderPost.url)}
|
||||
>
|
||||
{splitAndLast(folderPost.url)}
|
||||
</a>
|
||||
) : (
|
||||
<div class="mt-6 px-2 py-1 uppercase font-display text-c-text-2 font-medium">
|
||||
{folderName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="pl-2 my-1">
|
||||
<ul class="border-l border-c-border-1">
|
||||
<Astro.self hierarchy={children} />
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
)
|
||||
}
|
||||
{
|
||||
hierarchy.posts.sort(postComparison).map((p) => (
|
||||
<a
|
||||
class="inline-block rounded-2xl w-full hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-3 py-2"
|
||||
href={appendSlash(p.url)}
|
||||
>
|
||||
{splitAndLast(p.url)}
|
||||
</a>
|
||||
))
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
---
|
||||
import { Sun, Moon, Laptop } from "lucide-astro";
|
||||
|
||||
const { showSidebarButton = true, version = "latest" } = Astro.props;
|
||||
---
|
||||
|
||||
|
@ -1,20 +1,63 @@
|
||||
---
|
||||
import type { PageEntry } from "../layouts/PagesLayout.astro";
|
||||
import type { AstroFileHierarchy } from "@/layouts/NewDocsLayout.astro";
|
||||
|
||||
const entry: PageEntry = Astro.props.entry;
|
||||
const entry: AstroFileHierarchy = Astro.props.entry;
|
||||
const post_url = entry.url + (entry.url.endsWith("/") ? "" : "/");
|
||||
|
||||
// this may deal with folders.
|
||||
// if so, it will turn any `-` into whitespace,
|
||||
// and remove any leading number
|
||||
const entry_title = entry.title.replaceAll("-", " ").replaceAll(/\d+_/g, "");
|
||||
const mono_class = entry.frontmatter.mono === true ? "font-mono " : "";
|
||||
---
|
||||
|
||||
{
|
||||
!entry.children && (
|
||||
entry.children.length > 0 ? (
|
||||
entry.has_index === true ? (
|
||||
<>
|
||||
<li>
|
||||
<a
|
||||
class={
|
||||
mono_class +
|
||||
"inline-block rounded-lg w-full hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-3 py-1"
|
||||
}
|
||||
href={post_url}
|
||||
>
|
||||
{entry_title}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<ul class="my-1 ml-2 border-l border-c-border-1">
|
||||
{entry.children!.map((nextEntry) => (
|
||||
<Astro.self entry={nextEntry} />
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
class={
|
||||
mono_class +
|
||||
"mt-6 px-3 py-1 uppercase font-display text-c-text-2 font-medium"
|
||||
}
|
||||
>
|
||||
{entry_title}
|
||||
</div>
|
||||
|
||||
<ul class="my-1">
|
||||
{entry.children!.map((nextEntry) => (
|
||||
<Astro.self entry={nextEntry} />
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
<li>
|
||||
<a
|
||||
class="inline-block rounded-lg w-full hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-3 py-1"
|
||||
class={
|
||||
mono_class +
|
||||
"inline-block rounded-lg w-full hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-3 py-1"
|
||||
}
|
||||
href={post_url}
|
||||
>
|
||||
{entry.title}
|
||||
@ -22,7 +65,9 @@ const entry_title = entry.title.replaceAll("-", " ").replaceAll(/\d+_/g, "");
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
/*
|
||||
entry.children && (
|
||||
<>
|
||||
<div class="mt-6 px-3 py-1 uppercase font-display text-c-text-2 font-medium">
|
||||
@ -36,4 +81,5 @@ const entry_title = entry.title.replaceAll("-", " ").replaceAll(/\d+_/g, "");
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
*/
|
||||
}
|
||||
|
@ -1,150 +0,0 @@
|
||||
---
|
||||
import Navbar from "../components/Navbar.astro";
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
import TOC from "../components/TOC.astro";
|
||||
import Sidebar from "../components/ApiLayout/Sidebar.astro";
|
||||
import { splitAndLast } from "../components/utils";
|
||||
|
||||
const { headings } = Astro.props;
|
||||
|
||||
export type Post = {
|
||||
frontmatter: any;
|
||||
getHeadings: any;
|
||||
url: string;
|
||||
file: any;
|
||||
Content: any;
|
||||
default: any;
|
||||
};
|
||||
type Posts = Array<Readonly<Post>>;
|
||||
|
||||
const basePath = "/api/std";
|
||||
const posts: Posts = await Astro.glob(
|
||||
"../pages/en/latest/api/std/**/*.{md,mdx}",
|
||||
);
|
||||
|
||||
export type Hierarchy = {
|
||||
posts: Array<Post>;
|
||||
children: Record<string, [Post | null, Hierarchy]>;
|
||||
};
|
||||
|
||||
function createHierarchy(posts: Posts): Hierarchy {
|
||||
const hierarchy: Hierarchy = {
|
||||
posts: [],
|
||||
children: {},
|
||||
};
|
||||
|
||||
for (const post of posts) {
|
||||
const postUrl: string = post.url;
|
||||
const urlAfterBase = postUrl.split(basePath)[1] ?? "";
|
||||
|
||||
// handle / (index)
|
||||
if (urlAfterBase === "") {
|
||||
hierarchy.posts.push(post);
|
||||
continue;
|
||||
}
|
||||
|
||||
const urlSegments = urlAfterBase.split("/").slice(1);
|
||||
|
||||
// top level urls
|
||||
if (urlSegments.length === 1) {
|
||||
hierarchy.posts.push(post);
|
||||
continue;
|
||||
}
|
||||
|
||||
// folders
|
||||
const folders = urlSegments.slice(0, -1);
|
||||
|
||||
// traverse the hierarchy until the neccesary hierarchy is found
|
||||
let currentHierarchy = hierarchy;
|
||||
for (const folderName of folders) {
|
||||
// check if folder exists, create otherwise
|
||||
if (!hierarchy.children[folderName]) {
|
||||
// create if doesnt exist
|
||||
hierarchy.children[folderName] = [
|
||||
null,
|
||||
{
|
||||
posts: [],
|
||||
children: {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const [_, childrenHierarchy] = hierarchy.children[folderName]!;
|
||||
currentHierarchy = childrenHierarchy;
|
||||
}
|
||||
|
||||
// add the page
|
||||
currentHierarchy.posts.push(post);
|
||||
}
|
||||
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
function normalizeHierarchy(h: Hierarchy): Hierarchy {
|
||||
let posts = h.posts;
|
||||
for (const folderName in h.children) {
|
||||
// search if there is a post with the same name
|
||||
// as the folder
|
||||
const postIdx = h.posts.findIndex(
|
||||
(post) => splitAndLast(post.url) === folderName,
|
||||
);
|
||||
|
||||
if (postIdx !== -1) {
|
||||
const post = h.posts[postIdx]!;
|
||||
h.children[folderName]![0] = post;
|
||||
posts.splice(postIdx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// do the same to all children
|
||||
// TODO
|
||||
|
||||
return {
|
||||
children: h.children,
|
||||
posts,
|
||||
};
|
||||
}
|
||||
|
||||
const hierarchy = normalizeHierarchy(createHierarchy(posts));
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
<Navbar />
|
||||
|
||||
<div class="lg:grid lg:grid-cols-[14rem_auto_12rem] lg:container mx-auto">
|
||||
<div
|
||||
id="sidebar"
|
||||
class="pt-12 h-screen lg:sticky top-0 fixed z-10 bg-c-bg w-60 lg:w-auto border-r-2 lg:border-0
|
||||
border-c-border-1 -translate-x-64 lg:translate-x-0 transition-transform"
|
||||
>
|
||||
<nav class="py-4 pr-2 overflow-x-scroll h-[calc(100vh-3rem)]">
|
||||
<Sidebar hierarchy={hierarchy} />
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<main
|
||||
class="py-[3.5rem] lg:pl-12 lg:pr-4 markdown min-w-0 small-container mx-auto"
|
||||
>
|
||||
<slot />
|
||||
<div class="h-32"></div>
|
||||
</main>
|
||||
|
||||
<div
|
||||
class="lg:pt-12 hidden lg:block pt-4 max-h-screen overflow-x-scroll sticky top-0"
|
||||
>
|
||||
<nav class="rounded-md lg:mt-10">
|
||||
<h2 class="font-display font-medium pb-2 text-c-text-2">
|
||||
On this page
|
||||
</h2>
|
||||
|
||||
<TOC headings={headings} />
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { sidebarHighlight } from "./utils";
|
||||
// Highlight the current url of the sidebar
|
||||
document.addEventListener("DOMContentLoaded", sidebarHighlight);
|
||||
</script>
|
||||
</BaseLayout>
|
@ -15,13 +15,46 @@ export interface AstroFile {
|
||||
__usesAstroImage: boolean;
|
||||
url: string;
|
||||
file: string;
|
||||
relative_file: string;
|
||||
}
|
||||
|
||||
export interface AstroFileP {
|
||||
frontmatter: Frontmatter;
|
||||
__usesAstroImage: boolean;
|
||||
/**
|
||||
* Full URL in the filesystem's `pages` folder.
|
||||
* E.g.: `/en/latest/api/std/Array`
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Full URL in the filesystem
|
||||
* E.g.: `/home/user/project/src/pages/en/latest/api/std/Array`
|
||||
*/
|
||||
file: string;
|
||||
/**
|
||||
* Title of the page, as found in the frontmatter
|
||||
* E.g.: `Array`
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* URL of the file, relative to a `base_url`.
|
||||
* E.g.: if base_url=`/en/latest/api`, then path=`/std/Array/index.mdx`
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* Whether or not this file has a children whose name is index.mdx
|
||||
*/
|
||||
has_index: boolean;
|
||||
}
|
||||
|
||||
export type AstroFileHierarchy = AstroFileP & {
|
||||
children: Array<AstroFileHierarchy>;
|
||||
};
|
||||
|
||||
export interface Frontmatter {
|
||||
layout: string;
|
||||
title: string;
|
||||
order: number;
|
||||
mono?: boolean;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
@ -41,77 +74,108 @@ const {
|
||||
version = "latest",
|
||||
}: Props = Astro.props;
|
||||
|
||||
const base_len = base_url.length;
|
||||
|
||||
const posts_2 = posts
|
||||
const posts_2: Array<AstroFileP> = posts
|
||||
.map((post) => ({
|
||||
...post,
|
||||
title: post.frontmatter.title,
|
||||
// this should be a path relative to the base url.
|
||||
// e.g if base_url is `/spec`, then this instead of
|
||||
// being `/spec/ast/tokens` would be `/ast/tokens`
|
||||
path: post.url.substring(base_len),
|
||||
path: post.file.split(base_url)[1]!,
|
||||
has_index: false,
|
||||
}))
|
||||
.sort((p1, p2) =>
|
||||
p1.frontmatter.order > p2.frontmatter.order ? 1 : -1,
|
||||
);
|
||||
|
||||
// build a hierarchy of the files
|
||||
const second_level: Record<string, Array<AstroFile>> = {
|
||||
_: [],
|
||||
};
|
||||
for (const post of posts_2) {
|
||||
const fragments = post.path.split("/");
|
||||
if (fragments.length === 3) {
|
||||
const folder_name = fragments[1]!;
|
||||
function buildFileTree(
|
||||
flatPosts: Array<AstroFileP>,
|
||||
): Array<AstroFileHierarchy> {
|
||||
// @ts-ignore
|
||||
const root: AstroFileHierarchy = { path: "", children: [] };
|
||||
|
||||
// create if not exists
|
||||
if (second_level[folder_name] === undefined) {
|
||||
second_level[folder_name] = [];
|
||||
}
|
||||
second_level[folder_name].push(post);
|
||||
} else {
|
||||
// add to root folder
|
||||
second_level["_"]!.push(post);
|
||||
}
|
||||
flatPosts.forEach((post) => {
|
||||
// Remove leading/trailing slashes and split path
|
||||
const parts = post.path.replace(/^\/|\/$/g, "").split("/");
|
||||
let current = root;
|
||||
|
||||
// Build path segments
|
||||
parts.forEach((part, index) => {
|
||||
const path = "/" + parts.slice(0, index + 1).join("/");
|
||||
let node = current.children?.find(
|
||||
(child) => child.path === path,
|
||||
);
|
||||
|
||||
if (!node) {
|
||||
node = {
|
||||
...post,
|
||||
path,
|
||||
title:
|
||||
index === parts.length - 1
|
||||
? post.title
|
||||
: part,
|
||||
children: [],
|
||||
};
|
||||
current.children?.push(node);
|
||||
}
|
||||
current = node;
|
||||
});
|
||||
});
|
||||
|
||||
return root.children || [];
|
||||
}
|
||||
|
||||
// build a folder hierarchy (only 2 levels)
|
||||
|
||||
const entries: Array<any> = [];
|
||||
const levels_keys = Object.keys(second_level).toSorted();
|
||||
|
||||
// The key `_` contains the top level links. Always insert those
|
||||
const top_level_posts = second_level["_"]!;
|
||||
// sort
|
||||
const sorted_posts = top_level_posts.toSorted(sort_posts);
|
||||
entries.push(...sorted_posts);
|
||||
|
||||
for (const levels_key of levels_keys) {
|
||||
if (levels_key === "_") {
|
||||
// top level, already inserted
|
||||
continue;
|
||||
} else {
|
||||
const posts = second_level[levels_key]!;
|
||||
const sorted_posts = posts.toSorted(sort_posts);
|
||||
const parentEntry = {
|
||||
path: "",
|
||||
title: levels_key,
|
||||
children: sorted_posts,
|
||||
url: "",
|
||||
};
|
||||
entries.push(parentEntry);
|
||||
}
|
||||
function sortFileTree(tree: AstroFileHierarchy[]): AstroFileHierarchy[] {
|
||||
return tree
|
||||
.map((node) => ({
|
||||
...node,
|
||||
children: node.children
|
||||
? sortFileTree(node.children)
|
||||
: [],
|
||||
}))
|
||||
.sort(sort_posts);
|
||||
}
|
||||
|
||||
function sort_posts(p1, p2) {
|
||||
if (!!p1.frontmatter.order && !!p2.frontmatter.order) {
|
||||
function sort_posts(p1: AstroFileP, p2: AstroFileP) {
|
||||
if (p1.frontmatter.order !== p2.frontmatter.order) {
|
||||
return p1.frontmatter.order > p2.frontmatter.order ? 1 : -1;
|
||||
} else {
|
||||
return p1.title > p2.title ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
function markIndexPages(tree: AstroFileHierarchy[]): AstroFileHierarchy[] {
|
||||
return tree.map((node) => {
|
||||
const indexChild = node.children?.find((child) =>
|
||||
child.path.endsWith("index.mdx"),
|
||||
);
|
||||
|
||||
if (indexChild && node.children) {
|
||||
// Keep children (except index) and has_index, replace everything else
|
||||
const { children, ...indexProps } = indexChild;
|
||||
return {
|
||||
...indexProps,
|
||||
children: markIndexPages(
|
||||
node.children.filter(
|
||||
(child) =>
|
||||
!child.path.endsWith(
|
||||
"index.mdx",
|
||||
),
|
||||
),
|
||||
),
|
||||
has_index: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...node,
|
||||
children: node.children
|
||||
? markIndexPages(node.children)
|
||||
: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const posts_hierarchy = sortFileTree(markIndexPages(buildFileTree(posts_2)));
|
||||
|
||||
// FIXME: centralize somewhere else
|
||||
const versions = ["latest", "v0.0.1"].filter((x) => x !== version);
|
||||
const url_components = base_url.split(version);
|
||||
const lc = url_components[0] ?? "";
|
||||
@ -186,7 +250,7 @@ const rc = url_components[1] ?? "";
|
||||
<hr class="my-6" />
|
||||
|
||||
{
|
||||
entries.map((entry) => (
|
||||
posts_hierarchy.map((entry) => (
|
||||
<Sidebar entry={entry} />
|
||||
))
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
---
|
||||
layout: "@/layouts/ApiLayout.astro"
|
||||
layout: "../_wrapper.astro"
|
||||
title: concat
|
||||
order: 1
|
||||
mono: true
|
||||
---
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
@ -1,5 +1,8 @@
|
||||
---
|
||||
layout: "@/layouts/ApiLayout.astro"
|
||||
layout: "../_wrapper.astro"
|
||||
title: fold
|
||||
order: 1
|
||||
mono: true
|
||||
---
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
@ -1,5 +1,8 @@
|
||||
---
|
||||
layout: "@/layouts/ApiLayout.astro"
|
||||
layout: "../_wrapper.astro"
|
||||
title: Array
|
||||
order: 2
|
||||
mono: true
|
||||
---
|
||||
|
||||
import TwoColumn from "@/components/TwoColumn.astro";
|
@ -1,5 +1,8 @@
|
||||
---
|
||||
layout: "@/layouts/ApiLayout.astro"
|
||||
layout: "../_wrapper.astro"
|
||||
title: map
|
||||
order: 1
|
||||
mono: true
|
||||
---
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
24
src/pages/en/latest/api/std/_wrapper.astro
Normal file
24
src/pages/en/latest/api/std/_wrapper.astro
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
import NewDocsLayout, { type AstroFile } from "@/layouts/NewDocsLayout.astro";
|
||||
|
||||
const { frontmatter, headings } = Astro.props;
|
||||
// Get all the posts from this dir
|
||||
|
||||
const posts = (await Astro.glob(
|
||||
"./**/*.{md,mdx}",
|
||||
)) as unknown as Array<AstroFile>;
|
||||
|
||||
// The base of every URL under this glob
|
||||
const base_url = "/en/latest/api/std";
|
||||
const version = "latest";
|
||||
---
|
||||
|
||||
<NewDocsLayout
|
||||
base_url={base_url}
|
||||
frontmatter={frontmatter}
|
||||
headings={headings}
|
||||
posts={posts}
|
||||
version={version}
|
||||
>
|
||||
<slot />
|
||||
</NewDocsLayout>
|
@ -1,5 +1,7 @@
|
||||
---
|
||||
layout: "@/layouts/ApiLayout.astro"
|
||||
layout: "./_wrapper.astro"
|
||||
title: The THP stdlib
|
||||
order: 1
|
||||
---
|
||||
|
||||
import TwoColumn from "@/components/TwoColumn.astro";
|
||||
|
@ -1,5 +1,8 @@
|
||||
---
|
||||
layout: "@/layouts/ApiLayout.astro"
|
||||
layout: "./_wrapper.astro"
|
||||
title: print
|
||||
order: 2
|
||||
mono: true
|
||||
---
|
||||
|
||||
import TwoColumn from "@/components/TwoColumn.astro";
|
||||
|
Loading…
Reference in New Issue
Block a user