feat: Initial code commit
Add project code, including a basic markdown browser and some utility functions with associated tests
This commit is contained in:
parent
26b25a6cb6
commit
9e8658caea
9
public/favicon.svg
Normal file
9
public/favicon.svg
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||||
|
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||||
|
<style>
|
||||||
|
path { fill: #000; }
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
path { fill: #FFF; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 749 B |
124
src/components/NavBar.astro
Normal file
124
src/components/NavBar.astro
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
---
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
import { relativePagePaths } from "../lib/pagePaths";
|
||||||
|
|
||||||
|
type Props = { currentPage: string };
|
||||||
|
|
||||||
|
const { currentPage } = Astro.props;
|
||||||
|
|
||||||
|
const wikiPages = await getCollection("wiki");
|
||||||
|
const pages = relativePagePaths(wikiPages, currentPage);
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.navbar {
|
||||||
|
/* background: var(--bg-navbar); */
|
||||||
|
background: #ccc;
|
||||||
|
padding: .1em 0em;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.navbar > ul {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.navbar > ul > li {
|
||||||
|
/* background: var(--bg-navbar-item); */
|
||||||
|
list-style: none;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.navbar > ul > li:hover {
|
||||||
|
/* background: var(--bg-navbar-accent); */
|
||||||
|
background: aquamarine;
|
||||||
|
}
|
||||||
|
.active {
|
||||||
|
/* background: var(--bg-navbar-active); */
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
.next {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
.navbar > ul > li > a {
|
||||||
|
padding: .25em .5em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.separator {
|
||||||
|
height: .125em;
|
||||||
|
/* background: var(--bg-navbar-line); */
|
||||||
|
background: #f00;
|
||||||
|
margin: .125em 0em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<nav class="navbar">
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
pages.parentDirectory ?
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href={"/" + pages.parentDirectory.id}
|
||||||
|
>
|
||||||
|
{pages.parentDirectory.data.title.replaceAll("-", " ")}/
|
||||||
|
</a>
|
||||||
|
</li><div class="separator" /> : null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
pages.siblingDirectories.map(page =>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href={"/" + page.id}
|
||||||
|
>
|
||||||
|
{page.data.title.replaceAll("-", " ")}/
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
pages.siblingPages.map(page =>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href={"/" + page.id}
|
||||||
|
>
|
||||||
|
{page.data.title}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
pages.siblingDirectories.length || pages.siblingPages.length ?
|
||||||
|
<div class="separator" />
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
<li>
|
||||||
|
<a class="active"
|
||||||
|
href={"/" + pages.currentPage.id}
|
||||||
|
>
|
||||||
|
{pages.childPages.length || pages.childDirectories.length ? `${pages.currentPage.data.title.replaceAll("-", " ")}/` : pages.currentPage.data.title}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
pages.childDirectories.map(page =>
|
||||||
|
<li>
|
||||||
|
<a class="next"
|
||||||
|
href={"/" + page.id}
|
||||||
|
>
|
||||||
|
{page.data.title.replaceAll("-", " ")}/
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
pages.childPages.map(page =>
|
||||||
|
<li>
|
||||||
|
<a class="next"
|
||||||
|
href={"/" + page.id}
|
||||||
|
>
|
||||||
|
{page.data.title}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
81
src/components/NavPage.astro
Normal file
81
src/components/NavPage.astro
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
import { relativePagePaths } from "../lib/pagePaths";
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
|
||||||
|
type Props = { path: string }
|
||||||
|
|
||||||
|
const { path } = Astro.props;
|
||||||
|
|
||||||
|
const wikiPages = await getCollection("wiki");
|
||||||
|
const pages = relativePagePaths(wikiPages, path);
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.navpage {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
.navpage > ul {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.navpage > ul > li {
|
||||||
|
/* background: var(--bg-navpage-item); */
|
||||||
|
list-style: none;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.navpage > ul > li:hover {
|
||||||
|
/* background: var(--bg-navpage-accent); */
|
||||||
|
background: aquamarine;
|
||||||
|
}
|
||||||
|
.active {
|
||||||
|
/* background: var(--bg-navpage-active); */
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
.next {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
.navpage > ul > li > a {
|
||||||
|
padding: .25em .5em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.separator {
|
||||||
|
height: .125em;
|
||||||
|
/* background: var(--bg-navpage-line); */
|
||||||
|
background: #f00;
|
||||||
|
margin: .125em 0em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<nav class="navpage">
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
pages.childDirectories.map(page =>
|
||||||
|
<li>
|
||||||
|
<a class="next"
|
||||||
|
href={"/" + page.id}
|
||||||
|
>
|
||||||
|
{page.data.title.replaceAll("-", " ")}/
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
pages.childPages.map(page =>
|
||||||
|
<li>
|
||||||
|
<a class="next"
|
||||||
|
href={"/" + page.id}
|
||||||
|
>
|
||||||
|
{page.data.title}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
16
src/components/Renderer.astro
Normal file
16
src/components/Renderer.astro
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
import "../style/content.css";
|
||||||
|
|
||||||
|
import { type CollectionEntry } from 'astro:content';
|
||||||
|
|
||||||
|
type Props = { post: CollectionEntry<'wiki'> };
|
||||||
|
|
||||||
|
const { post } = Astro.props;
|
||||||
|
|
||||||
|
const { Content } = await post.render();
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
<div class="contents">
|
||||||
|
<Content />
|
||||||
|
</div>
|
2
src/env.d.ts
vendored
Normal file
2
src/env.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
|
/// <reference types="astro/client" />
|
1384
src/lib/pagePaths.test.ts
Normal file
1384
src/lib/pagePaths.test.ts
Normal file
File diff suppressed because it is too large
Load diff
163
src/lib/pagePaths.ts
Normal file
163
src/lib/pagePaths.ts
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
import { parse, join, sep } from "node:path";
|
||||||
|
|
||||||
|
export interface PageLinkData {
|
||||||
|
id: string;
|
||||||
|
data: { title: string; };
|
||||||
|
}
|
||||||
|
|
||||||
|
type AllPathInformation = Map<string, CollectionEntry<"wiki"> | null>;
|
||||||
|
|
||||||
|
export interface Paths {
|
||||||
|
siblingPages: PageLinkData[];
|
||||||
|
siblingDirectories: PageLinkData[];
|
||||||
|
childPages: PageLinkData[];
|
||||||
|
childDirectories: PageLinkData[];
|
||||||
|
|
||||||
|
parentDirectory: PageLinkData | null;
|
||||||
|
currentPage: PageLinkData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function relativePagePaths(wikiEntries: PageLinkData[], currentPath: string): Paths {
|
||||||
|
let currentPage: PageLinkData | undefined;
|
||||||
|
let parentDirectory: PageLinkData | undefined | null;
|
||||||
|
const siblingPages: Map<string, PageLinkData> = new Map();
|
||||||
|
const childPages: Map<string, PageLinkData> = new Map();
|
||||||
|
|
||||||
|
const currentPathParsed = parse(currentPath);
|
||||||
|
const currentPathExtensionless = join(currentPathParsed.dir, currentPathParsed.name);
|
||||||
|
|
||||||
|
const childDirectoryPaths: Set<string> = new Set();
|
||||||
|
const siblingDirectoryPaths: Set<string> = new Set();
|
||||||
|
|
||||||
|
for (const entry of wikiEntries) {
|
||||||
|
const pagePathParsed = parse(entry.id);
|
||||||
|
const pagePathExtensionless = join(pagePathParsed.dir, pagePathParsed.name);
|
||||||
|
|
||||||
|
if (pagePathExtensionless === currentPathExtensionless) {
|
||||||
|
currentPage = entry
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isInCurrentDirectory = pagePathParsed.dir === currentPathParsed.dir;
|
||||||
|
if (isInCurrentDirectory) {
|
||||||
|
siblingPages.set(pagePathExtensionless, entry);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDirectChild = pagePathParsed.dir === currentPathExtensionless;
|
||||||
|
if (isDirectChild) {
|
||||||
|
childPages.set(pagePathExtensionless, entry);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isIndirectChild = pagePathParsed.dir.startsWith(currentPathExtensionless + sep);
|
||||||
|
if (isIndirectChild) {
|
||||||
|
const nextPathSeparator = pagePathParsed.dir.indexOf(sep, currentPathExtensionless.length + 1);
|
||||||
|
|
||||||
|
if (nextPathSeparator === -1) {
|
||||||
|
childDirectoryPaths.add(pagePathParsed.dir);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
childDirectoryPaths.add(pagePathParsed.dir.slice(0, nextPathSeparator));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isIndirectInCurrentDirectory = currentPathParsed.dir === "" || pagePathParsed.dir.startsWith(currentPathParsed.dir + sep);
|
||||||
|
if (isIndirectInCurrentDirectory) {
|
||||||
|
const nextPathSeparator = pagePathParsed.dir.indexOf(sep, currentPathParsed.dir.length + 1);
|
||||||
|
|
||||||
|
if (nextPathSeparator === -1) {
|
||||||
|
siblingDirectoryPaths.add(pagePathParsed.dir);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
siblingDirectoryPaths.add(pagePathParsed.dir.slice(0, nextPathSeparator));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isParentDirectory = pagePathExtensionless === currentPathParsed.dir;
|
||||||
|
if (isParentDirectory) {
|
||||||
|
parentDirectory = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const childDirectories: PageLinkData[] = [];
|
||||||
|
for (const childDirectoryPath of childDirectoryPaths.values()) {
|
||||||
|
const childDirectoryPage = childPages.get(childDirectoryPath);
|
||||||
|
if (childDirectoryPage) {
|
||||||
|
childDirectories.push(childDirectoryPage);
|
||||||
|
childPages.delete(childDirectoryPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const childDirectoryPathParsed = parse(childDirectoryPath);
|
||||||
|
childDirectories.push({
|
||||||
|
id: childDirectoryPath,
|
||||||
|
data: { title: childDirectoryPathParsed.name }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const siblingDirectories: PageLinkData[] = [];
|
||||||
|
for (const siblingDirectoryPath of siblingDirectoryPaths.values()) {
|
||||||
|
const siblingDirectoryPage = siblingPages.get(siblingDirectoryPath);
|
||||||
|
if (siblingDirectoryPage) {
|
||||||
|
siblingDirectories.push(siblingDirectoryPage);
|
||||||
|
siblingPages.delete(siblingDirectoryPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const siblingDirectoryPathParsed = parse(siblingDirectoryPath);
|
||||||
|
siblingDirectories.push({
|
||||||
|
id: siblingDirectoryPath,
|
||||||
|
data: { title: siblingDirectoryPathParsed.name }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage === undefined) {
|
||||||
|
currentPage = {
|
||||||
|
id: currentPath,
|
||||||
|
data: { title: currentPathParsed.name }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentDirectory === undefined) {
|
||||||
|
if (currentPathParsed.dir) {
|
||||||
|
const parentDirectoryPathParsed = parse(currentPathParsed.dir);
|
||||||
|
parentDirectory = {
|
||||||
|
id: currentPathParsed.dir,
|
||||||
|
data: { title: parentDirectoryPathParsed.name }
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
parentDirectory = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
siblingPages: Array.from(siblingPages.values()),
|
||||||
|
childPages: Array.from(childPages.values()),
|
||||||
|
|
||||||
|
siblingDirectories,
|
||||||
|
childDirectories,
|
||||||
|
|
||||||
|
currentPage,
|
||||||
|
parentDirectory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function allPageAndDirectoryPaths(wikiEntries: CollectionEntry<"wiki">[]): AllPathInformation {
|
||||||
|
const pathInformation: Map<string, CollectionEntry<"wiki"> | null> = new Map();
|
||||||
|
|
||||||
|
for (const entry of wikiEntries) {
|
||||||
|
pathInformation.set(entry.id, entry);
|
||||||
|
|
||||||
|
let parsedEntryPath = parse(entry.id);
|
||||||
|
while (parsedEntryPath.dir) {
|
||||||
|
pathInformation.set(parsedEntryPath.dir, null);
|
||||||
|
parsedEntryPath = parse(parsedEntryPath.dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathInformation;
|
||||||
|
}
|
97
src/pages/[...slug].astro
Normal file
97
src/pages/[...slug].astro
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
---
|
||||||
|
import { type CollectionEntry, getCollection, getEntry } from 'astro:content';
|
||||||
|
import NavBar from '../components/NavBar.astro';
|
||||||
|
import Renderer from '../components/Renderer.astro';
|
||||||
|
import NavPage from '../components/NavPage.astro';
|
||||||
|
import "../style/globals.css"
|
||||||
|
import { allPageAndDirectoryPaths } from "../lib/pagePaths";
|
||||||
|
import { parse } from "node:path"
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const wikiPages = await getCollection('wiki');
|
||||||
|
const paths = allPageAndDirectoryPaths(wikiPages);
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
params: { slug: undefined },
|
||||||
|
props: { path: "home", name: "Home", post: await getEntry("wiki", "home") }
|
||||||
|
},
|
||||||
|
...Array.from(paths.entries()).flatMap(([key, post]) => {
|
||||||
|
if (!post) {
|
||||||
|
return [{
|
||||||
|
params: { slug: key },
|
||||||
|
props: { path: key, name: parse(key).name }
|
||||||
|
}]
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
params: { slug: post.slug },
|
||||||
|
props: { path: post.id, name: post.data.title, post },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: { slug: post.slug + ".md" },
|
||||||
|
props: { path: post.id, name: post.data.title, post },
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// ...pages.flatMap((post: CollectionEntry<'wiki'> | undefined) => {
|
||||||
|
// if (!post) {
|
||||||
|
// return [{
|
||||||
|
// params: { slug: }
|
||||||
|
// }]
|
||||||
|
// } else {
|
||||||
|
// return [
|
||||||
|
// {
|
||||||
|
// params: { slug: post.slug },
|
||||||
|
// props: { path: post.id, name: post.data.title, post },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// params: { slug: post.slug + ".md" },
|
||||||
|
// props: { path: post.id, name: post.data.title, post },
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
post?: CollectionEntry<'wiki'>
|
||||||
|
path: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { post, path, name } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Aux Docs - {name}</title>
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
.box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 0;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.nav-pane {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 16em;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body class="box">
|
||||||
|
<div class="nav-pane">
|
||||||
|
<NavBar currentPage={path} />
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
post ? (
|
||||||
|
<Renderer post={post} />
|
||||||
|
) : (
|
||||||
|
<NavPage path={path} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</body>
|
1
src/reset.d.ts
vendored
Normal file
1
src/reset.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
import "@total-typescript/ts-reset";
|
15
src/style/content.css
Normal file
15
src/style/content.css
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
.contents table {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contents td {
|
||||||
|
padding: 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contents {
|
||||||
|
overflow: scroll;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 0em 2em;
|
||||||
|
}
|
7
src/style/globals.css
Normal file
7
src/style/globals.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
a:visited {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contents {
|
||||||
|
width: 100%;
|
||||||
|
}
|
Loading…
Reference in a new issue