Compare commits

..

No commits in common. "main" and "post/page" have entirely different histories.

15 changed files with 52 additions and 240 deletions

View File

@ -11,7 +11,7 @@ repos:
hooks: hooks:
- id: conventional-pre-commit - id: conventional-pre-commit
stages: [commit-msg] stages: [commit-msg]
args: [feat, fix, chore, test, build, merge, refactor, doc] args: [feat, fix, chore, test, build, merge, refactor]
- repo: local - repo: local
hooks: hooks:
- id: eslint - id: eslint

View File

@ -19,18 +19,6 @@ To insert custom content, create a directory with a similar structure to
directory. The website will be built statically in `build/`. Note that changing directory. The website will be built statically in `build/`. Note that changing
`favicon.svg` will require modifying the `static/` folder. `favicon.svg` will require modifying the `static/` folder.
Set `metadata.json` based on the example to have a custom avatar and background
image.
- `name`: Displayed name on the home page
- `blogName`: Displayed name of the blog
- `url`: The site url
- `frontDescription`: The text above the main navbar
- `frontCover`: Path to the cover image
- `picture`: Avatar
- `links`: A map of brands to links
- `extra`: Extra pages showing on the navbar
- `markdown`: `{ "highlighterLangs": [ ... ], "katex": [ ... ] }`
## Developing ## Developing
Install [pre-commit](https://pre-commit.com/). Install [pre-commit](https://pre-commit.com/).

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "chrysoblog", "name": "chrysoblog",
"version": "0.0.4", "version": "0.0.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "chrysoblog", "name": "chrysoblog",
"version": "0.0.4", "version": "0.0.2",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"bits-ui": "^0.21.13", "bits-ui": "^0.21.13",

View File

@ -1,6 +1,6 @@
{ {
"name": "chrysoblog", "name": "chrysoblog",
"version": "0.0.4", "version": "0.0.2",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",

View File

@ -12,12 +12,5 @@
}, },
"extra": [ "extra": [
{ "route": "/archives", "name": "Archives" } { "route": "/archives", "name": "Archives" }
],
"markdown": {
"highlighterLangs": [
"python",
"lean",
"tex"
] ]
}
} }

View File

@ -26,7 +26,3 @@ $$
$$ $$
\vec z^{(l+1)} = \mat M\vec z^{(l)} + \vec b \vec z^{(l+1)} = \mat M\vec z^{(l)} + \vec b
$$ $$
```tex
\\newcommand{\\cirno}{9}
```

View File

@ -15,8 +15,7 @@ export function transformPostMeta(metadata: PostMetadata, slug: string) : Post {
}; };
} }
// Generates a list of all posts filtering by tags and series, reverse chronologically ordered. export async function getPosts(tag: string | null = null) : Promise<Post[]> {
export async function getPosts(filter: { tag?: string, series?: string }) : Promise<Post[]> {
const allPostFiles = import.meta.glob('$content/post/*.md'); const allPostFiles = import.meta.glob('$content/post/*.md');
const iterablePostFiles = Object.entries(allPostFiles); const iterablePostFiles = Object.entries(allPostFiles);
@ -28,10 +27,8 @@ export async function getPosts(filter: { tag?: string, series?: string }) : Prom
return transformPostMeta(metadata, slug); return transformPostMeta(metadata, slug);
}) })
); );
if (filter?.tag) if (tag)
posts = posts.filter(post => post.tags.includes(filter.tag!)) posts = posts.filter(post => post.tags.includes(tag))
if (filter?.series)
posts = posts.filter(post => post.series?.includes(filter.series!) ?? false)
posts.sort((post1, post2) => { posts.sort((post1, post2) => {
const date1: Date = post1.date; const date1: Date = post1.date;

View File

@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import '../app.css'; import '../app.css';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { name } from '$content/metadata.json';
import Footer from "./Footer.svelte"; import Footer from "./Footer.svelte";
import Navbar from "./Navbar.svelte"; import Navbar from "./Navbar.svelte";
import PageTransition from "./PageTransition.svelte"; import PageTransition from "./PageTransition.svelte";
@ -12,8 +11,7 @@
<!-- Homepage has its own navbar with animations --> <!-- Homepage has its own navbar with animations -->
{#if $page.url.pathname != "/"} {#if $page.url.pathname != "/"}
<div id="layout"> <div id="layout">
<div id="nav"> <div id="navbar">
<p id="bio-name">{name}</p>
<Navbar /> <Navbar />
<slot name="header" /> <slot name="header" />
</div> </div>
@ -33,12 +31,6 @@
{/if} {/if}
<style> <style>
#bio-name {
font-family: serif;
font-size: 3rem;
font-weight: normal;
color: var(--name);
}
#layout { #layout {
max-width: max(80vw, 100vh); max-width: max(80vw, 100vh);
margin-left: auto; margin-left: auto;
@ -66,7 +58,7 @@
width: min(100vw, max(50vw, 100vh)); width: min(100vw, max(50vw, 100vh));
grid-area: content; grid-area: content;
} }
#nav { #navbar {
align-self: right; align-self: right;
padding-left: 10px; padding-left: 10px;
grid-area: nav; grid-area: nav;

View File

@ -19,22 +19,15 @@
import metadata from '$content/metadata.json'; import metadata from '$content/metadata.json';
const iconMap: Map<string, string> = { const iconMap: Map<string, string> = {
"orcid": "fa-brands fa-orcid", "bitbucket": "fa-brands fa-bitbucket",
"google-scholar": "fa-brands fa-google-scholar",
"discord": "fa-brands fa-discord", "discord": "fa-brands fa-discord",
"email": "fa-solid fa-envelope", "email": "fa-solid fa-envelope",
"bitbucket": "fa-brands fa-bitbucket",
"github": "fa-brands fa-github", "github": "fa-brands fa-github",
"gitlab": "fa-brands fa-gitlab", "gitlab": "fa-brands fa-gitlab",
"open-source": "fa-brands fa-osi", "google-scholar": "fa-brands fa-google-scholar",
"bluesky": "fa-brands fa-bluesky",
"youtube": "fa-brands fa-youtube",
"instagram": "fa-brands fa-instagram", "instagram": "fa-brands fa-instagram",
"bandcamp": "fa-brands fa-bandcamp", "open-source": "fa-brands fa-osi",
"pixiv": "fa-brands fa-pixiv", "orcid": "fa-brands fa-orcid",
}; };
const iconLinks: [string, string][] = Object.entries(metadata.links).map( const iconLinks: [string, string][] = Object.entries(metadata.links).map(
([key, link]) => [iconMap[key], link]); ([key, link]) => [iconMap[key], link]);
@ -108,17 +101,18 @@
<style> <style>
#background { #background {
background-attachment: fixed;
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center center; background-position: center center;
z-index: -10; z-index: -10;
position: absolute; position: fixed;
min-width: 100vw; min-width: 100%;
height: 100vh; min-height: 100%;
overscroll-behaviour: none; overscroll-behaviour: none;
} }
#front { #front {
height: 100svh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overscroll-behavior: none; overscroll-behavior: none;

View File

@ -1,95 +1,43 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import { routes } from "$lib/sitemap.ts" import { routes } from "$lib/sitemap.ts"
import { name } from '$content/metadata.json';
import ThemeSwitch from "$lib/components/ThemeSwitch.svelte" import ThemeSwitch from "$lib/components/ThemeSwitch.svelte"
import { Rss, Menu } from "lucide-svelte"; import { Rss } from "lucide-svelte";
function isCurrentLink(pathname, route) { function isCurrentLink(pathname, route) {
return route != "/" && pathname.startsWith(route) return route != "/" && pathname.startsWith(route)
|| route == "/post" && pathname.startsWith("/page"); || route == "/post" && pathname.startsWith("/page");
} }
import { onMount } from 'svelte';
let showDropdown = false;
function onDropdownToggle(event) {
showDropdown = !showDropdown;
}
function onKeyDown(event) {
if (event.key == "Escape") {
showDropdown = !showDropdown;
}
}
</script> </script>
<nav <div id="navbar" class="h-5 space-x-4">
id="navbar" <p id="bio-name">{name}</p>
class="h-5 space-x-4"
class:dropdown-opened={showDropdown}>
<div
class="link-container"
role="button"
tabindex={0}
on:keydown={onKeyDown}
on:click={() => { showDropdown = false; }}
>
{#each routes as item} {#each routes as item}
<a <a
class="nav-link" class="nav-link"
class:disabled-link={item.disabled} class:disabled-link={item.disabled}
class:current-link={isCurrentLink($page.url.pathname, item.route)} class:current-link={isCurrentLink($page.url.pathname, item.route)}
on:click={() => { showDropdown = false; }} href={item.route}>{item.name}</a>
href={item.route}>
{item.name}
</a>
{/each} {/each}
<a class="nav-link icon" href="/rss.xml"><Rss /></a> <a class="nav-link icon" href="/rss.xml"><Rss /></a>
<div><ThemeSwitch /></div> <div><ThemeSwitch /></div>
</div> </div>
<button
id="dropdown-toggle"
aria-hidden="true"
on:click={onDropdownToggle}>
<Menu />
</button>
</nav>
<svelte:window on:keydown={onKeyDown} />
<style> <style>
.link-container { #bio-name {
font-family: serif;
font-size: 3rem;
font-weight: normal;
color: var(--name);
}
#navbar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; align-items: flex-end;
position: sticky; justify-content: between;
top: 0;
}
#dropdown-toggle {
display: none;
}
/* mobile mode */
@media (max-width: 768px) {
.link-container {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 100vh;
justify-content: center;
margin: 0;
padding-left: 5vw;
padding-right: 5vw;
background: hsl(var(--background) / .8);
/* Initial hidden state */
opacity: 0;
transform: translateY(-100%);
transition: transform 0.2s, opacity 0.2s;
} }
@media (max-width: 700px) {
#navbar { #navbar {
display: inline-block; display: inline-block;
white-space: pre-wrap; white-space: pre-wrap;
@ -97,14 +45,5 @@
#navbar .nav-link { #navbar .nav-link {
display: inline-block; display: inline-block;
} }
#dropdown-toggle {
display: initial;
z-index: 5;
}
.dropdown-opened > .link-container {
opacity: 1;
z-index: 1;
transform: translateY(0);
}
} }
</style> </style>

View File

@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import { TrackPrevious, TrackNext } from 'svelte-radix';
import type { PageData } from './$types'; import type { PageData } from './$types';
export let data: PageData; export let data: PageData;
const { post, Content, seriesNeighbours } = data; const { post, Content } = data;
import siteMetadata from '$content/metadata.json'; import siteMetadata from '$content/metadata.json';
import PostHeader from '$lib/components/PostHeader.svelte'; import PostHeader from '$lib/components/PostHeader.svelte';
</script> </script>
@ -18,39 +17,3 @@
<Content /> <Content />
</article> </article>
<hr /> <hr />
<ul id="series">
{#each seriesNeighbours as { name, prevSlug, prevTitle, nextSlug, nextTitle }}
<li class="series-neighbour">
{#if prevTitle}
<a href="/post/{prevSlug}">{prevTitle}</a>
<p class="dummy"><TrackPrevious /></p>
{/if}
<p class="series-tag">{name}</p>
{#if nextTitle}
<p class="dummy"><TrackNext /></p>
<a href="/post/{nextSlug}">{nextTitle}</a>
{/if}
</li>
{/each}
</ul>
<style>
#series {
text-align: center;
}
.series-tag {
color: var(--series);
display: inline-block;
}
.dummy {
display: inline-block;
color: rgba(128,128,128, .6);
vertical-align: middle;
padding-left: 1em;
padding-right: 1em;
}
.series-neighbour {
padding-left: 2em;
padding-right: 2em;
}
</style>

View File

@ -1,54 +1,16 @@
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
import type { Post } from '$lib/types' import { transformPostMeta } from '$lib/posts';
import { getPosts, transformPostMeta } from '$lib/posts';
export const load: PageLoad = async ({ params }) => { export const load: PageLoad = async ({ params }) => {
try { try {
const post = await import(`$content/post/${params.slug}.md`); const post = await import(`$content/post/${params.slug}.md`);
if (!post) throw error(404); if (!post) throw error(404);
const metadata: Post = transformPostMeta(post.metadata, params.slug);
const Content = post.default; const Content = post.default;
const seriesNeighbours : {
name: string,
prevSlug?: string,
prevTitle?: string,
nextSlug?: string,
nextTitle?: string,
}[] = await Promise.all((metadata.series || []).map(async (series: string) => {
const postInSeries: Post[] = await getPosts({ series });
let prevSlug = undefined;
let prevTitle = undefined;
let nextSlug = undefined;
let nextTitle = undefined;
for (const p of postInSeries) {
if (p.date > metadata.date) {
nextSlug = p.slug;
nextTitle = p.title;
}
if (p.date < metadata.date) {
prevSlug = p.slug;
prevTitle = p.title;
break;
}
}
return { return {
name: series, post: transformPostMeta(post.metadata, params.slug),
prevSlug,
prevTitle,
nextSlug,
nextTitle,
};
}));
return {
post: metadata,
Content, Content,
seriesNeighbours,
}; };
} }
catch { catch {

View File

@ -2,7 +2,7 @@ import type { PageLoad } from './$types';
import { getPosts } from '$lib/posts'; import { getPosts } from '$lib/posts';
export const load: PageLoad = async ({ params }) => { export const load: PageLoad = async ({ params }) => {
const posts = await getPosts({ tag: params.slug }); const posts = await getPosts(params.slug);
return { return {
name: params.slug, name: params.slug,
posts, posts,

View File

@ -1,6 +1,5 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import adapter from '@sveltejs/adapter-static'; import adapter from '@sveltejs/adapter-static';
import fs from 'fs';
import { mdsvex, escapeSvelte } from 'mdsvex'; import { mdsvex, escapeSvelte } from 'mdsvex';
import remarkAlert from './src/lib/markdown.js' import remarkAlert from './src/lib/markdown.js'
@ -13,9 +12,7 @@ import remarkMath from 'remark-math'
const contentDir = process.env?.CHRYSOBLOG_CONTENT ?? "src/content"; const contentDir = process.env?.CHRYSOBLOG_CONTENT ?? "src/content";
const metadata = await fs.promises.readFile(contentDir + "/metadata.json") const langs = [
.then((file) => JSON.parse(file.toString()));
const langs = metadata?.markdown.highlighterLangs ?? [
"javascript", "javascript",
"lean", "lean",
"lisp", "lisp",
@ -29,7 +26,11 @@ const langs = metadata?.markdown.highlighterLangs ?? [
"c++", "c++",
"sh", "sh",
]; ];
const katexOptions = metadata?.markdown.katex ?? { const highlightingThemes = {
light: 'vitesse-light',
dark: 'vitesse-dark',
};
const katexOptions = {
macros: { macros: {
"\\Nat": "\\mathbb N", "\\Nat": "\\mathbb N",
"\\Complex": "\\mathbb C", "\\Complex": "\\mathbb C",
@ -38,10 +39,6 @@ const katexOptions = metadata?.markdown.katex ?? {
"\\rv": "\\mathrm", "\\rv": "\\mathrm",
}, },
}; };
const highlightingThemes = {
light: 'vitesse-light',
dark: 'vitesse-dark',
};
const themes = Object.keys(highlightingThemes).map(function(k){ return highlightingThemes[k] }); const themes = Object.keys(highlightingThemes).map(function(k){ return highlightingThemes[k] });
const highlighter = await getHighlighter({ const highlighter = await getHighlighter({

View File

@ -1,9 +0,0 @@
import { expect, test } from '@playwright/test';
test('Navigate in series', async ({ page }) => {
await page.goto('/post');
await page.getByText('Placeholder 2').click();
await expect(page).toHaveURL("/post/placeholder2/");
await page.getByText('Placeholder 1').click();
await expect(page).toHaveURL("/post/placeholder1/");
});