feat: nextcloud sync

This commit is contained in:
2026-01-20 18:39:07 +01:00
parent 16c277c50c
commit ff2a2fee48
18 changed files with 647 additions and 80 deletions
+32 -33
View File
@@ -1,47 +1,46 @@
<script lang="ts">
import { Marked, Renderer } from "@ts-stack/markdown";
import { onMount } from "svelte";
Marked.setOptions({
renderer: new Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false,
});
interface Props {
src: string;
content?: string;
basePath?: string;
}
let { src }: Props = $props();
let { content = "", basePath = "" }: Props = $props();
let content: Element[] = $state([]);
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}`;
}
return `<img src="${src}" alt="${text}" title="${title || ''}" />`;
};
onMount(async () => {
const DOM_PARSER = new DOMParser();
const markdown = await (await fetch(src)).text();
const parsedMarkdown = DOM_PARSER.parseFromString(
Marked.parse(markdown ? markdown : "__nothing to see here__"),
"text/html",
);
content = [...parsedMarkdown.body.children];
// content.forEach((element) => console.log(element.nodeName));
});
//have to impelment correct relative img path's
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">
{#if content != null}
{#each content as element, i}
{@html element.outerHTML}
{/each}
{/if}
{@html html}
</div>
<style global>
+21
View File
@@ -0,0 +1,21 @@
import { createClient } from "webdav";
import { env } from "$env/dynamic/private";
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/
const getWebdavUrl = () => {
if (!env.NEXTCLOUD_URL) return "";
const base = env.NEXTCLOUD_URL.endsWith("/") ? env.NEXTCLOUD_URL : `${env.NEXTCLOUD_URL}/`;
return `${base}remote.php/dav/files/${env.NEXTCLOUD_USER}/`;
};
export const client = createClient(
getWebdavUrl(),
{
username: env.NEXTCLOUD_USER || "",
password: env.NEXTCLOUD_PASSWORD || ""
}
);
+29
View File
@@ -0,0 +1,29 @@
import { client } from "$lib/server/nextcloud";
import { error } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
import mime from "mime-types";
export const GET: RequestHandler = async ({ params }) => {
const { path } = params;
if (!path) {
throw error(400, "Path is required");
}
try {
// Fetch from Nextcloud. We assume the path is relative to the root or a specific folder.
// If your files are in a specific folder, e.g., "Schreifuchs", prepend it here.
const buffer = await client.getFileContents("/" + path);
const contentType = mime.lookup(path) || "application/octet-stream";
return new Response(buffer as any, {
headers: {
"Content-Type": contentType,
"Cache-Control": "public, max-age=3600" // Cache for 1 hour
}
});
} catch (e) {
console.error(`Error fetching asset from Nextcloud: ${path}`, e);
throw error(404, "Asset not found");
}
};
+16
View File
@@ -0,0 +1,16 @@
import { client } from "$lib/server/nextcloud";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async () => {
try {
const content = await client.getFileContents("/fotos/Fotos.md", { format: "text" });
return {
content: content as string
};
} catch (e) {
console.error("Error fetching Fotos.md from Nextcloud", e);
return {
content: "Failed to load content from Nextcloud."
};
}
};
+4 -1
View File
@@ -2,6 +2,9 @@
import ImageTile from "$lib/components/ImageTile.svelte";
import Markdown from "$lib/components/Markdown.svelte";
import HeadImage from "$lib/images/fotos/sunne_untergang.webp";
import type { PageData } from "./$types";
let { data }: { data: PageData } = $props();
</script>
<ImageTile src={HeadImage}>
@@ -13,6 +16,6 @@
</ImageTile>
<main class="flex items-center justify-center pt-8 bg-black min-h-screen px-12">
<section class="max-w-screen-md">
<Markdown src="/fotos/Fotos.md" />
<Markdown content={data.content} basePath="/fotos" />
</section>
</main>
+16
View File
@@ -0,0 +1,16 @@
import { client } from "$lib/server/nextcloud";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async () => {
try {
const content = await client.getFileContents("/informatik/Informatik.md", { format: "text" });
return {
content: content as string
};
} catch (e) {
console.error("Error fetching Informatik.md from Nextcloud", e);
return {
content: "Failed to load content from Nextcloud."
};
}
};
+4 -1
View File
@@ -2,6 +2,9 @@
import ImageTile from "$lib/components/ImageTile.svelte";
import Markdown from "$lib/components/Markdown.svelte";
import HeadImage from "$lib/images/informatik/monitor.webp";
import type { PageData } from "./$types";
let { data }: { data: PageData } = $props();
</script>
<ImageTile src={HeadImage}>
@@ -13,6 +16,6 @@
</ImageTile>
<main class="flex items-center justify-center pt-8 bg-black min-h-screen px-12">
<section class="max-w-screen-md">
<Markdown src="/informatik/Informatik.md" />
<Markdown content={data.content} basePath="/informatik" />
</section>
</main>
+16
View File
@@ -0,0 +1,16 @@
import { client } from "$lib/server/nextcloud";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async () => {
try {
const content = await client.getFileContents("/musig/Musig.md", { format: "text" });
return {
content: content as string
};
} catch (e) {
console.error("Error fetching Musig.md from Nextcloud", e);
return {
content: "Failed to load content from Nextcloud."
};
}
};
+4 -1
View File
@@ -2,6 +2,9 @@
import ImageTile from "$lib/components/ImageTile.svelte";
import Markdown from "$lib/components/Markdown.svelte";
import HeadImage from "$lib/images/musig/plattespiler.webp";
import type { PageData } from "./$types";
let { data }: { data: PageData } = $props();
</script>
<ImageTile src={HeadImage}>
@@ -13,6 +16,6 @@
</ImageTile>
<main class="flex items-center justify-center pt-8 bg-black min-h-screen px-12">
<section class="max-w-screen-md">
<Markdown src="/musig/Musig.md" />
<Markdown content={data.content} basePath="/musig" />
</section>
</main>
+16
View File
@@ -0,0 +1,16 @@
import { client } from "$lib/server/nextcloud";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async () => {
try {
const content = await client.getFileContents("/video/Video.md", { format: "text" });
return {
content: content as string
};
} catch (e) {
console.error("Error fetching Video.md from Nextcloud", e);
return {
content: "Failed to load content from Nextcloud."
};
}
};
+4 -1
View File
@@ -2,6 +2,9 @@
import ImageTile from "$lib/components/ImageTile.svelte";
import Markdown from "$lib/components/Markdown.svelte";
import HeadImage from "$lib/images/video/jochen.webp";
import type { PageData } from "./$types";
let { data }: { data: PageData } = $props();
</script>
<ImageTile src={HeadImage}>
@@ -13,6 +16,6 @@
</ImageTile>
<main class="flex items-center justify-center pt-8 bg-black min-h-screen px-12">
<section class="max-w-screen-md">
<Markdown src="/video/Video.md" />
<Markdown content={data.content} basePath="/video" />
</section>
</main>