feat: initialize template
@@ -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,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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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 ."
|
||||
}
|
||||
|
||||
@@ -1,37 +0,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",
|
||||
"mime-types": "^3.0.2",
|
||||
"svelte-preprocess": "^6.0.2",
|
||||
"webdav": "^5.8.0"
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
cache
|
||||
@@ -1 +0,0 @@
|
||||
aa922b989658d017b7a6296093cef3dac23a39ddc1afa6cf7ea24c049a284b56
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
@import "@fontsource/outfit";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -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 {};
|
||||
@@ -1,14 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="%paraglide.lang%" dir="%paraglide.textDirection%">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<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>
|
||||
@@ -1,3 +0,0 @@
|
||||
import { i18n } from "$lib/i18n"
|
||||
|
||||
export const handle = i18n.handle()
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,98 +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: center;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
.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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
Before Width: | Height: | Size: 661 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 423 KiB |
|
Before Width: | Height: | Size: 439 KiB |
|
Before Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 476 KiB |
|
Before Width: | Height: | Size: 766 KiB |
@@ -1 +0,0 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
@@ -1,43 +0,0 @@
|
||||
const cache = new Map<string, { timestamp: number; data: any }>();
|
||||
const DEFAULT_TTL = 1000 * 60 * 15; // 15 minutes 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 now = Date.now();
|
||||
const entry = cache.get(key);
|
||||
|
||||
if (entry && (now - entry.timestamp < ttl)) {
|
||||
return entry.data as T;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await fetcher();
|
||||
cache.set(key, { timestamp: now, data });
|
||||
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();
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { ParamMatcher } from '@sveltejs/kit';
|
||||
|
||||
export const match: ParamMatcher = (param) => {
|
||||
return ['de', 'en'].includes(param);
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -1,40 +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 * 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="/" class="text-white text-2xl">schreifuchs.ch</a>
|
||||
<nav class="flex gap-2 p-2">
|
||||
<a
|
||||
href="/"
|
||||
hreflang="de"
|
||||
data-no-translate
|
||||
class="text-white hover:underline {languageTag() === 'de' ? 'font-bold' : ''}"
|
||||
>
|
||||
DE
|
||||
</a>
|
||||
<a
|
||||
href="/en"
|
||||
hreflang="en"
|
||||
data-no-translate
|
||||
class="text-white hover:underline {languageTag() === 'en' ? 'font-bold' : ''}"
|
||||
>
|
||||
EN
|
||||
</a>
|
||||
</nav>
|
||||
</header>
|
||||
{@render children?.()}
|
||||
</ParaglideJS>
|
||||
@@ -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';
|
||||
@@ -1,49 +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 staticImages[page.slug.toLowerCase()] || page.coverImage || "";
|
||||
};
|
||||
</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>
|
||||
@@ -1,101 +0,0 @@
|
||||
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";
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
try {
|
||||
let buffer: ArrayBuffer;
|
||||
let contentType: string;
|
||||
|
||||
// Try both original path and hidden variant if applicable
|
||||
const pathsToTry = [path];
|
||||
if (!path.startsWith('_')) {
|
||||
pathsToTry.push(`_${path}`);
|
||||
}
|
||||
|
||||
let response: Response | null = null;
|
||||
let lastError: any = null;
|
||||
|
||||
for (const currentPath of pathsToTry) {
|
||||
try {
|
||||
if (width || height) {
|
||||
// Use Nextcloud Preview API
|
||||
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}`);
|
||||
// Ensure leading slash for the file parameter
|
||||
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) {
|
||||
buffer = await ncResponse.arrayBuffer();
|
||||
contentType = ncResponse.headers.get("Content-Type") || "image/png";
|
||||
|
||||
return new Response(buffer, {
|
||||
headers: {
|
||||
"Content-Type": contentType,
|
||||
"Cache-Control": "public, max-age=86400",
|
||||
"Vary": "Accept-Encoding"
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn(`Nextcloud preview failed (${ncResponse.status}) for ${fullPath}.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch original via WebDAV (Fallback or no resize requested)
|
||||
console.log(`Fetching original file via WebDAV: /${currentPath}`);
|
||||
const data = await client.getFileContents("/" + currentPath);
|
||||
buffer = data as ArrayBuffer;
|
||||
contentType = mime.lookup(currentPath) || "application/octet-stream";
|
||||
|
||||
return new Response(buffer, {
|
||||
headers: {
|
||||
"Content-Type": contentType,
|
||||
"Cache-Control": "public, max-age=86400",
|
||||
"Vary": "Accept-Encoding"
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
lastError = e;
|
||||
continue; // Try next path variant
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new Error("Asset not found");
|
||||
} catch (e) {
|
||||
console.error(`Error fetching asset from Nextcloud: ${path}`, e);
|
||||
throw error(404, "Asset not found");
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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,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()
|
||||
]
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
package components
|
||||
|
||||
templ Hello() {
|
||||
<h1>Hello World</h1>
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 |