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