Reorganise views/ directory structure to match routes
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 16s

Move shrines and bookmarks under home/, landing and 404 into own
subdirectories, and retire Notes.vue (served by external service).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 11:15:46 +01:00
parent 869d9a168e
commit 4d154ff837
11 changed files with 494 additions and 14 deletions

View File

@@ -1,7 +1,7 @@
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
import DefaultLayout from "@/layouts/DefaultLayout.vue"; import DefaultLayout from "@/layouts/DefaultLayout.vue";
import CVLayout from "@/layouts/CVLayout.vue"; import CVLayout from "@/layouts/CVLayout.vue";
import Landing from "@/views/Landing.vue"; import Landing from "@/views/landing/Landing.vue";
import { useHomeDataStore } from "@/stores/homeData"; import { useHomeDataStore } from "@/stores/homeData";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
@@ -30,42 +30,37 @@ const router = createRouter({
{ {
path: "bookmarks", path: "bookmarks",
name: "bookmarks", name: "bookmarks",
component: () => import("@/views/Bookmarks.vue"), component: () => import("@/views/home/bookmarks/Bookmarks.vue"),
},
{
path: "notes/:path(.*)*",
name: "notes",
component: () => import("@/views/Notes.vue"),
}, },
{ {
path: "shrines", path: "shrines",
name: "shrine links", name: "shrine links",
component: () => import("@/views/Shrines.vue"), component: () => import("@/views/home/shrines/Shrines.vue"),
}, },
{ {
path: "shrines/gto", path: "shrines/gto",
name: "gto shrine", name: "gto shrine",
component: () => import("@/views/shrines/GTO.vue"), component: () => import("@/views/home/shrines/GTO.vue"),
}, },
{ {
path: "shrines/skipskipbenben", path: "shrines/skipskipbenben",
name: "skipskipbenben shrine", name: "skipskipbenben shrine",
component: () => import("@/views/shrines/Skipskipbenben.vue"), component: () => import("@/views/home/shrines/Skipskipbenben.vue"),
}, },
{ {
path: "shrines/evangelion", path: "shrines/evangelion",
name: "evangelion shrine", name: "evangelion shrine",
component: () => import("@/views/shrines/Evangelion.vue"), component: () => import("@/views/home/shrines/Evangelion.vue"),
}, },
{ {
path: "shrines/demoman", path: "shrines/demoman",
name: "demoman shrine", name: "demoman shrine",
component: () => import("@/views/shrines/Demoman.vue"), component: () => import("@/views/home/shrines/Demoman.vue"),
}, },
{ {
path: ":pathMatch(.*)*", path: ":pathMatch(.*)*",
name: "404", name: "404",
component: () => import("@/views/404.vue"), component: () => import("@/views/404/404.vue"),
}, },
], ],
}, },

13
vue/src/views/404/404.vue Normal file
View File

@@ -0,0 +1,13 @@
<template>
<main class="flex flex-col items-center">
<div
class="a4page-portrait items-center bdr-1 flex flex-col relative overflow-scroll"
>
<h1>404</h1>
<RouterLink to="/" class="bdr-2">
<img src="/img/memes/epic.jpeg" loading="lazy" />
</RouterLink>
<h1>Click her, she will take you home</h1>
</div>
</main>
</template>

View File

@@ -6,8 +6,8 @@ import Header from "@/components/text/Header.vue";
const site_links = [ const site_links = [
{ name: "CV", link: "/cv" }, { name: "CV", link: "/cv" },
{ name: "Bookmarks", link: "/bookmarks" }, { name: "Bookmarks", link: "/bookmarks" },
{ name: "Shrines", link: "/shrines" },
{ name: "Admin", link: "/admin" }, { name: "Admin", link: "/admin" },
// { name: "Shrines", link: "/shrines" },
]; ];
const social_links = [ const social_links = [

View File

@@ -0,0 +1,255 @@
<script setup>
import LinkTable from "@/components/util/LinkTable.vue";
const links = [
[
"Reading Links",
[
{
name: "Substack",
link: "https://substack.com/",
},
{
name: "Medium",
link: "https://medium.com/",
},
{
name: "4Chan",
link: "https://www.4chan.org/",
},
],
],
[
"Job Links",
[
{
name: "LinkedIn",
link: "https://www.linkedin.com/",
},
{
name: "Jack and Jill",
link: "https://app.jackandjill.ai",
},
{
name: "LinkedIn",
link: "https://www.linkedin.com/",
},
{
name: "Prospects",
link: "https://www.prospects.ac.uk/",
},
{
name: "GOV",
link: "https://findajob.dwp.gov.uk",
},
{
name: "Glassdoor",
link: "https://www.glassdoor.co.uk/",
},
{
name: "Indeed",
link: "https://www.indeed.co.uk/",
},
],
],
[
"Learning Links",
[
{
name: "Leetcode",
link: "https://leetcode.com/",
},
{
name: "ISLP",
link: "https://hastie.su.domains/ISLP/ISLP_website.pdf.download.html",
},
],
],
[
"Social Links",
[
{
name: "Outlook",
link: "https://outlook.live.com/",
},
{
name: "Gmail",
link: "https://mail.google.com/",
},
{
name: "Whatsapp",
link: "https://web.whatsapp.com/",
},
],
],
[
"Radio links",
[
{
name: "Radio Helsinki",
link: "https://www.radiohelsinki.fi/",
},
{
name: "Palanga Street Radio",
link: "https://palanga.live/",
},
{
name: "IDA Radio",
link: "https://idaidaida.net/",
},
{
name: "Tīrkultūra",
link: "https://www.tirkultura.lv/",
},
],
],
[
"Hacking Links",
[
{
name: "pwn.college",
link: "https://pwn.college/",
},
{
name: "OSINT Framework",
link: "https://osintframework.com/",
},
{
name: "OverTheWire",
link: "https://overthewire.org/",
},
{
name: "TryHackMe",
link: "https://tryhackme.com/",
},
],
],
[
"Chinese Links",
[
{
name: "MDBG Chinese Dictionary",
link: "https://www.mdbg.net/chinese/dictionary",
},
{
name: "Stroke Order",
link: "https://www.strokeorder.com/",
},
{
name: "HSK 1 Peking University",
link: "https://youtube.com/playlist?list=PLVWfp7qXLmKVfSUkucXErLncKn-JqgBbK&si=2ytO3inS8-iOAOx2",
},
{
name: "Stroke Order",
link: "https://www.strokeorder.com/",
},
{
name: "Offbeat Mandarin",
link: "https://www.youtube.com/@OffbeatMandarin",
},
],
],
[
"Art links",
[
{
name: "Frida Kahlo",
link: "https://www.fridakahlo.org/",
},
{
name: "Cameron's World",
link: "https://www.cameronsworld.net/",
},
{
name: "Neocities",
link: "https://neocities.org/",
},
],
],
[
"Vue links",
[
{
name: "Vue",
link: "https://vuejs.org/guide/introduction.html",
},
{
name: "Vue Router",
link: "https://router.vuejs.org/introduction.html",
},
{
name: "Pinia",
link: "https://pinia.vuejs.org/introduction.html",
},
],
],
[
"Go links",
[
{
name: "Golang",
link: "https://golang.org/doc/",
},
{
name: "Gin Gonic",
link: "https://gin-gonic.com/en/docs/introduction/",
},
{
name: "GORM",
link: "https://gorm.io/gen/index.html",
},
],
],
[
"Doc links",
[
{
name: "Rust",
link: "https://doc.rust-lang.org/stable/book/index.html",
},
{
name: "Javascript",
link: "https://developer.mozilla.org/en-US/docs/Web/JavaScript",
},
{
name: "Python",
link: "https://docs.python.org/3/",
},
],
],
[
"Article links",
[
{
name: "Go and GORM",
link: "https://medium.com/@chaewonkong/learn-go-understanding-and-implementing-foreign-keys-with-gorm-6d7608e1dbf6",
},
{
name: "JWT Auth in GO",
link: "https://medium.com/monstar-lab-bangladesh-engineering/jwt-auth-in-go-dde432440924",
},
{
name: "Websockets in GO",
link: "https://medium.com/@tanngontn/golang-gin-framework-with-normal-websocket-and-websocket-with-producer-is-rabbitmq-guide-93cad7d290f7",
},
],
],
];
</script>
<template>
<main class="items-center flex flex-col">
<div
class="a4page-portrait bdr-1 flex flex-row flex-wrap overflow-x-auto gap-1"
>
<div class="w-full h-fit">
<LinkTable
class="flex flex-col flex-wrap"
v-for="link in links"
:title="link[0]"
:items="link[1]"
/>
</div>
</div>
</main>
</template>

View File

@@ -0,0 +1,29 @@
<script setup>
import VideoTable from "@/components/util/VideoTable.vue";
import Link from "@/components/text/Link.vue";
const videoSources = [
{ name: "demoman", link: "/img/demoman/1760582395316219.webm" },
{ name: "demoman", link: "/img/demoman/1761052136609718.webm" },
{ name: "demoman", link: "/img/demoman/1761088452011210.mp4" },
{ name: "demoman", link: "/img/demoman/1761570214170465.webm" },
{ name: "demoman", link: "/img/demoman/1761828457509465.webm" },
];
</script>
<template>
<main class="items-center flex flex-col">
<div
class="a4page-portrait bdr-1 flex flex-row relative overflow-scroll items-center"
>
<p>
<Link href="https://wiki.teamfortress.com/wiki/Demoman"
>The goat</Link
>
</p>
<div>
<VideoTable :sourceArr="videoSources" />
</div>
</div>
</main>
</template>

View File

@@ -0,0 +1,13 @@
<script setup>
import Wip from "@/components/util/Wip.vue";
</script>
<template>
<main class="items-center flex flex-col">
<div
class="a4page-portrait items-center bdr-1 flex flex-col relative overflow-scroll"
>
<Wip />
</div>
</main>
</template>

View File

@@ -0,0 +1,11 @@
<script setup>
import Wip from "@/components/util/Wip.vue";
</script>
<template>
<main class="items-center flex flex-col">
<div class="a4page-portrait items-center bdr-1 flex flex-col relative overflow-scroll">
<Wip />
</div>
</main>
</template>

View File

@@ -0,0 +1,20 @@
<script setup>
import RouterTable from "@/components/util/RouterTable.vue";
const shrine_links = [
{ name: "Demoman", link: "/shrines/demoman" },
{ name: "Evangelion", link: "/shrines/evangelion" },
{ name: "GTO", link: "/shrines/gto" },
{ name: "Skipskipbenben", link: "/shrines/skipskipbenben" },
];
</script>
<template>
<main class="items-center flex flex-col">
<div class="background" />
<div
class="a4page-portrait bdr-1 flex flex-col relative overflow-scroll gap-1"
>
<RouterTable :linkArr="shrine_links" />
</div>
</main>
</template>

View File

@@ -0,0 +1,13 @@
<script setup>
import Wip from "@/components/util/Wip.vue";
</script>
<template>
<main class="items-center flex flex-col">
<div
class="a4page-portrait items-center bdr-1 flex flex-col relative overflow-scroll"
>
<Wip />
</div>
</main>
</template>

View File

@@ -0,0 +1,56 @@
<script setup>
import Link from "@/components/text/Link.vue";
import InlineLink from "@/components/text/InlineLink.vue";
import Header from "@/components/text/Header.vue";
import Paragraph from "@/components/text/Paragraph.vue";
const links = [
{ name: "GitHub", href: "https://github.com/SteveThePug" },
{ name: "Gitea", href: "/gitea/explore/repos" },
{ name: "Spotify", href: "https://open.spotify.com/user/stevethepug" },
];
</script>
<template>
<main class="flex justify-center px-4 py-16">
<div class="max-w-xl w-full flex flex-col gap-12">
<section>
<Header>Adam French</Header>
<Paragraph>
Junior software engineer focused on full-stack development,
systems programming, and infrastructure. First Class Honours
in Computer Science with Mathematics from Leeds and
Waterloo.
</Paragraph>
</section>
<section>
<Header>About</Header>
<Paragraph>
This website is self-hosted and has a lot more on it than it
needs to. Please have a look at my
<InlineLink to="/cv">CV</InlineLink> for a full breakdown of
my experience, projects, and skills. Please visit
<InlineLink to="/stp">STP</InlineLink> for the prefered but
less professional experience.
</Paragraph>
</section>
<nav class="navRow flex flex-row flex-wrap gap-4 justify-around">
<Link to="/cv"> CV </Link>
<Link to="/stp"> STP </Link>
<Link href="mailto:adam.a.french@outlook.com"> Email </Link>
<Link v-for="link in links" :key="link.name" :href="link.href">
{{ link.name }}
</Link>
</nav>
</div>
</main>
</template>
<style scoped>
.navRow > a {
padding: 0.5rem 1rem;
border: 1px solid currentColor;
}
</style>

View File

@@ -0,0 +1,75 @@
<script setup>
import Markdown from "@/components/util/Markdown.vue";
import { ref, onMounted } from "vue";
import axios from "axios";
import { useRoute } from "vue-router";
const file = ref(null);
const filename = ref("");
const last_edited = ref(null);
// if the address is https://www.adam-french.co.uk/notes/PATH
// request from https://www.adam-french.co.uk/api/notes/PATH
const route = useRoute();
const pathArray = route.params.path;
const path = Array.isArray(pathArray) ? pathArray.join("/") : pathArray;
const url = `/api/notes/${path}`;
function getFilename(headers) {
const disposition = headers["content-disposition"];
if (!disposition) return null;
const match = disposition.match(/filename="?([^"]+)"?/);
return match ? match[1] : null;
}
async function fetchFile() {
const response = await axios.get(url, { responseType: "blob" });
filename.value = getFilename(response.headers);
const lastModified = response.headers["last-modified"];
last_edited.value = lastModified ? new Date(lastModified) : null;
if (filename.value.toLowerCase().endsWith(".md")) {
const text = await response.data.text();
file.value = fixLinks(text);
} else {
file.value = response.data;
}
}
function fixLinks(filedata) {
return filedata.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
if (
url.startsWith("http://") ||
url.startsWith("https://") ||
url.startsWith("#") ||
url.startsWith("./") ||
url.startsWith("../") ||
url.startsWith("//")
) {
return match;
}
return `[${text}](/notes/${url})`;
});
}
onMounted(fetchFile);
</script>
<template>
<main class="items-center flex flex-col">
<div class="background" />
<div
v-if="file"
class="a4page-portrait border-primary-1 flex flex-col relative overflow-scroll gap-1 bg-bg_primary"
>
<h1>{{ filename }}</h1>
<small>{{ last_edited }}</small>
<Markdown class="flex-1 border-box text-wrap" :source="file" />
</div>
<div v-else>Loading</div>
</main>
</template>