chore: upgrade to nuxt 4

This commit is contained in:
eggy
2025-10-19 15:32:55 +08:00
parent e814c77333
commit 21c717ed79
50 changed files with 3353 additions and 4155 deletions

View File

@@ -1,7 +1,7 @@
@import "base.scss";
@use "base.scss";
.prose article {
@include headings {
@include base.headings {
& > a:hover,
& > a:active {
text-decoration: underline;

View File

Before

Width:  |  Height:  |  Size: 490 B

After

Width:  |  Height:  |  Size: 490 B

View File

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 339 B

View File

Before

Width:  |  Height:  |  Size: 712 B

After

Width:  |  Height:  |  Size: 712 B

View File

@@ -1,20 +1,30 @@
<script setup lang="ts">
import type { GithubPushEvent } from "@/shared/github";
import type { Ref } from "vue";
const FEED_URL = "https://api.github.com/users/potatoeggy/events";
const imgUrl = ref("");
const href = ref("");
onMounted(async () => {
const results = (await useFetch(FEED_URL)).data as Ref<GithubPushEvent[]>;
const latestEvent = results.value.find(
(event) => event.type === "PushEvent"
) as GithubPushEvent;
const latestCommit = latestEvent.payload.commits[0];
imgUrl.value = `https://opengraph.githubassets.com/hash/${latestEvent.repo.name}/commit/${latestCommit.sha}`;
href.value = `https://github.com/${latestEvent.repo.name}/commit/${latestCommit.sha}`;
const { data: results } = await useFetch<GithubPushEvent[]>(FEED_URL, {
onResponse(res) {
res.response.json;
},
});
const latestEvent = results.value?.find(
(event: GithubPushEvent) => event.type === "PushEvent"
);
const latestCommitSha = latestEvent.payload.head;
const imgUrl = computed(() =>
results.value
? `https://opengraph.githubassets.com/hash/${latestEvent.repo.name}/commit/${latestCommitSha}`
: ""
);
const href = computed(() =>
results.value
? `https://github.com/${latestEvent.repo.name}/commit/${latestCommitSha}`
: ""
);
</script>
<template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { getPrettyDate, getUtcDate } from "~~/shared/metadata";
import type { AnyParsedContent } from "~~/shared/types";
import { getPrettyDate, getUtcDate } from "@/shared/metadata";
import type { AnyParsedContent } from "@/shared/types";
const { doc } = defineProps<{ doc: AnyParsedContent }>();

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import ColourPicker from "./ColourPicker.vue";
import { navItems } from "@/data/navItems";
import { navItems } from "~/data/navItems";
</script>
<template>

View File

@@ -13,40 +13,44 @@
</p>
<!-- i could make this a list but god i'm so tired with nuxt -->
<div class="flex justify-around flex-wrap gap-8 items-center">
<ServiceCard name="Gitea" href="https://git.eggworld.me" img="gitea.svg">
<ServiceCard
name="Gitea"
href="https://git.eggipelago.com"
img="gitea.svg"
>
Self-hosted GitHub
</ServiceCard>
<ServiceCard
name="Eifueo"
href="https://eifueo.eggworld.me"
href="https://eifueo.eggipelago.com"
img="eifueo.svg"
>
Note collection
</ServiceCard>
<ServiceCard
name="Primoprod"
href="https://primoprod.eggworld.me"
href="https://primoprod.vercel.app"
img="primogem.webp"
>
Wish simulator
</ServiceCard>
<ServiceCard
name="Calibre"
href="https://calibre.eggworld.me"
href="https://calibre.eggipelago.com"
img="calibre-web.webp"
>
Kobo Cloud
</ServiceCard>
<ServiceCard
name="Jellyfin"
href="https://jellyfin.eggworld.me"
href="https://jellyfin.eggipelago.com"
img="jellyfin.svg"
>
FOSS media server
</ServiceCard>
<ServiceCard
name="Minecraft"
href="minecraft.eggworld.me"
href="minecraft.eggipelago.com"
img="minecraft.svg"
unclickable
broken

View File

@@ -34,7 +34,7 @@ useHead({ title: "Oeufs?" });
GitHub</a
>
and
<a class="underline" href="https://git.eggworld.me/eggy/public">
<a class="underline" href="https://git.eggipelago.com/eggy/public">
Gitea
</a>
</p>

View File

@@ -7,7 +7,7 @@ tags:
- featured
---
Welcome to the very first [Primoprod](https://primoprod.eggworld.me) progress report! In a similar vein to quite a few open source emulation projects (such as those I follow myself using [emufeed](https://github.com/potatoeggy/emufeed/blob/master/sources.py)), I'll be releasing these tidbits in lieu of daily Unstagnation shorts sometimes.
Welcome to the very first [Primoprod](https://primoprod.vercel.app) progress report! In a similar vein to quite a few open source emulation projects (such as those I follow myself using [emufeed](https://github.com/potatoeggy/emufeed/blob/master/sources.py)), I'll be releasing these tidbits in lieu of daily Unstagnation shorts sometimes.
In this hopefully small series of development notes, I'll be laying out my experiences learning web development as an absolute amateur.

View File

@@ -6,7 +6,7 @@ tags:
- retrospective
---
For three and a half years, the [Eifueo project](https://eifueo.eggworld.me) has dutifully carried out its task of collecting and organising notes in a way that would be quick and easy to review. Although this worked out wonderfully in high school, the method is both inefficient and insufficient for the pace of higher education.
For three and a half years, the [Eifueo project](https://eifueo.eggipelago.com) has dutifully carried out its task of collecting and organising notes in a way that would be quick and easy to review. Although this worked out wonderfully in high school, the method is both inefficient and insufficient for the pace of higher education.
So how can we make it better?
@@ -14,7 +14,7 @@ So how can we make it better?
We can't.
At their core, engineering courses in university are about problem-solving. Instead of blindly memorising rules to be applied once to get you the answer, you blindly memorise rules to be applied *two or more times* to get you the answer.
At their core, engineering courses in university are about problem-solving. Instead of blindly memorising rules to be applied once to get you the answer, you blindly memorise rules to be applied _two or more times_ to get you the answer.
If we have to do that, it's much easier to write plenty of practice problems instead of rewriting plenty of notes.
@@ -26,7 +26,7 @@ Reformatting notes is not an easy endeavour. We have to re-examine our old notes
Me? A writer? Imagine.
This one isn't actually too bad, and doing practice problems instead isn't going to help much, but think of all the other fun things I could be doing instead. I already *have* the notes. If I need them, I'll just look back at them.
This one isn't actually too bad, and doing practice problems instead isn't going to help much, but think of all the other fun things I could be doing instead. I already _have_ the notes. If I need them, I'll just look back at them.
Anyway, by the end of Eifueo's lifespan, most of the content was regurgitated onto the site.
@@ -40,7 +40,7 @@ High school had many courses that were "expression"-focused, and those were the
Unfortunately, the physics courses are antithetical to everything Eifueo stands for. They have a rigid structure that you can't bullshit your way out of but are also flexible enough that you can't simply apply a formula. The best way to get good is simply to do more problems.
You'll have to understand that this greatly saddened me as a person who tries his hardest to do *less* problems. The cost-benefit ratio isn't worth it anymore.
You'll have to understand that this greatly saddened me as a person who tries his hardest to do _less_ problems. The cost-benefit ratio isn't worth it anymore.
## Retrospective
@@ -48,6 +48,4 @@ With a heavy heart, I must bid farewell to one of the first services I ever depl
o7 It has served me well.
![At this rate, I'm going to be an expert in perspective art!](assessment-art.webp)

View File

@@ -1,7 +0,0 @@
export const navItems = [
{ href: "/#about", title: "About" },
{ href: "/blog", title: "Blog" },
{ href: "/stories", title: "Stories" },
];
export default navItems;

View File

@@ -1,129 +0,0 @@
export type Language =
| "python"
| "javascript"
| "java"
| "typescript"
| "vue"
| "react"
| "markdown"
| "flutter"
| "android"
| "rust"
| "golang";
export interface Project {
name: string;
href: string;
img?: string;
description?: string;
longDescription?: string;
langs: Language[];
license?: "AGPL-3.0" | "GPL-3.0" | "MIT" | "LGPL-3.0";
type: "web" | "tool" | "embedded" | "service";
}
export const projects: Project[] = [
{
name: "Mandown",
href: "https://github.com/potatoeggy/mandown",
description:
"A comic downloader and converter to CBZ / EPUB / PDF for my Kobo.",
longDescription: "Available via CLI and a Qt GUI!",
langs: ["python"],
license: "AGPL-3.0",
img: "mandown.webp",
type: "tool",
},
{
name: "Noveldown",
href: "https://github.com/potatoeggy/noveldown",
langs: ["python"],
license: "LGPL-3.0",
description:
"A webnovel downloader and EPUB converter for my Kobo, with lots of metadata!",
longDescription: "Heavily borrows Mandown's design.",
type: "tool",
},
{
name: "Jeopardy",
href: "https://github.com/potatoeggy/jeopardy",
img: "jeopardy.webp",
langs: ["typescript", "vue"],
license: "AGPL-3.0",
description: "Kahoot-inspired Jeopardy! game, including Final Jeopardy!",
longDescription: "Created for Bayview's Computer Club.",
type: "web",
},
{
name: "Primoprod",
href: "https://github.com/potatoeggy/primoprod",
img: "primoprod.webp",
langs: ["typescript", "vue"],
license: "AGPL-3.0",
description:
"A game simulator to increase productivity with quests and gambling.",
longDescription: "My first project with a JS framework!",
type: "web",
},
{
name: "PillowⓇ",
href: "https://github.com/potatoeggy/ece198",
description:
"A water quality statistics aggregator written for the STM32 microcontroller with a display and keypad.",
langs: ["rust"],
license: "GPL-3.0",
type: "embedded",
img: "pillow.webp",
},
{
name: "Napbot",
href: "https://github.com/potatoeggy/napbot",
langs: ["python"],
license: "AGPL-3.0",
description:
"A Discord music bot with synchronised lyrics, originally a sleep tracking bot to encourage sleeping.",
img: "napbot.webp",
type: "service",
},
{
name: "AutoFicFare",
href: "https://github.com/potatoeggy/autoficfare",
langs: ["python"],
license: "GPL-3.0",
description:
"Automatically update fanfiction in a Calibre database to instantly update them on your Kobo.",
type: "tool",
},
];
const unreleasedProjects: Project[] = [
{
name: "Aleister",
href: "https://github.com/potatoeggy/aleister",
langs: ["rust"],
license: "AGPL-3.0",
type: "service",
},
{
name: "Aoto",
href: "https://github.com/potatoeggy/aoto",
langs: ["golang", "typescript", "react"],
license: "AGPL-3.0",
type: "web",
},
{
name: "Kobink",
href: "https://github.com/potatoeggy/kobink",
langs: ["rust"],
license: "AGPL-3.0",
type: "service",
},
{
name: "GBARR",
href: "https://github.com/potatoeggy/gbarr",
langs: ["rust"],
license: "GPL-3.0",
type: "embedded",
},
];
export default projects;

View File

@@ -1,13 +0,0 @@
interface SiteRevision {
title: string;
url: string;
}
export const revisions: SiteRevision[] = [
{
title: "Nuxt 3 (2022)",
url: "https://eggworld.me",
},
{ title: "Eleventy (2021)", url: "https://2021.eggworld.me" },
{ title: "Vanilla (2019-2020)", url: "https://2020.eggworld.me" },
];

View File

@@ -1,3 +0,0 @@
export const SpecialTags: string[] = [
"featured",
];

View File

@@ -1,79 +0,0 @@
export interface TagData {
name?: string;
description?: string;
}
export const tagInfo: Record<string, TagData> = {
barin: {
name: "Barin",
description:
"Welcome to Barin — a world in constant conflict between productivity and procrastination.",
},
bsscc: {
name: "BSSCC",
description: "Posts related to Bayview's Computer Club.",
},
ibia: {
name: "Ibia",
description:
"A Kurious child struggles to fight the misinformation brought by the Six Goddesses of the Subjects.",
},
misc: { name: "Miscellaneous" },
poetry: {
name: "Poetry",
description:
"Poetry is interesting in that there is a lot of implied stuff that is normally said directly in prose.",
},
primoprod: {
name: "Primoprod",
description:
'Reports following the development of <a href="https://github.com/potatoeggy/primoprod">Primoprod</a>.',
},
tech: { name: "Technology" },
unstagnation: {
name: "Unstagnation Short",
description:
"A collection of very short stories written to do something productive during JuneAugust 2020 and August 2021.",
},
albatross: {
name: "The FOSS Albatross",
description:
'Articles about free and open source software. Also available on <a href="https://medium.com/the-foss-albatross">Medium</a>.',
},
birds: {
name: "Bird Family",
description:
"A large, loving family of birds who have found in each other a kindred soul for eternal suffering.",
},
birdseye: {
name: "Bird's-Eye View",
description: "What's the world like to a pair of human-watching bluebirds?",
},
uoft: {
name: "University of Teyvat",
description: "A <em>Genshin Impact</em> university AU.",
},
nanowrimo: {
name: "NaNoWriMo",
description:
"Story snippets written during National Novel Writing Month as part of a larger work.",
},
skyprojections: {
name: "Projections in the Sky",
description: "Dreams or reality — what is the difference?",
},
featured: {
name: "Featured",
description: "Works that are less rambly and more actually good!",
},
"monoceros (novel)": {
name: "Monoceros (novel)",
description: "A coffee shop where six students meet and become friends.",
},
"emma the narwhal": {
name: "Emma the Narwhal",
description:
'A mystery-betrayal story written by April Evans in <a href="/tags/stories/monoceros (novel)"><em>Monoceros</em> (novel)</a>.',
},
};
export default tagInfo;

View File

@@ -1,8 +1,9 @@
import { defineNuxtConfig } from "nuxt/config";
import svgLoader from "vite-svg-loader";
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
compatibilityDate: "2024-10-16",
compatibilityDate: "2025-10-19",
app: {
head: {
htmlAttrs: {
@@ -23,7 +24,6 @@ export default defineNuxtConfig({
{
defer: true,
src: "/script.js",
hid: "stupidEmergencyScript",
type: "module",
},
],
@@ -45,7 +45,7 @@ export default defineNuxtConfig({
shim: false,
},
site: {
url: process.env.BASE_URL || "https://eggworld.me",
url: process.env.BASE_URL || "https://eggipelago.com",
},
sitemap: {
strictNuxtContentPaths: true,

View File

@@ -1,5 +1,6 @@
{
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
@@ -8,19 +9,19 @@
},
"devDependencies": {
"@nuxt/content": "^2.13.4",
"@nuxtjs/color-mode": "^3.5.1",
"@nuxtjs/sitemap": "^6.1.2",
"@nuxtjs/tailwindcss": "^6.12.1",
"@tailwindcss/typography": "^0.5.15",
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/sitemap": "^7.4.7",
"@nuxtjs/tailwindcss": "^6.14.0",
"@tailwindcss/typography": "^0.5.19",
"@types/node": "^22.7.5",
"dayjs": "^1.11.13",
"nuxt": "3.13.2",
"prettier": "^3.3.3",
"dayjs": "^1.11.18",
"nuxt": "^4.1.3",
"prettier": "^3.6.2",
"reading-time": "^2.0.0-1",
"rehype-katex": "^7.0.1",
"remark-math": "^6.0.0",
"sass": "^1.79.5",
"typescript": "^5.6.3",
"sass": "^1.93.2",
"typescript": "^5.9.3",
"vite-svg-loader": "^5.1.0"
}
}

View File

@@ -1,3 +1,3 @@
Sitemap: https://eggworld.me/sitemap.xml
Sitemap: https://eggipelago.com/sitemap.xml
User-agent: *
Disallow:

View File

@@ -23,13 +23,13 @@ if (darkToggle) {
const FEED_URL = "https://api.github.com/users/potatoeggy/events";
const results = (await (await fetch(FEED_URL)).json());
const latestEvent = results.find((e) => e.type === "PushEvent");
const latestCommit = latestEvent.payload.commits[0];
const latestCommit = latestEvent.payload.head;
const commitImg = document.getElementById("github-commit-img");
const commitAnchor = document.getElementById("github-commit-a");
if (commitImg) {
commitImg.src = `https://opengraph.githubassets.com/hash/${latestEvent.repo.name}/commit/${latestCommit.sha}`;
commitImg.src = `https://opengraph.githubassets.com/hash/${latestEvent.repo.name}/commit/${latestCommitSha}`;
}
if (commitAnchor) {
commitAnchor.href = `https://github.com/${latestEvent.repo.name}/commit/${latestCommit.sha}`;
commitAnchor.href = `https://github.com/${latestEvent.repo.name}/commit/${latestCommitSha}`;
}
export {};

View File

@@ -2,7 +2,7 @@
// of all the nuxt bs while we wait for static generation
// to actually become a thing in nuxt 3
import type { GithubPushEvent } from "../shared/github";
import type { GithubPushEvent } from "../app/shared/github";
const html = document.getElementsByTagName("html")[0];
html.className = localStorage.theme ?? "light";
@@ -36,7 +36,7 @@ const results = (await (await fetch(FEED_URL)).json()) as GithubPushEvent[];
const latestEvent = results.find(
(e) => e.type === "PushEvent"
) as GithubPushEvent;
const latestCommit = latestEvent.payload.commits[0];
const latestCommitSha = latestEvent.payload.head;
const commitImg = document.getElementById(
"github-commit-img"
@@ -46,11 +46,11 @@ const commitAnchor = document.getElementById(
) as HTMLAnchorElement;
if (commitImg) {
commitImg.src = `https://opengraph.githubassets.com/hash/${latestEvent.repo.name}/commit/${latestCommit.sha}`;
commitImg.src = `https://opengraph.githubassets.com/hash/${latestEvent.repo.name}/commit/${latestCommitSha}`;
}
if (commitAnchor) {
commitAnchor.href = `https://github.com/${latestEvent.repo.name}/commit/${latestCommit.sha}`;
commitAnchor.href = `https://github.com/${latestEvent.repo.name}/commit/${latestCommitSha}`;
}
// to make this an esm module for top-level await

117
shared/github.d.ts vendored
View File

@@ -1,117 +0,0 @@
// i know i can import one but
// i can't find one so here we are
export interface GithubUser {
id: number;
login: string;
display_login: string;
gravatar_id: string;
url: string;
avatar_url: string;
}
export interface GithubRepo {
id: number;
name: string;
url: string;
}
export interface GithubCommit {
sha: string;
author: {
email: string;
name: string;
};
message: string;
distinct: boolean;
url: string;
}
export interface GithubPullRequest {
url: string;
id: number;
node_id: string;
html_url: string;
diff_url: string;
patch_url: string;
issue_url: string;
number: number;
state: string;
locked: boolean;
title: string;
body: string;
created_at: string;
updated_at: string;
closed_at: string | null;
merged_at: string | null;
merge_commit_sha: string | null;
draft: boolean;
// there's more but i don't wanna
}
export interface GithubRelease {
url: string;
assets_url: string;
upload_url: string;
html_url: string;
id: number;
// author: AUTHOR
node_id: string;
tag_name: string;
target_commitish: string;
name: string;
draft: boolean;
prerelease: boolean;
created_at: string;
published_at: string;
tarball_url: string;
zipball_url: string;
body: string;
short_description_html: string;
is_short_description_html_truncated: boolean;
}
export interface GithubCommitEventPayload {
push_id: number;
size: number;
distinct_size: number;
ref: string;
head: string;
before: string;
commits: GithubCommit[];
}
export interface GithubPullRequestEventPayload {
action: string;
number: number;
pull_request: GithubPullRequest;
}
export interface GithubReleaseEventPayload {
action: string;
release: GithubRelease;
public: boolean;
created_at: string;
}
export interface GithubEvent {
id: string;
type: "PushEvent" | "CreateEvent" | "ReleaseEvent" | "PullRequestEvent";
actor: GithubUser;
repo: GithubRepo;
payload:
| GithubCommitEventPayload
| GithubPullRequestEventPayload
| GithubReleaseEventPayload;
public: boolean;
created_at: string;
}
export interface GithubPushEvent extends GithubEvent {
type: "PushEvent";
payload: GithubCommitEventPayload;
}
export interface GithubCreateEvent {}
export interface GithubReleaseEvent {}

View File

@@ -1,44 +0,0 @@
import type { AnyParsedContent } from "./types";
import readingTime from "reading-time";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc.js";
dayjs.extend(utc);
function countWords(str: string) {
let words = 0;
for (const c of str) {
if (c === " " || c === "/") {
words++;
}
}
return words;
}
function search(obj: Record<string, any>, results: string[] = []) {
if (obj.value) {
results.push(obj.value);
}
if (obj.children) {
for (const el of obj.children) {
search(el, results);
}
}
return results;
}
export function calcReadingTime(doc: AnyParsedContent) {
let body: string[] = search(doc.body);
return readingTime(body.join(" "));
}
export function getPrettyDate(doc: AnyParsedContent) {
const date = dayjs(doc.date).utc();
return date.format("DD MMM YYYY");
}
export function getUtcDate(doc: AnyParsedContent) {
const date = dayjs(doc.date).utc();
return date.format("YYYY-MM-DD");
}

28
shared/types.d.ts vendored
View File

@@ -1,28 +0,0 @@
import type { ParsedContent } from "@nuxt/content/dist/runtime/types";
interface ReadingTime {
text: string;
minutes: number;
time: number;
words: number;
}
interface BlogParsedContent extends ParsedContent {
date: Date;
title: string;
tags: string[];
description?: string;
readingTime: ReadingTime;
nopreview?: boolean;
}
interface StoryParsedContent extends ParsedContent {
date: Date;
title: string;
tags: string[];
description?: string;
readingTime: ReadingTime;
nopreview?: boolean;
}
type AnyParsedContent = BlogParsedContent | StoryParsedContent;

View File

@@ -18,6 +18,6 @@
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"alwaysStrict": true
"alwaysStrict": true,
}
}

6965
yarn.lock

File diff suppressed because it is too large Load Diff