feat: initialize template

This commit is contained in:
2026-03-02 22:36:47 +01:00
parent b4e8baabdc
commit dd5e32cc4d
62 changed files with 433 additions and 1016 deletions
-12
View File
@@ -1,12 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json",
"mcpServers": {
"svelte": {
"command": "npx",
"args": [
"-y",
"@sveltejs/mcp"
]
}
}
}
-1
View File
@@ -1 +0,0 @@
engine-strict=true
-65
View File
@@ -1,65 +0,0 @@
# Project Context: Schreifuchs.ch
## Overview
This project is a personal portfolio website for "Schreifuchs", featuring sections for Photography (Fotos), Informatics (Informatik), Music (Musig), and Video. It is currently being transitioned from a static site to a dynamic Node.js site powered by Nextcloud via WebDAV.
## Tech Stack
- **Framework:** Svelte 5 (using Runes) & SvelteKit
- **Language:** TypeScript
- **Styling:** Tailwind CSS (with custom text-shadow plugin)
- **Bundler:** Vite
- **Adapter:** `@sveltejs/adapter-static` (SSG)
- **Markdown:** `@ts-stack/markdown` for content rendering
## Directory Structure
- `src/routes/`: File-based routing.
- `src/routes/fotos`, `informatik`, `musig`, `video`: Feature-specific pages.
- `src/lib/`: Shared utilities and components.
- `src/lib/components/`: Reusable UI components (`ImageTile`, `ImageLinkTile`, `Markdown`).
- `src/lib/images/`: Local image assets imported in components.
- `static/`: Static assets served as-is.
- Contains Markdown content files (`Fotos.md`, etc.) and their associated attachments.
## Key Conventions
- **Svelte 5:** Use Runes (`$state`, `$derived`, `$props`, etc.) for reactivity.
- **Styling:** Use Tailwind utility classes. The font family is configured to 'Outfit'.
- **Content:** Content is largely driven by Markdown files located on Nextcloud which are fetched and parsed.
## Development Scripts
- `npm run dev`: Start development server.
- `npm run build`: Build for production.
- `npm run check`: Run Svelte and TypeScript checks.
---
# Agent Instructions (Svelte MCP)
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
## Available MCP Tools:
### 1. list-sections
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
### 2. get-documentation
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
### 3. svelte-autofixer
Analyzes Svelte code and returns issues and suggestions.
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
### 4. playground-link
Generates a Svelte Playground link with the provided code.
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.
+20
View File
@@ -0,0 +1,20 @@
package main
import (
"log/slog"
"os"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/server"
)
func main() {
server := server.NewServer()
slog.Info("Server started", "addr", server.Addr)
err := server.ListenAndServe()
if err != nil {
slog.Error("Server failed to start", "error", err)
os.Exit(1)
}
}
+24
View File
@@ -0,0 +1,24 @@
module git.schreifuchs.ch/schreifuchs/schreifuchs.ch
go 1.25.7
tool github.com/a-h/templ/cmd/templ
require github.com/a-h/templ v0.3.977
require (
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cli/browser v1.3.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/natefinch/atomic v1.0.1 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/tools v0.35.0 // indirect
)
+43
View File
@@ -0,0 +1,43 @@
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo=
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ=
github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg=
github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+12
View File
@@ -0,0 +1,12 @@
package handler
import (
"net/http"
"github.com/a-h/templ"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web/pages"
)
func Home() http.Handler {
return templ.Handler(pages.Index())
}
+12
View File
@@ -0,0 +1,12 @@
package handler
import (
"net/http"
"github.com/a-h/templ"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web/pages"
)
func Home() http.Handler {
return templ.Handler(pages.Index())
}
+33
View File
@@ -0,0 +1,33 @@
package server
import (
"log/slog"
"net/http"
"time"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handler"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web"
)
func (s *Server) RegisterRoutes() http.Handler {
mux := http.NewServeMux()
fileServer := http.FileServer(http.FS(web.GetStaticFS()))
mux.Handle("/static/", http.StripPrefix("/static/", fileServer))
mux.Handle("/", handler.Home())
return s.loggingMiddleware(mux)
}
func (s *Server) loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
slog.Info("request",
"method", r.Method,
"path", r.URL.Path,
"duration", time.Since(start),
)
})
}
+34
View File
@@ -0,0 +1,34 @@
package server
import (
"fmt"
"net/http"
"os"
"strconv"
"time"
)
type Server struct {
port int
}
func NewServer() *http.Server {
port, _ := strconv.Atoi(os.Getenv("PORT"))
if port == 0 {
port = 8080
}
s := &Server{
port: port,
}
// Declare Server config
server := &http.Server{
Addr: fmt.Sprintf(":%d", s.port),
Handler: s.RegisterRoutes(),
IdleTimeout: time.Minute,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
}
return server
}
-8
View File
@@ -1,8 +0,0 @@
{
"$schema": "https://inlang.com/schema/message-format-plugin",
"hello_world": "Hallo Welt",
"pfadi": "Pfadi",
"site_title": "Schreifuchs.ch",
"site_description": "Portfolio von Schreifuchs - Fotografie, Informatik und Musik."
}
-8
View File
@@ -1,8 +0,0 @@
{
"$schema": "https://inlang.com/schema/message-format-plugin",
"hello_world": "Hello World",
"pfadi": "Scouts",
"site_title": "Schreifuchs.ch",
"site_description": "Portfolio of Schreifuchs - Photography, Informatics and Music ."
}
+36 -36
View File
@@ -1,38 +1,38 @@
{
"name": "schreifuchs",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-node": "^5.5.1",
"@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@types/mime-types": "^3.0.1",
"@types/node": "^25.0.9",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^3.4.3",
"typescript": "^5.5.0",
"vite": "^5.4.4"
},
"type": "module",
"dependencies": {
"@fontsource/outfit": "^5.0.13",
"@inlang/paraglide-js": "^1.11.0",
"@inlang/paraglide-sveltekit": "^0.16.1",
"@ts-stack/markdown": "^1.5.0",
"lru-cache": "^11.2.4",
"mime-types": "^3.0.2",
"svelte-preprocess": "^6.0.2",
"webdav": "^5.8.0"
}
"name": "schreifuchs",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-node": "^5.5.1",
"@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@types/mime-types": "^3.0.1",
"@types/node": "^25.0.9",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^3.4.3",
"typescript": "^5.5.0",
"vite": "^5.4.4"
},
"type": "module",
"dependencies": {
"@fontsource/outfit": "^5.0.13",
"@inlang/paraglide-js": "^1.11.0",
"@inlang/paraglide-sveltekit": "^0.16.1",
"@ts-stack/markdown": "^1.5.0",
"lru-cache": "^11.2.4",
"mime-types": "^3.0.2",
"svelte-preprocess": "^6.0.2",
"webdav": "^5.8.0"
}
}
-2
View File
@@ -1,2 +0,0 @@
onlyBuiltDependencies:
- esbuild
-6
View File
@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
-1
View File
@@ -1 +0,0 @@
cache
-1
View File
@@ -1 +0,0 @@
aa922b989658d017b7a6296093cef3dac23a39ddc1afa6cf7ea24c049a284b56
-14
View File
@@ -1,14 +0,0 @@
{
"$schema": "https://inlang.com/schema/project-settings",
"telemetry": "off",
"sourceLanguageTag": "de",
"languageTags": ["de", "en"],
"modules": [
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js",
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js"
],
"plugin.inlang.messageFormat": {
"pathPattern": "./messages/{languageTag}.json"
}
}
-5
View File
@@ -1,5 +0,0 @@
@import "@fontsource/outfit";
@tailwind base;
@tailwind components;
@tailwind utilities;
-13
View File
@@ -1,13 +0,0 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};
-22
View File
@@ -1,22 +0,0 @@
<!doctype html>
<html lang="%paraglide.lang%" dir="%paraglide.textDirection%">
<head>
<meta charset="utf-8" />
<script
defer
src="https://umami.schreifuchs.ch/script.js"
data-website-id="f09f9cc5-6357-48df-8fb2-5c62089c04a0"
></script>
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>schreifuchs.ch</title>
<meta
name="description"
content="Portfolio von Niklas - Fotografie, Informatik, Musik und Video."
/>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
-3
View File
@@ -1,3 +0,0 @@
import { i18n } from "$lib/i18n"
export const handle = i18n.handle()
-27
View File
@@ -1,27 +0,0 @@
<script lang="ts">
import ImageTile from "./ImageTile.svelte";
import type { Snippet } from 'svelte';
interface Props {
src: string;
href: string;
target?: "_self" | "_blank" | "_parent" | "_top";
children?: Snippet;
}
let {
src,
href,
target = "_self",
children
}: Props = $props();
</script>
<div>
<ImageTile {src}>
<a {href} {target} class="flex items-center justify-center h-full">
{@render children?.()}
</a>
</ImageTile>
</div>
-50
View File
@@ -1,50 +0,0 @@
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
src: string;
alt?: string;
children?: Snippet;
}
let { src, alt = "", children }: Props = $props();
const placeholder = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPjxyZWN0IGZpbGw9IiMzMzMiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiLz48L3N2Zz4=';
let imageSrc = $derived(src || placeholder);
// Generate srcset for optimized images from our proxy
let srcset = $derived.by(() => {
if (!src || !src.startsWith('/assets/')) return undefined;
const widths = [400, 800, 1200, 1600];
const sets = widths.map(w => `${src}${src.includes('?') ? '&' : '?'}w=${w} ${w}w`);
// Add original as fallback
sets.push(`${src} 2000w`);
return sets.join(', ');
});
const sizes = "(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw";
</script>
<div class="grid grid-cols-1 grid-rows-1">
<img
src={imageSrc}
{srcset}
{sizes}
{alt}
loading="lazy"
class="row-start-1 col-start-1 pointer-events-none w-full h-96 object-cover"
onerror={(e) => {
const img = e.currentTarget as HTMLImageElement;
img.src = placeholder;
// Clear srcset on error to avoid loading broken thumbnails
img.srcset = "";
}}
/>
<div class="row-start-1 col-start-1">
{@render children?.()}
</div>
</div>
-99
View File
@@ -1,99 +0,0 @@
<script lang="ts">
import { Marked, Renderer } from "@ts-stack/markdown";
interface Props {
content?: string;
basePath?: string;
}
let { content = "", basePath = "" }: Props = $props();
const renderer = new Renderer();
// Custom image renderer to proxy through /assets/
renderer.image = (href, title, text) => {
let src = href;
if (href && !href.startsWith('http')) {
// Ensure basePath starts with / if provided
const normalizedBasePath = basePath.startsWith('/') ? basePath : `/${basePath}`;
// If href starts with /, it's relative to root. Otherwise, relative to basePath.
if (!href.startsWith('/')) {
src = `${normalizedBasePath}/${href}`.replace(/\/+/g, '/');
}
src = `/assets${src}`;
// Generate srcset for optimized images
const widths = [400, 800, 1200, 1600, 2000];
const srcset = widths
.map(w => `${src}?w=${w} ${w}w`)
.join(', ');
const sizes = "(max-width: 1024px) 100vw, 1024px";
return `<img src="${src}" srcset="${srcset}" sizes="${sizes}" alt="${text}" title="${title || ''}" loading="lazy" />`;
}
return `<img src="${src}" alt="${text}" title="${title || ''}" loading="lazy" />`;
};
let html = $derived(
Marked.parse(content || "__nothing to see here__", {
renderer,
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false,
})
);
</script>
<div class="markdown">
{@html html}
</div>
<style global>
.markdown {
color: white;
text-align: left;
display: flex;
flex-direction: column;
}
.markdown h1 {
font-size: 2rem;
}
.markdown h2 {
font-size: 1.75rem;
}
.markdown h3 {
font-size: 1.5rem;
}
.markdown h4 {
font-size: 1.25rem;
}
.markdown * {
margin-bottom: 1rem;
}
.markdown img {
max-height: 90vh;
display: block;
margin: 0 auto;
}
.markdown table,
.markdown tr,
.markdown th,
.markdown td {
border-collapse: collapse;
border: 1px solid;
padding: 0.3rem 1rem;
}
.markdown th {
font-size: 1.2rem;
}
</style>
-11
View File
@@ -1,11 +0,0 @@
import { createI18n } from "@inlang/paraglide-sveltekit"
import * as runtime from "$lib/paraglide/runtime"
export const i18n = createI18n(runtime, {
pathnames: {
"/about": {
en: "/about",
de: "/ueber-uns"
}
}
})
Binary file not shown.

Before

Width:  |  Height:  |  Size: 661 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 KiB

-1
View File
@@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.
-87
View File
@@ -1,87 +0,0 @@
import { LRUCache } from "lru-cache";
// const cache = new Map<string, { timestamp: number; data: any }>();
const DEFAULT_TTL = 1000 * 60 * 15; // 15 minutes default TTL
const MAX_CACHE_SIZE = 1000 * 1000 * 750; // 750 MB
const cache = new LRUCache({
max: 100,
// Maximum allowed size (in bytes)
maxSize: MAX_CACHE_SIZE,
// IMPORTANT: We must teach the cache how to calculate the size of an item.
// We check for your specific image structure, generic buffers, or fallback to JSON length.
sizeCalculation: (value) => {
// 1. Check if it's our specific image object { buffer: ArrayBuffer, ... }
if (value && typeof value === "object") {
const obj = value as Record<string, any>;
if (obj.buffer instanceof ArrayBuffer) {
return obj.buffer.byteLength;
}
}
// 2. Handle raw ArrayBuffers or Node.js Buffers
if (value instanceof ArrayBuffer) {
return value.byteLength;
}
if (Buffer.isBuffer(value)) {
return value.length;
}
// 3. Fallback for strings or JSON metadata
try {
const str = JSON.stringify(value);
return str ? str.length : 1;
} catch {
return 1;
}
},
ttl: DEFAULT_TTL,
});
/**
* Caches the result of a promise-returning function.
* @param key Unique key for the cache entry.
* @param fetcher Function that returns a promise with the data to cache.
* @param ttl Time to live in milliseconds. Defaults to 15 minutes.
*/
export async function withCache<T>(
key: string,
fetcher: () => Promise<T>,
ttl = DEFAULT_TTL,
): Promise<T> {
const entry = cache.get(key);
if (entry) {
return entry as T;
}
try {
const data = await fetcher();
if (data) {
cache.set(key, data, { ttl: ttl });
}
return data;
} catch (error) {
console.error(`Error fetching data for key ${key}:`, error);
// If fetch fails but we have stale data, return it?
// For now, let's bubble the error up so the page handles it (e.g. 404),
// or maybe just re-throw.
throw error;
}
}
/**
* Manually clears a specific cache key.
*/
export function clearCache(key: string) {
cache.delete(key);
}
/**
* Clears the entire cache.
*/
export function clearAllCache() {
cache.clear();
}
-124
View File
@@ -1,124 +0,0 @@
import { createClient } from "webdav";
import { env } from "$env/dynamic/private";
import { withCache } from "./cache";
if (!env.NEXTCLOUD_URL || !env.NEXTCLOUD_USER || !env.NEXTCLOUD_PASSWORD) {
console.warn(
"Nextcloud environment variables are not fully set. WebDAV client might not work correctly.",
);
}
// Construct the standard Nextcloud WebDAV URL: https://example.com/remote.php/dav/files/USERNAME/[BASE_DIR/]
const getWebdavUrl = () => {
if (!env.NEXTCLOUD_URL) return "";
let url = env.NEXTCLOUD_URL.endsWith("/")
? env.NEXTCLOUD_URL
: `${env.NEXTCLOUD_URL}/`;
url += `remote.php/dav/files/${env.NEXTCLOUD_USER}/`;
if (env.NEXTCLOUD_BASE_DIR) {
// Remove leading/trailing slashes from base dir to ensure clean concatenation
const baseDir = env.NEXTCLOUD_BASE_DIR.replace(/^\/+|\/+$/g, "");
if (baseDir) {
url += `${baseDir}/`;
}
}
return url;
};
export const client = createClient(getWebdavUrl(), {
username: env.NEXTCLOUD_USER || "",
password: env.NEXTCLOUD_PASSWORD || "",
});
export async function getCoverImage(slug: string): Promise<string | null> {
return withCache(`cover:${slug}`, async () => {
try {
const contents = await client.getDirectoryContents("/" + slug);
if (!Array.isArray(contents)) return null;
const cover = contents.find(
(item: any) =>
item.type === "file" &&
item.basename.match(/^cover\.(webp|jpg|jpeg|png)$/i),
);
return cover ? `/assets/${slug}/${cover.basename}` : null;
} catch (e) {
console.warn(`Failed to check cover image for ${slug}:`, e);
return null;
}
});
}
export async function getPages(lang: string = 'de') {
return withCache(`pages:${lang}`, async () => {
try {
const items = await client.getDirectoryContents("/");
if (!Array.isArray(items)) {
return [];
}
const directories = items.filter(
(item: any) =>
item.type === "directory" &&
!item.basename.startsWith(".") &&
!item.basename.startsWith("_"),
);
const pages = await Promise.all(
directories.map(async (item: any) => {
const slug = item.basename;
const baseName = slug.charAt(0).toUpperCase() + slug.slice(1);
const filename = lang === 'de' ? `${baseName}.md` : `${baseName}.${lang}.md`;
const path = `/${slug}/${filename}`;
let title = baseName;
try {
const content = await getPageContent(path);
const headerMatch = content.match(/^#{1,6}\s+(.+)$/m);
if (headerMatch) {
title = headerMatch[1];
}
} catch (e) {
// Fallback to German if English is missing
if (lang !== 'de') {
try {
const fallbackContent = await getPageContent(`/${slug}/${baseName}.md`);
const headerMatch = fallbackContent.match(/^#{1,6}\s+(.+)$/m);
if (headerMatch) {
title = headerMatch[1];
}
} catch (innerE) {
console.warn(`Could not fetch fallback title for ${slug}`);
}
}
}
const coverImage = await getCoverImage(slug);
return {
slug,
title,
coverImage,
};
}),
);
return pages;
} catch (e) {
console.error("Failed to fetch pages from Nextcloud:", e);
return [];
}
});
}
export async function getPageContent(path: string): Promise<string> {
return withCache(`content:${path}`, async () => {
const content = await client.getFileContents(path, { format: "text" });
return content as string;
});
}
-5
View File
@@ -1,5 +0,0 @@
import type { ParamMatcher } from '@sveltejs/kit';
export const match: ParamMatcher = (param) => {
return ['de', 'en'].includes(param);
};
-9
View File
@@ -1,9 +0,0 @@
import type { LayoutServerLoad } from './$types';
import { getPages } from '$lib/server/nextcloud';
export const load: LayoutServerLoad = async ({ params }) => {
const pages = await getPages(params.lang);
return {
pages
};
};
-49
View File
@@ -1,49 +0,0 @@
<script lang="ts">
import "../app.css";
import type { Snippet } from "svelte";
import { ParaglideJS } from "@inlang/paraglide-sveltekit";
import { i18n } from "$lib/i18n";
import { languageTag } from "$lib/paraglide/runtime";
import { page } from "$app/state";
import * as m from "$lib/paraglide/messages";
let { children }: { children?: Snippet } = $props();
</script>
<svelte:head>
<title>{m.site_title()}</title>
<meta name="description" content={m.site_description()} />
</svelte:head>
<ParaglideJS {i18n}>
<header
class="fixed z-50 mix-blend-difference p-1 w-full flex justify-between items-start"
>
<a href={i18n.resolveRoute("/")} class="text-white text-2xl"
>schreifuchs.ch</a
>
<nav class="flex gap-2 p-2">
<a
href={i18n.resolveRoute(i18n.route(page.url.pathname), "de")}
hreflang="de"
data-no-translate
class="text-white hover:underline {languageTag() === 'de'
? 'font-bold'
: ''}"
>
DE
</a>
<a
href={i18n.resolveRoute(i18n.route(page.url.pathname), "en")}
hreflang="en"
data-no-translate
class="text-white hover:underline {languageTag() === 'en'
? 'font-bold'
: ''}"
>
EN
</a>
</nav>
</header>
{@render children?.()}
</ParaglideJS>
-3
View File
@@ -1,3 +0,0 @@
// This can be false if you're using a fallback (i.e. SPA mode)
// export const prerender = true;
// export const trailingSlash = 'always';
-50
View File
@@ -1,50 +0,0 @@
<script lang="ts">
import ImageLinkTile from "$lib/components/ImageLinkTile.svelte";
import type { PageData } from "./$types";
import { i18n } from "$lib/i18n";
import * as m from "$lib/paraglide/messages";
// Import local images to preserve existing design
import mouse from "$lib/images/home/mouse.webp";
import drummachine from "$lib/images/home/drummachine.webp";
import scouts from "$lib/images/home/scouts.webp";
import console_image from "$lib/images/home/console.webp";
import lauch from "$lib/images/home/lauch.webp";
let { data }: { data: PageData } = $props();
// Map slugs to local images for backward compatibility/styling
const staticImages: Record<string, string> = {
informatik: console_image,
fotos: mouse,
video: lauch,
musig: drummachine,
};
// Helper to resolve image source: Local override -> Nextcloud cover -> Placeholder (handled by ImageTile)
const getTileImage = (page: { slug: string; coverImage: string | null }) => {
return page.coverImage || staticImages[page.slug.toLowerCase()] || "";
};
</script>
<main class="">
{#if data.pages}
{#each data.pages as page}
<ImageLinkTile src={getTileImage(page)} href={`/${page.slug}`}>
<h2
class="text-3xl font-bold text-white hover:underline text-shadow-lg"
>
{page.title}
</h2>
</ImageLinkTile>
{/each}
{/if}
<!-- Static/External Links -->
<ImageLinkTile src={scouts} href="https://pfadifrisco.ch/" target="_blank">
<h2 class="text-3xl font-bold text-white hover:underline text-shadow-lg">
{m.pfadi()}
</h2>
</ImageLinkTile>
</main>
@@ -1,69 +0,0 @@
import { getCoverImage, getPageContent } from "$lib/server/nextcloud";
import type { PageServerLoad } from "./$types";
import { error } from "@sveltejs/kit";
export const load: PageServerLoad = async ({ params }) => {
const { slug, lang } = params;
const language = lang || 'de';
// Try both the regular slug and the hidden (_slug) folder
const slugsToTry = [slug, `_${slug}`];
let rawContent = "";
let finalSlug = slug;
let baseName = "";
let filename = "";
for (const s of slugsToTry) {
const cleanSlug = s.startsWith('_') ? s.slice(1) : s;
baseName = cleanSlug.charAt(0).toUpperCase() + cleanSlug.slice(1);
filename = language === 'de' ? `${baseName}.md` : `${baseName}.${language}.md`;
const path = `/${s}/${filename}`;
try {
try {
rawContent = await getPageContent(path);
finalSlug = s;
break; // Found it!
} catch (e) {
if (language !== 'de') {
console.warn(`Localized content ${filename} not found in ${s}, falling back to German.`);
rawContent = await getPageContent(`/${s}/${baseName}.md`);
finalSlug = s;
break; // Found fallback
} else {
throw e; // Try next slug variant
}
}
} catch (e) {
if (s === slugsToTry[slugsToTry.length - 1]) {
console.error(`Error fetching page from Nextcloud for slug ${slug}`, e);
throw error(404, "Page not found");
}
}
}
try {
// Extract the first header (e.g., # Title or ## Title)
const headerMatch = rawContent.match(/^#{1,6}\s+(.+)$/m);
const title = headerMatch ? headerMatch[1] : (finalSlug.startsWith('_') ? finalSlug.slice(1) : finalSlug);
// Remove the first header from content so it's not rendered twice
const content = headerMatch
? rawContent.replace(headerMatch[0], "").trim()
: rawContent;
const coverImage = await getCoverImage(finalSlug);
return {
content,
title,
slug: finalSlug,
coverImage,
lang: language
};
} catch (e) {
console.error(`Error processing content for ${finalSlug}`, e);
throw error(404, "Page not found");
}
};
@@ -1,46 +0,0 @@
<script lang="ts">
import ImageTile from "$lib/components/ImageTile.svelte";
import Markdown from "$lib/components/Markdown.svelte";
import type { PageData } from "./$types";
// Legacy images
import sunne_untergang from "$lib/images/fotos/sunne_untergang.webp";
import monitor from "$lib/images/informatik/monitor.webp";
import plattespiler from "$lib/images/musig/plattespiler.webp";
import jochen from "$lib/images/video/jochen.webp";
let { data }: { data: PageData } = $props();
const legacyHeaders: Record<string, string> = {
fotos: sunne_untergang,
informatik: monitor,
musig: plattespiler,
video: jochen
};
// Fallback image data URI
const placeholder = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPjxyZWN0IGZpbGw9IiMzMzMiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiLz48L3N2Zz4=';
// Use the cover image found by the server, or legacy local image, or placeholder
let headerImage = $derived(
data.coverImage ||
legacyHeaders[data.slug.toLowerCase()] ||
"" // ImageTile handles empty/null src with placeholder or its own logic
);
</script>
<svelte:head>
<title>{data.title} - schreifuchs.ch</title>
</svelte:head>
<ImageTile src={headerImage} alt={data.title}>
<h2 class="flex items-center justify-center h-full text-3xl font-bold text-white text-shadow-lg">
{data.title}
</h2>
</ImageTile>
<main class="flex items-center justify-center pt-8 bg-black min-h-screen px-12">
<section class="max-w-screen-md w-full">
<Markdown content={data.content} basePath={`/${data.slug}`} />
</section>
</main>
-136
View File
@@ -1,136 +0,0 @@
// src/routes/assets/[...path]/+server.ts
import { client } from "$lib/server/nextcloud";
import { error } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
import mime from "mime-types";
import { env } from "$env/dynamic/private";
import { withCache } from "$lib/server/cache";
const DEFAULT_TTL = 1000 * 60 * 15; // 15min
const EXTENDED_TTL = 1000 * 60 * 60 * 24 * 7; // 1 week
// Define what we are storing in the cache
type CachedAsset = {
buffer: ArrayBuffer;
contentType: string;
};
export const GET: RequestHandler = async ({ params, url }) => {
const { path } = params;
const width = url.searchParams.get("w");
const height = url.searchParams.get("h");
if (!path) {
throw error(400, "Path is required");
}
// 1. Create a unique cache key for this specific asset variation
const cacheKey = `asset:${path}?w=${width ?? ""}&h=${height ?? ""}`;
const ttl = path.toString().includes("cover") ? DEFAULT_TTL : EXTENDED_TTL;
try {
// 2. Use withCache to wrap the fetching logic
const cachedData = await withCache<CachedAsset>(
cacheKey,
async () => {
// --- START OF FETCH LOGIC ---
// This function only runs if the data is NOT in the cache
const pathsToTry = [path];
if (!path.startsWith("_")) {
pathsToTry.push(`_${path}`);
}
let lastError: any = null;
for (const currentPath of pathsToTry) {
try {
// A. Try Nextcloud Preview (Resizing)
if (width || height) {
const baseUrl = env.NEXTCLOUD_URL?.endsWith("/")
? env.NEXTCLOUD_URL
: `${env.NEXTCLOUD_URL}/`;
let fullPath = currentPath;
if (env.NEXTCLOUD_BASE_DIR) {
const baseDir = env.NEXTCLOUD_BASE_DIR.replace(
/^\/+|\/+$/g,
"",
);
fullPath = baseDir ? `${baseDir}/${currentPath}` : currentPath;
}
const isJpg = fullPath.match(/\.(jpg|jpeg)$/i);
const previewExtension = isJpg ? "jpg" : "png";
const previewUrl = new URL(
`${baseUrl}index.php/core/preview.${previewExtension}`,
);
const fileParam = fullPath.startsWith("/")
? fullPath
: `/${fullPath}`;
previewUrl.searchParams.set("file", fileParam);
if (width) previewUrl.searchParams.set("x", width);
if (height) previewUrl.searchParams.set("y", height);
previewUrl.searchParams.set("forceIcon", "0");
const auth = Buffer.from(
`${env.NEXTCLOUD_USER}:${env.NEXTCLOUD_PASSWORD}`,
).toString("base64");
console.log(
`Fetching preview: ${previewUrl.toString()} for file: ${fullPath}`,
);
const ncResponse = await fetch(previewUrl.toString(), {
headers: { Authorization: `Basic ${auth}` },
});
if (ncResponse.ok) {
const buffer = await ncResponse.arrayBuffer();
const contentType =
ncResponse.headers.get("Content-Type") || "image/png";
// Return the raw data to be cached
return { buffer, contentType };
} else {
console.warn(
`Nextcloud preview failed (${ncResponse.status}) for ${fullPath}.`,
);
}
}
// B. Try WebDAV (Original)
console.log(`Fetching original file via WebDAV: /${currentPath}`);
const data = await client.getFileContents("/" + currentPath);
const buffer = data as ArrayBuffer;
const contentType =
mime.lookup(currentPath) || "application/octet-stream";
// Return the raw data to be cached
return { buffer, contentType };
} catch (e) {
lastError = e;
continue; // Try next path variant
}
}
throw lastError || new Error("Asset not found");
// --- END OF FETCH LOGIC ---
},
ttl,
);
// 3. Construct a fresh Response using the cached data
return new Response(cachedData.buffer, {
headers: {
"Content-Type": cachedData.contentType,
"Cache-Control": "public, max-age=86400", // Browser cache
Vary: "Accept-Encoding",
},
});
} catch (e) {
console.error(`Error fetching asset: ${path}`, e);
throw error(404, "Asset not found");
}
};
-11
View File
@@ -1,11 +0,0 @@
import adapter from '@sveltejs/adapter-node';
import { sveltePreprocess } from 'svelte-preprocess';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter()
},
preprocess: [vitePreprocess(), sveltePreprocess({})]
};
export default config;
-29
View File
@@ -1,29 +0,0 @@
import plugin from "tailwindcss/plugin"
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {
fontFamily: {
sans: ['Outfit', 'outfit', 'sans-serif']
},
textShadow: {
sm: '0 1px 2px #000',
DEFAULT: '0 2px 4px #000',
lg: '0px 0px 20px #000',
},
},
},
plugins: [
plugin(function({ matchUtilities, theme }) {
matchUtilities(
{
'text-shadow': (value) => ({
textShadow: value,
}),
},
{ values: theme('textShadow') }
)
}),
],
}
+1
View File
@@ -0,0 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
Executable
BIN
View File
Binary file not shown.
-13
View File
@@ -1,13 +0,0 @@
import { paraglide } from '@inlang/paraglide-sveltekit/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
paraglide({
project: './project.inlang',
outdir: './src/lib/paraglide'
}),
sveltekit()
]
});
+5
View File
@@ -0,0 +1,5 @@
package components
templ Hello() {
<h1>Hello World</h1>
}
+40
View File
@@ -0,0 +1,40 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
package components
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func Hello() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1>Hello World</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate
+18
View File
@@ -0,0 +1,18 @@
package layouts
templ Base(title string) {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{ title }</title>
<link rel="icon" type="image/png" href="/static/favicon.png"/>
</head>
<body>
<main>
{ children... }
</main>
</body>
</html>
}
+61
View File
@@ -0,0 +1,61 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
package layouts
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func Base(title string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `layouts/base.templ`, Line: 9, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><link rel=\"icon\" type=\"image/png\" href=\"/static/favicon.png\"></head><body><main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</main></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate
+12
View File
@@ -0,0 +1,12 @@
package pages
import (
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web/layouts"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web/components"
)
templ Index() {
@layouts.Base("Home") {
@components.Hello()
}
}
+63
View File
@@ -0,0 +1,63 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web/components"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web/layouts"
)
func Index() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = components.Hello().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = layouts.Base("Home").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate
+19
View File
@@ -0,0 +1,19 @@
//go:generate go tool templ generate
package web
import (
"embed"
"io/fs"
)
//go:embed static/*
var Static embed.FS
func GetStaticFS() fs.FS {
// fs.Sub returns an fs.FS corresponding to the subtree rooted at "static"
f, err := fs.Sub(Static, "static")
if err != nil {
panic(err)
}
return f
}

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB