diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..f157bd1 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,9 @@ + + + + diff --git a/src/components/NavBar.astro b/src/components/NavBar.astro new file mode 100644 index 0000000..168cbb5 --- /dev/null +++ b/src/components/NavBar.astro @@ -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); + +--- + + + diff --git a/src/components/NavPage.astro b/src/components/NavPage.astro new file mode 100644 index 0000000..216a92a --- /dev/null +++ b/src/components/NavPage.astro @@ -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); + +--- + + + + \ No newline at end of file diff --git a/src/components/Renderer.astro b/src/components/Renderer.astro new file mode 100644 index 0000000..4726331 --- /dev/null +++ b/src/components/Renderer.astro @@ -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(); +--- + + +
+ +
\ No newline at end of file diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..acef35f --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/src/lib/pagePaths.test.ts b/src/lib/pagePaths.test.ts new file mode 100644 index 0000000..64a6d29 --- /dev/null +++ b/src/lib/pagePaths.test.ts @@ -0,0 +1,1384 @@ +import { it, expect, describe } from "vitest"; +import { allPageAndDirectoryPaths, relativePagePaths, type PageLinkData } from "./pagePaths"; +import type { CollectionEntry } from "astro:content"; + +describe("relativePagePaths", () => { + it("returns the current page when it does not exist in the set", () => { + // Act + const result = relativePagePaths([], "current"); + + // Assert + const currentPage: PageLinkData = { + id: "current", + data: { title: "current" } + }; + expect(result.currentPage).toMatchObject(currentPage); + }); + + it("returns the current page when it exists in the set", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current", + data: { title: "Current Page" } + }; + + // Act + const result = relativePagePaths([currentPage], "current"); + + // Assert + expect(result.currentPage).toMatchObject(currentPage); + }); + + it("returns the current page when it exists in the set and there are other pages in the set", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current", + data: { title: "Current Page" } + }; + const otherPage: PageLinkData = { + id: "notcurrent", + data: { title: "Should not be the current page" } + }; + + // Act + const result = relativePagePaths([ + otherPage, + currentPage, + otherPage, + otherPage + ], "current"); + + // Assert + expect(result.currentPage).toMatchObject(currentPage); + }); + + it("returns the current page when it does not exist in the set but there are other pages in the set", () => { + // Arrange + const otherPage: PageLinkData = { + id: "notcurrent", + data: { title: "Should not be the current page" } + }; + + // Act + const result = relativePagePaths([ + otherPage, + otherPage, + otherPage + ], "current"); + + // Assert + const currentPage: PageLinkData = { + id: "current", + data: { title: "current" } + }; + expect(result.currentPage).toMatchObject(currentPage); + }); + + it("returns pages in the same directory as siblingPages when the directory is /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "notcurrent", + data: { title: "Should not be the current page" } + }, + { + id: "alsonotcurrent", + data: { title: "Should also not be the current page" } + }, + { + id: "notcurrent2", + data: { title: "Should still not be the current page" } + } + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages + ], "current"); + + // Assert + expect(result.siblingPages).toEqual(expect.arrayContaining(otherPages)); + expect(result.siblingPages).toHaveLength(otherPages.length); + }); + + it("returns pages in the same directory as siblingPages when the directory is not /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "thisdir/current", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "thisdir/notcurrent", + data: { title: "Should not be the current page" } + }, + { + id: "thisdir/alsonotcurrent", + data: { title: "Should also not be the current page" } + }, + { + id: "thisdir/notcurrent2", + data: { title: "Should still not be the current page" } + } + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages + ], "thisdir/current"); + + // Assert + expect(result.siblingPages).toEqual(expect.arrayContaining(otherPages)); + expect(result.siblingPages).toHaveLength(otherPages.length); + }); + + it("does not return pages in different directories as siblingPages when the directory is /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "otherdir/notcurrent", + data: { title: "Should not be the current page" } + }, + { + id: "current/child", + data: { title: "Not a sibling!" } + }, + { + id: "thisdir/lowerdown/notcurrent2", + data: { title: "Should still not be the current page" } + } + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages + ], "current"); + + // Assert + expect(result.siblingPages).toEqual([]); + }); + + it("does not return pages in different directories as siblingPages when the directory is not /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "thisdir/current", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "otherdir/notcurrent", + data: { title: "Should not be the current page" } + }, + { + id: "alsonotcurrent", + data: { title: "Should also not be the current page" } + }, + { + id: "thisdir/current/child", + data: { title: "Not a sibling!" } + }, + { + id: "thisdir/lowerdown/notcurrent2", + data: { title: "Should still not be the current page" } + } + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages + ], "thisdir/current"); + + // Assert + expect(result.siblingPages).toEqual([]); + }); + + it("returns exclusively pages which are direct children when dir is /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "otherdir/notcurrent", + data: { title: "Should not be the current page" } + }, + { + id: "alsonotcurrent", + data: { title: "Should also not be the current page" } + }, + { + id: "somedir/lowerdown/notcurrent2", + data: { title: "Should still not be the current page" } + }, + { + id: "current/child2/level2", + data: { title: "A 2nd-level child!" } + }, + ]; + const childPages: PageLinkData[] = [ + { + id: "current/child", + data: { title: "A child!" } + }, + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages, + ...childPages + ], "current"); + + // Assert + expect(result.childPages).toEqual(expect.arrayContaining(childPages)); + expect(result.childPages).toHaveLength(childPages.length); + }); + + it("returns exclusively pages which are direct children when dir is not /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "thisdir/current", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "otherdir/notcurrent", + data: { title: "Should not be the current page" } + }, + { + id: "alsonotcurrent", + data: { title: "Should also not be the current page" } + }, + { + id: "thisdir/lowerdown/notcurrent2", + data: { title: "Should still not be the current page" } + }, + { + id: "thisdir/current/child2/level2", + data: { title: "A 2nd-level child!" } + }, + ]; + const childPages: PageLinkData[] = [ + { + id: "thisdir/current/child", + data: { title: "A child!" } + }, + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages, + ...childPages + ], "thisdir/current"); + + // Assert + expect(result.childPages).toEqual(expect.arrayContaining(childPages)); + expect(result.childPages).toHaveLength(childPages.length); + }); + + it("returns nonempty directories which are direct children when dir is not /", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "thisdir/current/child/level2.1", + data: { title: "level2.1" } + }, + { + id: "thisdir/current/child/level2.2", + data: { title: "level2.2" } + }, + { + id: "thisdir/current/child/level2/level3", + data: { title: "level3.1" } + }, + { + id: "thisdir/current/child2/level2/level3", + data: { title: "level3.2" } + }, + { + id: "thisdir/current/child3", + data: { title: "just a child 3 page" } + }, + { + id: "thisdir/current/child3/level2/level3", + data: { title: "level3.3" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "thisdir/current"); + + // Assert + const childDirectories: PageLinkData[] = [ + { + id: "thisdir/current/child", + data: { title: "child" } + }, + { + id: "thisdir/current/child2", + data: { title: "child2" } + }, + { + id: "thisdir/current/child3", + data: { title: "just a child 3 page" } + }, + ]; + expect(result.childDirectories).toEqual(expect.arrayContaining(childDirectories)); + expect(result.childDirectories).toHaveLength(childDirectories.length); + }); + + it("returns nonempty directories which are direct children when dir is /", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "current/child/level2.1", + data: { title: "level2.1" } + }, + { + id: "current/child/level2.2", + data: { title: "level2.2" } + }, + { + id: "current/child/level2/level3", + data: { title: "level3.1" } + }, + { + id: "current/child2/level2/level3", + data: { title: "level3.2" } + }, + { + id: "current/child3", + data: { title: "just a child 3 page" } + }, + { + id: "current/child3/level2/level3", + data: { title: "level3.3" } + }, + { + id: "sibling/dir", + data: { title: "siblingdirpage" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "current"); + + // Assert + const childDirectories: PageLinkData[] = [ + { + id: "current/child", + data: { title: "child" } + }, + { + id: "current/child2", + data: { title: "child2" } + }, + { + id: "current/child3", + data: { title: "just a child 3 page" } + }, + ]; + expect(result.childDirectories).toEqual(expect.arrayContaining(childDirectories)); + expect(result.childDirectories).toHaveLength(childDirectories.length); + }); + + it("returns sibling directories when dir is not /", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "dir/sibling/level2.1", + data: { title: "level2.1" } + }, + { + id: "dir/sibling/level2.2", + data: { title: "level2.2" } + }, + { + id: "dir/sibling/level2/level3", + data: { title: "level3.1" } + }, + { + id: "dir/sibling2/level2/level3", + data: { title: "level3.2" } + }, + { + id: "dir/sibling3", + data: { title: "just a sibling 3 page" } + }, + { + id: "dir/sibling3/level2/level3", + data: { title: "level3.3" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "dir/current"); + + // Assert + const siblingDirectories: PageLinkData[] = [ + { + id: "dir/sibling", + data: { title: "sibling" } + }, + { + id: "dir/sibling2", + data: { title: "sibling2" } + }, + { + id: "dir/sibling3", + data: { title: "just a sibling 3 page" } + }, + ]; + expect(result.siblingDirectories).toEqual(expect.arrayContaining(siblingDirectories)); + expect(result.siblingDirectories).toHaveLength(siblingDirectories.length); + }); + + it("returns sibling directories when dir is /", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "sibling/level2.1", + data: { title: "level2.1" } + }, + { + id: "sibling/level2.2", + data: { title: "level2.2" } + }, + { + id: "sibling/level2/level3", + data: { title: "level3.1" } + }, + { + id: "sibling2/level2/level3", + data: { title: "level3.2" } + }, + { + id: "sibling3", + data: { title: "just a sibling 3 page" } + }, + { + id: "sibling3/level2/level3", + data: { title: "level3.3" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "current"); + + // Assert + const siblingDirectories: PageLinkData[] = [ + { + id: "sibling", + data: { title: "sibling" } + }, + { + id: "sibling2", + data: { title: "sibling2" } + }, + { + id: "sibling3", + data: { title: "just a sibling 3 page" } + }, + ]; + expect(result.siblingDirectories).toEqual(expect.arrayContaining(siblingDirectories)); + expect(result.siblingDirectories).toHaveLength(siblingDirectories.length); + }); + + it("does not return siblingPages pages for directories on the same level", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "sibling/level2.1", + data: { title: "level2.1" } + }, + { + id: "sibling/level2.2", + data: { title: "level2.2" } + }, + { + id: "sibling/level2/level3", + data: { title: "level3.1" } + }, + { + id: "sibling2/level2/level3", + data: { title: "level3.2" } + }, + { + id: "sibling3", + data: { title: "just a sibling 3 page" } + }, + { + id: "sibling3/level2/level3", + data: { title: "level3.3" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "current"); + + // Assert + expect(result.siblingPages).toEqual([]); + }); + + it("does not return childPages pages for directories which are direct children", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "thisdir/current/child/level2.1", + data: { title: "level2.1" } + }, + { + id: "thisdir/current/child/level2.2", + data: { title: "level2.2" } + }, + { + id: "thisdir/current/child/level2/level3", + data: { title: "level3.1" } + }, + { + id: "thisdir/current/child2/level2/level3", + data: { title: "level3.2" } + }, + { + id: "thisdir/current/child3", + data: { title: "just a child 3 page" } + }, + { + id: "thisdir/current/child3/level2/level3", + data: { title: "level3.3" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "thisdir/current"); + + // Assert + expect(result.childPages).toEqual([]); + }); + + it("returns directories which are direct children but only have subdirectories", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "current/child/subdirectory/second_subdirectory/page", + data: { title: "deeply nested page" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "current"); + + // Assert + const childDirectories: PageLinkData[] = [ + { + id: "current/child", + data: { title: "child" } + }, + ]; + expect(result.childDirectories).toEqual(expect.arrayContaining(childDirectories)); + expect(result.childDirectories).toHaveLength(childDirectories.length); + }); + + it("returns directories which are siblings but only have subdirectories", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "sibling/subdirectory/second_subdirectory/page", + data: { title: "deeply nested page" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "current"); + + // Assert + const siblingDirectories: PageLinkData[] = [ + { + id: "sibling", + data: { title: "sibling" } + }, + ]; + expect(result.siblingDirectories).toEqual(expect.arrayContaining(siblingDirectories)); + expect(result.siblingDirectories).toHaveLength(siblingDirectories.length); + }); + + it("returns a generic parentDirectory if there is no page there", () => { + // Act + const result = relativePagePaths([], "some/dir/current"); + + // Assert + expect(result.parentDirectory).toEqual({ + id: "some/dir", + data: { title: "dir" } + }); + }); + + it("returns a specific parent directory if there is a page there", () => { + // Arrange + const parentDirectory: PageLinkData = { + id: "some/dir", + data: { title: "A Parent Directory Page" } + }; + + // Act + const result = relativePagePaths([parentDirectory], "some/dir/current"); + + // Assert + expect(result.parentDirectory).toEqual(parentDirectory); + }); + + it("returns parentDirectory null if we are at the top directory", () => { + // Act + const result = relativePagePaths([], "current"); + + // Assert + expect(result.parentDirectory).toEqual(null); + }); + + it("(.md extensions) returns the current page when it exists in the set", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current.md", + data: { title: "Current Page" } + }; + + // Act + const result = relativePagePaths([currentPage], "current"); + + // Assert + expect(result.currentPage).toMatchObject(currentPage); + }); + + it("(.md extensions, .md currentPage) returns the current page when it exists in the set", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current.md", + data: { title: "Current Page" } + }; + + // Act + const result = relativePagePaths([currentPage], "current.md"); + + // Assert + expect(result.currentPage).toMatchObject(currentPage); + }); + + it("(.md currentPage only) returns the current page when it exists in the set", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current", + data: { title: "Current Page" } + }; + + // Act + const result = relativePagePaths([currentPage], "current.md"); + + // Assert + expect(result.currentPage).toMatchObject(currentPage); + }); + + it("(.md extensions) returns the current page when it exists in the set and there are other pages in the set", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current.md", + data: { title: "Current Page" } + }; + const otherPage: PageLinkData = { + id: "notcurrent.md", + data: { title: "Should not be the current page" } + }; + + // Act + const result = relativePagePaths([ + otherPage, + currentPage, + otherPage, + otherPage + ], "current"); + + // Assert + expect(result.currentPage).toMatchObject(currentPage); + }); + + it("(.md extensions) returns the current page when it does not exist in the set but there are other pages in the set", () => { + // Arrange + const otherPage: PageLinkData = { + id: "notcurrent.md", + data: { title: "Should not be the current page" } + }; + + // Act + const result = relativePagePaths([ + otherPage, + otherPage, + otherPage + ], "current"); + + // Assert + const currentPage: PageLinkData = { + id: "current", + data: { title: "current" } + }; + expect(result.currentPage).toMatchObject(currentPage); + }); + + it("(.md extensions) returns pages in the same directory as siblingPages when the directory is /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current.md", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "notcurrent.md", + data: { title: "Should not be the current page" } + }, + { + id: "alsonotcurrent.md", + data: { title: "Should also not be the current page" } + }, + { + id: "notcurrent2.md", + data: { title: "Should still not be the current page" } + } + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages + ], "current"); + + // Assert + expect(result.siblingPages).toEqual(expect.arrayContaining(otherPages)); + expect(result.siblingPages).toHaveLength(otherPages.length); + }); + + it("(.md extensions) returns pages in the same directory as siblingPages when the directory is not /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "thisdir/current.md", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "thisdir/notcurrent.md", + data: { title: "Should not be the current page" } + }, + { + id: "thisdir/alsonotcurrent.md", + data: { title: "Should also not be the current page" } + }, + { + id: "thisdir/notcurrent2.md", + data: { title: "Should still not be the current page" } + } + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages + ], "thisdir/current"); + + // Assert + expect(result.siblingPages).toEqual(expect.arrayContaining(otherPages)); + expect(result.siblingPages).toHaveLength(otherPages.length); + }); + + it("(.md extensions) does not return pages in different directories as siblingPages when the directory is /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current.md", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "otherdir/notcurrent.md", + data: { title: "Should not be the current page" } + }, + { + id: "current/child.md", + data: { title: "Not a sibling!" } + }, + { + id: "thisdir/lowerdown/notcurrent2.md", + data: { title: "Should still not be the current page" } + } + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages + ], "current"); + + // Assert + expect(result.siblingPages).toEqual([]); + }); + + it("(.md extensions) does not return pages in different directories as siblingPages when the directory is not /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "thisdir/current.md", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "otherdir/notcurrent.md", + data: { title: "Should not be the current page" } + }, + { + id: "alsonotcurrent.md", + data: { title: "Should also not be the current page" } + }, + { + id: "thisdir/current/child.md", + data: { title: "Not a sibling!" } + }, + { + id: "thisdir/lowerdown/notcurrent2.md", + data: { title: "Should still not be the current page" } + } + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages + ], "thisdir/current"); + + // Assert + expect(result.siblingPages).toEqual([]); + }); + + it("(.md extensions) returns exclusively pages which are direct children when dir is /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "current.md", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "otherdir/notcurrent.md", + data: { title: "Should not be the current page" } + }, + { + id: "alsonotcurrent.md", + data: { title: "Should also not be the current page" } + }, + { + id: "somedir/lowerdown/notcurrent2.md", + data: { title: "Should still not be the current page" } + }, + { + id: "current/child2/level2.md", + data: { title: "A 2nd-level child!" } + }, + ]; + const childPages: PageLinkData[] = [ + { + id: "current/child.md", + data: { title: "A child!" } + }, + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages, + ...childPages + ], "current"); + + // Assert + expect(result.childPages).toEqual(expect.arrayContaining(childPages)); + expect(result.childPages).toHaveLength(childPages.length); + }); + + it("(.md extensions) returns exclusively pages which are direct children when dir is not /", () => { + // Arrange + const currentPage: PageLinkData = { + id: "thisdir/current.md", + data: { title: "Current Page" } + }; + const otherPages: PageLinkData[] = [ + { + id: "otherdir/notcurrent.md", + data: { title: "Should not be the current page" } + }, + { + id: "alsonotcurrent.md", + data: { title: "Should also not be the current page" } + }, + { + id: "thisdir/lowerdown/notcurrent2.md", + data: { title: "Should still not be the current page" } + }, + { + id: "thisdir/current/child2/level2.md", + data: { title: "A 2nd-level child!" } + }, + ]; + const childPages: PageLinkData[] = [ + { + id: "thisdir/current/child.md", + data: { title: "A child!" } + }, + ]; + + // Act + const result = relativePagePaths([ + currentPage, + ...otherPages, + ...childPages + ], "thisdir/current"); + + // Assert + expect(result.childPages).toEqual(expect.arrayContaining(childPages)); + expect(result.childPages).toHaveLength(childPages.length); + }); + + it("(.md extensions) returns nonempty directories which are direct children when dir is not /", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "thisdir/current/child/level2.1.md", + data: { title: "level2.1" } + }, + { + id: "thisdir/current/child/level2.2.md", + data: { title: "level2.2" } + }, + { + id: "thisdir/current/child/level2/level3.md", + data: { title: "level3.1" } + }, + { + id: "thisdir/current/child2/level2/level3.md", + data: { title: "level3.2" } + }, + { + id: "thisdir/current/child3.md", + data: { title: "just a child 3 page" } + }, + { + id: "thisdir/current/child3/level2/level3.md", + data: { title: "level3.3" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "thisdir/current"); + + // Assert + const childDirectories: PageLinkData[] = [ + { + id: "thisdir/current/child", + data: { title: "child" } + }, + { + id: "thisdir/current/child2", + data: { title: "child2" } + }, + { + id: "thisdir/current/child3.md", + data: { title: "just a child 3 page" } + }, + ]; + expect(result.childDirectories).toEqual(expect.arrayContaining(childDirectories)); + expect(result.childDirectories).toHaveLength(childDirectories.length); + }); + + it("(.md extensions) returns nonempty directories which are direct children when dir is /", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "current/child/level2.1.md", + data: { title: "level2.1" } + }, + { + id: "current/child/level2.2.md", + data: { title: "level2.2" } + }, + { + id: "current/child/level2/level3.md", + data: { title: "level3.1" } + }, + { + id: "current/child2/level2/level3.md", + data: { title: "level3.2" } + }, + { + id: "current/child3.md", + data: { title: "just a child 3 page" } + }, + { + id: "current/child3/level2/level3.md", + data: { title: "level3.3" } + }, + { + id: "sibling/dir.md", + data: { title: "siblingdirpage" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "current"); + + // Assert + const childDirectories: PageLinkData[] = [ + { + id: "current/child", + data: { title: "child" } + }, + { + id: "current/child2", + data: { title: "child2" } + }, + { + id: "current/child3.md", + data: { title: "just a child 3 page" } + }, + ]; + expect(result.childDirectories).toEqual(expect.arrayContaining(childDirectories)); + expect(result.childDirectories).toHaveLength(childDirectories.length); + }); + + it("(.md extensions) returns sibling directories when dir is not /", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "dir/sibling/level2.1.md", + data: { title: "level2.1" } + }, + { + id: "dir/sibling/level2.2.md", + data: { title: "level2.2" } + }, + { + id: "dir/sibling/level2/level3.md", + data: { title: "level3.1" } + }, + { + id: "dir/sibling2/level2/level3.md", + data: { title: "level3.2" } + }, + { + id: "dir/sibling3.md", + data: { title: "just a sibling 3 page" } + }, + { + id: "dir/sibling3/level2/level3.md", + data: { title: "level3.3" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "dir/current"); + + // Assert + const siblingDirectories: PageLinkData[] = [ + { + id: "dir/sibling", + data: { title: "sibling" } + }, + { + id: "dir/sibling2", + data: { title: "sibling2" } + }, + { + id: "dir/sibling3.md", + data: { title: "just a sibling 3 page" } + }, + ]; + expect(result.siblingDirectories).toEqual(expect.arrayContaining(siblingDirectories)); + expect(result.siblingDirectories).toHaveLength(siblingDirectories.length); + }); + + it("(.md extensions) returns sibling directories when dir is /", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "sibling/level2.1.md", + data: { title: "level2.1" } + }, + { + id: "sibling/level2.2.md", + data: { title: "level2.2" } + }, + { + id: "sibling/level2/level3.md", + data: { title: "level3.1" } + }, + { + id: "sibling2/level2/level3.md", + data: { title: "level3.2" } + }, + { + id: "sibling3.md", + data: { title: "just a sibling 3 page" } + }, + { + id: "sibling3/level2/level3.md", + data: { title: "level3.3" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "current"); + + // Assert + const siblingDirectories: PageLinkData[] = [ + { + id: "sibling", + data: { title: "sibling" } + }, + { + id: "sibling2", + data: { title: "sibling2" } + }, + { + id: "sibling3.md", + data: { title: "just a sibling 3 page" } + }, + ]; + expect(result.siblingDirectories).toEqual(expect.arrayContaining(siblingDirectories)); + expect(result.siblingDirectories).toHaveLength(siblingDirectories.length); + }); + + it("(.md extensions) does not return siblingPages pages for directories on the same level", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "sibling/level2.1.md", + data: { title: "level2.1" } + }, + { + id: "sibling/level2.2.md", + data: { title: "level2.2" } + }, + { + id: "sibling/level2/level3.md", + data: { title: "level3.1" } + }, + { + id: "sibling2/level2/level3.md", + data: { title: "level3.2" } + }, + { + id: "sibling3.md", + data: { title: "just a sibling 3 page" } + }, + { + id: "sibling3/level2/level3.md", + data: { title: "level3.3" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "current"); + + // Assert + expect(result.siblingPages).toEqual([]); + }); + + it("(.md extensions) does not return childPages pages for directories which are direct children", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "thisdir/current/child/level2.1.md", + data: { title: "level2.1" } + }, + { + id: "thisdir/current/child/level2.2.md", + data: { title: "level2.2" } + }, + { + id: "thisdir/current/child/level2/level3.md", + data: { title: "level3.1" } + }, + { + id: "thisdir/current/child2/level2/level3.md", + data: { title: "level3.2" } + }, + { + id: "thisdir/current/child3.md", + data: { title: "just a child 3 page" } + }, + { + id: "thisdir/current/child3/level2/level3.md", + data: { title: "level3.3" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "thisdir/current"); + + // Assert + expect(result.childPages).toEqual([]); + }); + + it("(.md extensions) returns directories which are direct children but only have subdirectories", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "current/child/subdirectory/second_subdirectory/page.md", + data: { title: "deeply nested page" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "current"); + + // Assert + const childDirectories: PageLinkData[] = [ + { + id: "current/child", + data: { title: "child" } + }, + ]; + expect(result.childDirectories).toEqual(expect.arrayContaining(childDirectories)); + expect(result.childDirectories).toHaveLength(childDirectories.length); + }); + + it("(.md extensions) returns directories which are siblings but only have subdirectories", () => { + // Arrange + const pages: PageLinkData[] = [ + { + id: "sibling/subdirectory/second_subdirectory/page.md", + data: { title: "deeply nested page" } + }, + ]; + + // Act + const result = relativePagePaths(pages, "current"); + + // Assert + const siblingDirectories: PageLinkData[] = [ + { + id: "sibling", + data: { title: "sibling" } + }, + ]; + expect(result.siblingDirectories).toEqual(expect.arrayContaining(siblingDirectories)); + expect(result.siblingDirectories).toHaveLength(siblingDirectories.length); + }); + + it("(.md extensions) returns a generic parentDirectory if there is no page there", () => { + // Act + const result = relativePagePaths([], "some/dir/current.md"); + + // Assert + expect(result.parentDirectory).toEqual({ + id: "some/dir", + data: { title: "dir" } + }); + }); + + it("(.md extensions) returns a specific parent directory if there is a page there", () => { + // Arrange + const parentDirectory: PageLinkData = { + id: "some/dir", + data: { title: "A Parent Directory Page.md" } + }; + + // Act + const result = relativePagePaths([parentDirectory], "some/dir/current"); + + // Assert + expect(result.parentDirectory).toEqual(parentDirectory); + }); +}); + +describe("allPageAndDirectoryPaths", () => { + it("returns a page when it exists", () => { + // Arrange + const pageObject = { + ["current.md"]: { + id: "current.md" + } + }; + const nonDirectoryPages = Object.values(pageObject).filter(page => page != null); + const pages = Array.from(nonDirectoryPages) as unknown as CollectionEntry<"wiki">[]; + + // Act + const result = allPageAndDirectoryPaths(pages); + + // Assert + expect(Object.fromEntries(result)).toMatchObject(pageObject); + }); + it("returns parent directories when they exist", () => { + // Arrange + const pageObject = { + ["directory/current.md"]: { + id: "directory/current.md" + }, + ["directory"]: null, + }; + const nonDirectoryPages = Object.values(pageObject).filter(page => page != null); + const pages = Array.from(nonDirectoryPages) as unknown as CollectionEntry<"wiki">[]; + + // Act + const result = allPageAndDirectoryPaths(pages); + + // Assert + expect(Object.fromEntries(result)).toMatchObject(pageObject); + }); + it("returns only a single directory instance when there are multiple pages", () => { + // Arrange + const pageObject = { + ["directory/current.md"]: { + id: "directory/current.md" + }, + ["directory/current2.md"]: { + id: "directory/current2.md" + }, + ["directory"]: null, + }; + const nonDirectoryPages = Object.values(pageObject).filter(page => page != null); + const pages = Array.from(nonDirectoryPages) as unknown as CollectionEntry<"wiki">[]; + + // Act + const result = allPageAndDirectoryPaths(pages); + + // Assert + expect(Object.fromEntries(result)).toMatchObject(pageObject); + }); + it("returns nested subdirectories", () => { + // Arrange + const pageObject = { + ["directory/subdirectory/subsubdirectory/current.md"]: { + id: "directory/subdirectory/subsubdirectory/current.md" + }, + ["directory/current2.md"]: { + id: "directory/current2.md" + }, + ["directory"]: null, + ["directory/subdirectory"]: null, + ["directory/subdirectory/subsubdirectory"]: null, + }; + const nonDirectoryPages = Object.values(pageObject).filter(page => page != null); + const pages = Array.from(nonDirectoryPages) as unknown as CollectionEntry<"wiki">[]; + + // Act + const result = allPageAndDirectoryPaths(pages); + + // Assert + expect(Object.fromEntries(result)).toMatchObject(pageObject); + }); + +}); \ No newline at end of file diff --git a/src/lib/pagePaths.ts b/src/lib/pagePaths.ts new file mode 100644 index 0000000..8f625f4 --- /dev/null +++ b/src/lib/pagePaths.ts @@ -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 | 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 = new Map(); + const childPages: Map = new Map(); + + const currentPathParsed = parse(currentPath); + const currentPathExtensionless = join(currentPathParsed.dir, currentPathParsed.name); + + const childDirectoryPaths: Set = new Set(); + const siblingDirectoryPaths: Set = 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 | 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; +} \ No newline at end of file diff --git a/src/pages/[...slug].astro b/src/pages/[...slug].astro new file mode 100644 index 0000000..e809fba --- /dev/null +++ b/src/pages/[...slug].astro @@ -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; +--- + + + + Aux Docs - {name} + + + + + { + post ? ( + + ) : ( + + ) + } + \ No newline at end of file diff --git a/src/reset.d.ts b/src/reset.d.ts new file mode 100644 index 0000000..e186b1f --- /dev/null +++ b/src/reset.d.ts @@ -0,0 +1 @@ +import "@total-typescript/ts-reset"; \ No newline at end of file diff --git a/src/style/content.css b/src/style/content.css new file mode 100644 index 0000000..1df682b --- /dev/null +++ b/src/style/content.css @@ -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; +} \ No newline at end of file diff --git a/src/style/globals.css b/src/style/globals.css new file mode 100644 index 0000000..1e13bfc --- /dev/null +++ b/src/style/globals.css @@ -0,0 +1,7 @@ +a:visited { + color: blue; +} + +.contents { + width: 100%; +}