refactor: restructure folder layout

This commit is contained in:
afn
2022-12-25 01:46:45 -05:00
parent 6ea64ef873
commit c60a0a7731
39 changed files with 578 additions and 410 deletions

View File

@@ -1,6 +1,6 @@
import type { PageServerLoad } from './$types';
import { index_content } from '$lib/documentation.server';
import { index_content } from './documentation.server';
// The load function here used to get data from a json file created by a (prerendered) server route.
// This was because we could not prerender the documentation route before.

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import type { PageData } from './$types';
import DocsNavTree from '$lib/components/molecules/DocsNavTree.svelte';
import DocsNavTree from './DocsNavTree.svelte';
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import Footer from '$lib/components/molecules/Footer.svelte';
import Footer from '$layout/Footer.svelte';
export let data: PageData;
</script>

View File

@@ -0,0 +1,56 @@
<script lang="ts">
import type { DocumentInfo } from './documentation.shared';
export let info: DocumentInfo;
import { page } from '$app/stores';
</script>
<!-- Always part of a list -->
<li>
<a href="/docs/{info.slug}">
<div class="doc-section item" class:selected={$page.url.pathname === `/docs/${info.slug}`}>
<h5>{info.title}</h5>
</div>
</a>
</li>
<style>
a {
text-decoration: none;
}
li {
list-style: none;
}
.item {
padding: 0.6rem 1rem;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.6rem;
width: 100%;
user-select: none;
transition: background-color 0.4s var(--bezier-one);
}
.item h5 {
color: var(--grey-five);
transition: color 0.3s var(--bezier-one);
}
.selected h5 {
color: var(--grey-four);
transition: color 0.3s var(--bezier-one);
}
.selected {
background-color: var(--accent-color);
}
.item:hover:not(.selected) {
background-color: var(--grey-six);
}
.item:not(.selected):hover h5 {
color: var(--white);
}
</style>

View File

@@ -0,0 +1,37 @@
<script lang="ts">
import { is_tree } from './documentation.shared';
import type { DocsTree } from './documentation.shared';
import DocsNavNode from './DocsNavNode.svelte';
export let tree: DocsTree;
// How deeply nested this is.
export let nested = 0;
</script>
{#if nested}
<!-- The index should be part of the `ul` above us. -->
<DocsNavNode info={tree.index} />
{/if}
<ul>
{#if !nested}
<!-- There is no `ul` above us, so index should go here instead. -->
<DocsNavNode info={tree.index} />
{/if}
{#each tree.nodes as node}
{#if is_tree(node)}
<!-- Recursion here is fine. We are not dealing with a tree the size of a linux root file system. -->
<svelte:self tree={node} nested={nested + 1} />
{:else}
<DocsNavNode info={node} />
{/if}
{/each}
</ul>
<style>
ul {
padding-left: 1rem;
}
</style>

View File

@@ -1,17 +1,17 @@
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';
import { get } from '$lib/documentation.server';
import { get } from '../documentation.server';
// See also: ../+layout.server.ts
export const prerender = true;
export const load: PageServerLoad = ({ params }) => {
const document = get(params.slug);
if (document === null) {
error
throw error(404);
}
const document = get(params.slug);
if (document === null) {
error;
throw error(404);
}
return document;
}
return document;
};

View File

@@ -1,12 +1,11 @@
<script lang="ts">
import type { PageData } from './$types';
import type { PageData } from './$types';
import '$lib/documentation.scss';
import '../documentation.scss';
// Data here comes from a trusted source.
// CSS comes from the layout.
export let data: PageData;
// Data here comes from a trusted source.
// CSS comes from the layout.
export let data: PageData;
</script>
<svelte:head>
@@ -16,6 +15,6 @@
</svelte:head>
<div id="markup-content">
<h1 class="title">{data.title}</h1>
{@html data.content}
<h1 class="title">{data.title}</h1>
{@html data.content}
</div>

View File

@@ -0,0 +1,95 @@
#markup-content {
/* Defaults for text */
color: var(--accent-color-two);
font-weight: 300;
font-size: 1rem;
line-height: 1.75rem;
letter-spacing: 0.03rem !important;
a {
text-decoration: none;
color: var(--accent-color);
border-bottom: 1.5px solid var(--accent-low-opacity);
padding: 0px ;
}
code {
background-color: var(--grey-one);
border-radius: 8px;
padding: 0.2rem 0.5rem;
font-size: 0.8rem;
font-family: var(--mono-font);
font-weight: 300;
flex-wrap: wrap;
line-height: 1.25rem;
}
pre code {
font-size: 0.75rem;
background-color: var(--grey-six);
white-space: pre;
display: block;
flex-wrap: wrap;
padding: 0.5rem 1rem;
margin: 1rem 0;
}
h5 {
margin-bottom: 0.5rem;
}
h5 {
color: var(--accent-color);
}
h1, h2, h3, h4, h5 {
margin-bottom: 1rem;
}
h1 {
font-size: 2.25rem;
font-weight: 600;
letter-spacing: -0.02rem;
color: var(--accent-color-two);
border-bottom: 1px solid var(--grey-three);
padding-bottom: 1rem;
margin-bottom: 1rem;
}
h2 {
font-size: 1.5rem;
letter-spacing: -0.02rem;
font-weight: 600;
color: var(--accent-color-two);
border-bottom: 1px solid var(--grey-three);
padding-bottom: 1rem;
margin-top: 2rem;
}
h3 {
margin-top: 1rem;
margin-bottom: 0.5rem;
}
li {
margin-left: 1rem;
margin-bottom: 0.5rem;
}
/* Markup processors output this for bold text, but css spec is goofy aah */
strong {
font-weight: bold;
letter-spacing: 0.01rem;
}
ul {
padding-left: 2rem;
}
}

View File

@@ -0,0 +1,209 @@
import { is_tree } from './documentation.shared';
import type { Document, DocsTree, DocsTreeNode, DocumentInfo } from './documentation.shared';
import { browser, prerendering } from '$app/environment';
import fs, { existsSync as exists } from 'fs';
import path from 'path';
import { parse as parse_md } from 'marked';
import AsciiDocProcessor from 'asciidoctor'
// This file does not work in a browser.
if (browser) {
throw Error('SvelteKit has skill issues');
}
/// Constants
const supported_formats: Map<string, (markup: string) => Document> = new Map();
supported_formats.set("md", markup => {
let lines = markup.split('\n');
// Get and remove the first line.
const first_line = lines.splice(0, 1)[0];
// Remove `# `.
const title = first_line.substring(2);
// Convert the rest to html
const content = parse_md(lines.join('\n'));
return { title, content };
});
const asciidoctor = AsciiDocProcessor();
const adoc_fn = markup => {
// Get first line.
const first_line = markup.split('\n')[0];
// Remove `= `.
const title = first_line.substring(2);
// Convert it to html. Unlike markdown, we do not need to remove the first title heading.
// NOTE: Maybe consider change the safe mode value.
const content = asciidoctor.convert(markup, { doctype: "book" })
return { title, content };
}
supported_formats.set("adoc", adoc_fn)
supported_formats.set("asciidoc", adoc_fn)
const supported_filetypes = [...supported_formats.keys()];
let docs_folder = process.env.REVANCED_DOCS_FOLDER;
if (docs_folder === undefined) {
if (prerendering) { console.warn("Using testing docs in production build") }
docs_folder = "testing-docs";
}
const ignored_items = ["assets"];
/// Utility functions
function is_directory(item: string) {
return fs.lstatSync(item).isDirectory();
}
function get_ext(fname: string) {
// Get extname and remove the first dot.
return path.extname(fname).substring(1);
}
function get_slug_of_node(node: DocsTreeNode): string {
if (is_tree(node)) {
return node.index.slug;
}
return node.slug;
}
/// Important functions
// Get a document. Returns null if it does not exist.
export function get(slug: string): Document|null {
let target = path.join(docs_folder, slug);
// Handle index (readme) file for folder.
if (exists(target) && is_directory(target)) {
target += "/README";
}
const dir = path.dirname(target);
if (!exists(dir)) {
return null;
}
let full_path, ext, found = false;
// We are looking for the file `${target}.(any_supported_extension)`. Try to find it.
for (const item of fs.readdirSync(dir)) {
full_path = path.join(dir, item);
// Get file extension
ext = get_ext(item);
// Unsupported/unrelated file.
if (!supported_formats.has(ext)) {
continue;
}
const desired_path = `${target}.${ext}`; // Don't grab some other random supported file.
if (!is_directory(full_path) && desired_path == full_path) {
// We found it.
found = true;
break;
}
}
if (!found) {
return null;
}
// Process the file and return.
return supported_formats.get(ext)(fs.readFileSync(full_path, 'utf-8'));
}
// Get file information
function process_file(fname: string): DocumentInfo {
// Remove docs folder prefix and file extension suffix, then split it.
const parts = fname
.substring(`${docs_folder}/`.length, fname.length - (get_ext(fname).length + 1))
.split("/");
// Remove `README` suffix if present.
const last_part_index = parts.length - 1;
if (parts[last_part_index] == "README") {
parts.pop();
}
const slug = parts.join("/");
const title = get(slug).title;
return { slug, title };
}
// Returns a document tree.
function process_folder(dir: string): DocsTree|null {
let tree: DocsTree = {
index: null,
nodes: []
};
// List everything in the directory.
const items = fs.readdirSync(dir);
for (const item of items) {
if (ignored_items.includes(item) || [".", "_"].includes(item[0])) {
continue;
}
const itemPath = path.join(dir, item);
const is_dir = is_directory(itemPath);
let is_index_file = false;
if (!is_dir) {
// Ignore files we cannot process.
if (!supported_formats.has(get_ext(item))) {
continue;
}
for (const ext of supported_filetypes) {
if (item == `README.${ext}`) {
is_index_file = true;
}
}
}
const node = is_dir ? process_folder(itemPath) : process_file(itemPath);
if (node === null) {
console.error(`The ${itemPath} directory does not have a README/index file! ignoring...`)
continue;
}
if (is_index_file) {
tree.index = node;
} else {
tree.nodes.push(node);
}
}
if (tree.index === null) {
return null;
}
// `numeric: true` because we want to be able to specify
// the order if necessary by prepending a number to the file name.
tree.nodes.sort(
(a, b) => get_slug_of_node(a).localeCompare(get_slug_of_node(b), "en", { numeric: true })
);
return tree;
}
// Returns the document tree.
export function index_content(): DocsTree {
const tree = process_folder(docs_folder);
if (tree === null) {
throw new Error("Root must have index (README) file.")
}
return tree;
}

View File

@@ -0,0 +1,28 @@
/// Types
export interface Document {
title: string;
// HTML
content: string;
}
export interface DocumentInfo {
title: string;
slug: string;
}
// A tree representing the `docs` folder.
export interface DocsTree {
// index.whatever
index: DocumentInfo;
// Everything except index.whatever
nodes: DocsTreeNode[];
}
export type DocsTreeNode = DocsTree | DocumentInfo;
/// Functions
export function is_tree(node: DocsTreeNode) {
return node.hasOwnProperty('nodes');
}