1
0
Fork 0

Dependency refresh and cleanup

This commit is contained in:
Moritz Ruth 2025-02-13 22:48:49 +01:00
parent 6300d6746a
commit c70f9013f5
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
22 changed files with 1858 additions and 3392 deletions

View file

@ -1,7 +0,0 @@
{
"root": true,
"extends": "awzzm-vue",
"rules": {
"vue/no-reserved-component-names": "off"
}
}

5
.gitignore vendored
View file

@ -1,8 +1,7 @@
.idea/ /.idea/
/node_modules/ /node_modules/
/dist/ /dist/
/.vite-ssg-temp/ /.vite-ssg-temp/
*.log *.log
.env *.env

2
.nvmrc
View file

@ -1 +1 @@
16.11.0 22

View file

@ -8,7 +8,7 @@
<link rel="stylesheet" href="/node_modules/@fontsource/plus-jakarta-sans/400.css"> <link rel="stylesheet" href="/node_modules/@fontsource/plus-jakarta-sans/400.css">
<link rel="stylesheet" href="/node_modules/@fontsource/syne/700.css"> <link rel="stylesheet" href="/node_modules/@fontsource/syne/700.css">
<link rel="stylesheet" href="/node_modules/@fontsource/plus-jakarta-sans/700.css"> <link rel="stylesheet" href="/node_modules/@fontsource/plus-jakarta-sans/700.css">
<meta name="description" content="freelance software developer, graphic design enthusiast and hobby photographer"> <meta name="description" content="freelance software developer, photographer, musician">
<link rel="icon" type="image/png" href="/favicon.ico"> <link rel="icon" type="image/png" href="/favicon.ico">
</head> </head>
<body> <body>

View file

@ -1,12 +0,0 @@
[build.environment]
# bypass npm auto install
NPM_FLAGS = "--version"
[build]
publish = "dist"
command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm run build"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

View file

@ -1,34 +1,36 @@
{ {
"name": "moritzruth.de", "name": "moritzruth.de",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
"build": "vite-ssg build", "build": "vite-ssg build",
"start": "vite preview", "start": "vite preview"
"lint": "eslint . --fix --ignore-path .gitignore"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^2.0.1", "@vitejs/plugin-vue": "^5.2.1",
"@vue/compiler-sfc": "^3.1.1", "@vue/compiler-sfc": "^3.5.13",
"eslint": "^7.32.0", "typescript": "^5.7.3",
"eslint-config-awzzm-vue": "^2.0.1", "vite": "^6.1.0",
"typescript": "^4.5.4", "vite-plugin-pages": "^0.32.4",
"vite": "^2.3.7", "vite-plugin-windicss": "^1.9.4",
"vite-plugin-pages": "^0.19.4", "windicss": "^3.5.6"
"vite-plugin-windicss": "^1.0.4",
"windicss": "^3.2.1"
}, },
"dependencies": { "dependencies": {
"@fontsource/plus-jakarta-sans": "^4.5.0", "@fontsource/plus-jakarta-sans": "^5.1.1",
"@fontsource/syne": "^4.5.0", "@fontsource/syne": "^5.1.1",
"@iconify/json": "1.1.444", "@iconify/json": "^2.2.306",
"@vueuse/core": "^7.2.2", "@vueuse/core": "^12.5.0",
"@vueuse/head": "^0.5.1", "@vueuse/head": "^2.0.0",
"blobs": "^2.2.1-beta.1", "blobs": "^2.3.0-beta.2",
"ohmyfetch": "^0.4.11", "unplugin-icons": "^22.0.0",
"unplugin-icons": "^0.12.23", "vite-ssg": "^25.0.0",
"vite-ssg": "^0.17.2", "vue": "^3.5.13",
"vue": "^3.2.26", "vue-router": "^4.5.0"
"vue-router": "^4.0.12" },
"pnpm": {
"onlyBuiltDependencies": [
"esbuild"
]
} }
} }

4885
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,2 +0,0 @@
/.well-known/*
Access-Control-Allow-Origin: *

View file

@ -1,9 +0,0 @@
# Old domain
https://moritz-ruth.de/* https://moritzruth.de/:splat 301!
# External
/matrix https://matrix.to/#/@moritz:deltaa.xyz
/twitter https://twitter.com/moritzruth
# Internal
/kontakt /contact

2
redirects.txt Normal file
View file

@ -0,0 +1,2 @@
/kontakt /contact
/matrix https://matrix.to/#/@moritz:deltaa.xyz

View file

@ -1,52 +0,0 @@
<template>
<NotFoundPage v-if="post === null" object-name="post" back-target="/blog"/>
<TopBarLayout v-else title="Blog" back-target="/blog">
<article>
<h1 class="font-bold text-3xl sm:text-3xl sm:text-center font-special">
{{ post.title }}
</h1>
<XSpacer v="4"/>
<div class="flex justify-center -sm:flex-col -sm:space-y-1 -sm:pt-2 sm:space-x-3 text-sm sm:text-base">
<div>
Published at {{ post.published_at.slice(0, 10) }}
</div>
<div class="-sm:hidden">
|
</div>
<div>
Reading time: {{ post.reading_time_minutes }} minute{{ post.reading_time_minutes === 1 ? "" : "s" }}
</div>
</div>
<XSpacer v="8"/>
<Prose v-html="html"/>
</article>
</TopBarLayout>
</template>
<style module>
</style>
<script lang="ts">
import { useRoute } from "vue-router"
import { getPostBySlug } from "../posts"
import TopBarLayout from "./TopBarLayout.vue"
import Prose from "./Prose.vue"
import XSpacer from "./XSpacer.vue"
import NotFoundPage from "./NotFoundPage.vue"
export default {
name: "BlogPostPageContent",
components: { NotFoundPage, XSpacer, Prose, TopBarLayout },
async setup() {
const route = useRoute()
const post = await getPostBySlug(route.params.slug as string)
return {
post,
// Yep, that's awful code
html: post?.body_html?.replaceAll("h2>", "h3>")?.replaceAll("h1>", "h2>")
}
}
}
</script>

View file

@ -3,10 +3,12 @@
</template> </template>
<script> <script>
import { canvasPath as createBlobAnimation } from "blobs/v2/animate/index.js" import blobsLib from "blobs/v2/animate"
import { ref, watchEffect } from "vue" import { ref, watchEffect } from "vue"
import { useRafFn } from "@vueuse/core" import { useRafFn } from "@vueuse/core"
const createBlobAnimation = blobsLib.canvasPath
function getComponentsOfHexColor(hexColorString) { function getComponentsOfHexColor(hexColorString) {
return [ return [
Number.parseInt(hexColorString.slice(1, 3), 16), Number.parseInt(hexColorString.slice(1, 3), 16),

View file

@ -7,13 +7,12 @@
</style> </style>
<script> <script>
import { $fetch } from "ohmyfetch"
// eslint-disable-next-line no-promise-executor-return // eslint-disable-next-line no-promise-executor-return
const waitForAnimationFrame = () => new Promise(resolve => requestAnimationFrame(resolve)) const waitForAnimationFrame = () => new Promise(resolve => requestAnimationFrame(resolve))
const asyncSetup = async src => { const asyncSetup = async src => {
const blob = await $fetch(src) const response = await fetch(src)
const blob = await response.blob()
await waitForAnimationFrame() // makes sure the browser is idle await waitForAnimationFrame() // makes sure the browser is idle
return { return {
objectUrl: URL.createObjectURL(blob) objectUrl: URL.createObjectURL(blob)

View file

@ -12,14 +12,14 @@ export const photos: Photo[] = [
title: "Late afternoon", title: "Late afternoon",
altText: "A red house in London", altText: "A red house in London",
date: "2019", date: "2019",
description: "Taken in London during a sunny afternoon.\nSeems to be 65 Curzon St." description: "65 Curzon Street, Mayfair, London"
}, },
{ {
file: "Martyrdom.webp", file: "Martyrdom.webp",
title: "Martyrdom", title: "Martyrdom",
altText: "A church on a vineyard", altText: "A church on a vineyard",
date: "2019", date: "2019",
description: "The Sankt-Laurentius church right by the Mosel river in Bremm (Germany)." description: "The Sankt-Laurentius church by the Mosel river in Bremm (Germany)."
}, },
{ {
file: "Heat.webp", file: "Heat.webp",

View file

@ -1,64 +0,0 @@
<template>
<NotFoundPage v-if="post === null" object-name="post" back-target="/blog"/>
<TopBarLayout v-else
title="Blog"
back-target="/blog"
no-set-title
>
<Head>
<title>{{ post.title }} Moritz Ruth</title>
<meta name="keywords" :content="getKeywordsWithBase(post.tags).join(', ')">
</Head>
<article>
<h1 class="font-bold text-3xl sm:text-3xl sm:text-center font-special">
{{ post.title }}
</h1>
<XSpacer v="4"/>
<div class="flex justify-center -sm:flex-col -sm:space-y-1 -sm:pt-2 sm:space-x-3 text-sm sm:text-base">
<div>
Published at {{ post.published_at.slice(0, 10) }}
</div>
<div class="-sm:hidden">
|
</div>
<div>
Reading time: {{ post.reading_time_minutes }} minute{{ post.reading_time_minutes === 1 ? "" : "s" }}
</div>
</div>
<XSpacer v="8"/>
<Prose v-html="html"/>
</article>
</TopBarLayout>
</template>
<style module>
</style>
<script lang="ts">
import { useRoute } from "vue-router"
import { Head } from "@vueuse/head"
import TopBarLayout from "../../components/TopBarLayout.vue"
import { getPostBySlug } from "../../posts"
import Prose from "../../components/Prose.vue"
import XSpacer from "../../components/XSpacer.vue"
import NotFoundPage from "../../components/NotFoundPage.vue"
import { getKeywordsWithBase } from "../../data"
export default {
name: "BlogPostPage",
components: { NotFoundPage, XSpacer, Prose, TopBarLayout, Head },
async setup() {
const route = useRoute()
const post = await getPostBySlug(route.params.slug as string)
return {
post,
// Yep, that's awful code
html: post?.body_html?.replaceAll("h2>", "h3>")?.replaceAll("h1>", "h2>"),
getKeywordsWithBase
}
}
}
</script>

View file

@ -1,65 +0,0 @@
<template>
<TopBarLayout title="Blog" back-target="/">
<div class="fixed top-0 left-0 bottom-0 right-0 flex justify-center items-center">
<BlurredBlobCanvas
:blur="50"
:size="400"
:randomness="400"
:minimum-duration="3000"
:duration-variation="1000"
:minimum-opacity="0.4"
:opacity-variation="0"
:colors="['#eb34cf', '#6577fc']"
/>
</div>
<div class="flex flex-col space-y-8 max-w-120 mx-auto">
<router-link
v-for="post in posts"
:key="post.id"
:to="`/blog/${post.slug}`"
class="bg-light-300 bg-opacity-5 rounded-lg backdrop-blur-lg cursor-pointer hover:bg-opacity-10 focus-visible:bg-opacity-10 transform hover:scale-104
transition duration-200 p-5 sm:p-7 flex flex-col overflow-hidden"
>
<div class="font-bold text-xl">
{{ post.title }}
</div>
<XSpacer v="2"/>
<div class="text-lg">
{{ post.description }}
</div>
<XSpacer v="3"/>
<div class="font-bold text-sm flex justify-between">
<div>
Published at {{ new Date(post.published_at).toLocaleDateString() }}
</div>
<div>
{{ post.reading_time_minutes }} minute{{ post.reading_time_minutes === 1 ? "" : "s" }}
</div>
</div>
</router-link>
</div>
</TopBarLayout>
</template>
<style module>
</style>
<script lang="ts">
import TopBarLayout from "../../components/TopBarLayout.vue"
import { getPosts } from "../../posts"
import XSpacer from "../../components/XSpacer.vue"
import BlurredBlobCanvas from "../../components/BlurredBlobCanvas.vue"
export default {
name: "BlogIndexPage",
components: { BlurredBlobCanvas, XSpacer, TopBarLayout },
async setup() {
const posts = await getPosts()
return {
posts
}
}
}
</script>

View file

@ -21,10 +21,11 @@
<div class="text-lg sm:text-xl font-medium leading-8 pt-5"> <div class="text-lg sm:text-xl font-medium leading-8 pt-5">
<p> <p>
Im&nbsp;a&nbsp;freelance Im&nbsp;a&nbsp;freelance
software&nbsp;developer, software&nbsp;developer
graphic&nbsp;design&nbsp;enthusiast&nbsp;and from&nbsp;Europe.<br>
<router-link to="/photography" :class="$style.link">hobby&nbsp;photographer</router-link> I&nbsp;also occasionally&nbsp;take
from&nbsp;Europe. <router-link to="/photography" :class="$style.link">photos</router-link>
and make&nbsp;music.
</p> </p>
</div> </div>
<XSpacer v="10"/> <XSpacer v="10"/>
@ -32,7 +33,7 @@
</main> </main>
</div> </div>
<div class="relative lg:pr-20 -lg:pt-20 pb-10 mt-0"> <div class="relative lg:pr-20 -lg:pt-20 pb-10 mt-0">
<nav class="absolute w-full pt-20 flex justify-center"> <nav class="absolute w-full pt-5 flex justify-center">
<BlurredBlobCanvas <BlurredBlobCanvas
:blur="30" :blur="30"
:size="300" :size="300"

View file

@ -6,7 +6,7 @@
This legal notice (Impressum) is a requirement of the German Act for Telemedia&nbsp;Services (Telemediengesetz). This legal notice (Impressum) is a requirement of the German Act for Telemedia&nbsp;Services (Telemediengesetz).
</p> </p>
<p> <p>
The declarations on this page only apply to this website (moritzruth.de) and all websites which reside under subdomains of moritzruth.de. The declarations on this page only apply to this website (moritzruth.de) and all websites which reside under subdomains of moritzruth.de and deltaa.xyz.
</p> </p>
</section> </section>
<section> <section>

View file

@ -1,21 +0,0 @@
/* eslint-disable camelcase */
import { $fetch } from "ohmyfetch"
const USERNAME = "moritzruth"
export interface Post {
id: number
title: string
slug: string
description: string
published_at: string
reading_time_minutes: number
tags: string[]
}
export interface FullPost extends Post {
body_html: string
}
export const getPosts = () => $fetch<Post[]>(`https://dev.to/api/articles?username=${USERNAME}&per_page=1000`)
export const getPostBySlug = (slug: string) => $fetch<FullPost>(`https://dev.to/api/articles/${USERNAME}/${slug}`).catch(() => null)

3
src/vite-env.d.ts vendored
View file

@ -1,3 +0,0 @@
/// <reference types="vite-plugin-pages/client"/>
/// <reference types="unplugin-icons/types/vue"/>
/// <reference types="vite/client"/>

View file

@ -1,16 +1,30 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "declaration": false,
"module": "ESNext",
"target": "ES2021",
"lib": ["DOM", "ESNext"],
"strict": true,
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true, "forceConsistentCasingInFileNames": true,
"jsx": "preserve",
"lib": ["esnext", "dom"],
"module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"allowJs": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"strictNullChecks": true, "importsNotUsedAsValues": "error",
"downlevelIteration": true, "isolatedModules": true,
"forceConsistentCasingInFileNames": true "rootDir": "src",
} "skipLibCheck": true,
"sourceMap": true,
"strict": true,
"stripInternal": true,
"target": "esnext",
"types": [
"vite/client",
"unplugin-icons/types/vue",
"vite-plugin-pages/client"
]
},
"include": [
"src/**/*.ts",
"src/**/*.vue"
]
} }

View file

@ -7,17 +7,8 @@ import iconsPlugin from "unplugin-icons/vite"
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vuePlugin(), vuePlugin(),
pagesPlugin({ pagesPlugin(),
syncIndex: false
}),
windicssPlugin(), windicssPlugin(),
iconsPlugin() iconsPlugin()
], ]
ssgOptions: {
formatting: "minify",
format: "cjs",
includedRoutes(routes) {
return routes.filter(route => !route.includes(":") && route !== "/blog")
}
}
}) })