mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-28 18:37:43 +02:00
chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace: - bauntown (community website) - maerchenzauber (AI story generation) - memoro (voice memo app) - news (news aggregation) - nutriphi (nutrition tracking) - reader (reading app) - uload (URL shortener) - wisekeep (AI wisdom extraction) Update CLAUDE.md documentation: - Add presi to active projects - Document archived projects section - Update workspace configuration Archived apps can be re-activated by moving back to apps/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b97149ac12
commit
61d181fbc2
3148 changed files with 437 additions and 46640 deletions
263
apps-archived/uload/apps/web/src/lib/layouts/BlogLayout.svelte
Normal file
263
apps-archived/uload/apps/web/src/lib/layouts/BlogLayout.svelte
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
<script lang="ts">
|
||||
import type { Author } from '../../content/config';
|
||||
import { page } from '$app/stores';
|
||||
import { getEntry } from '$lib/content';
|
||||
|
||||
// Svelte 5: Props mit $props()
|
||||
let {
|
||||
title,
|
||||
excerpt,
|
||||
date,
|
||||
author, // Author ID
|
||||
tags = [],
|
||||
category,
|
||||
image = undefined,
|
||||
series = undefined,
|
||||
seo = {},
|
||||
readingTime = 5,
|
||||
} = $props<{
|
||||
title: string;
|
||||
excerpt: string;
|
||||
date: string | Date;
|
||||
author: string;
|
||||
tags?: string[];
|
||||
category: string;
|
||||
image?: string;
|
||||
series?: string;
|
||||
seo?: any;
|
||||
readingTime?: number;
|
||||
}>();
|
||||
|
||||
// Svelte 5: $state für reaktive Variablen
|
||||
let authorData = $state<Author | null>(null);
|
||||
let headings = $state<Array<{ id: string; text: string; level: string }>>([]);
|
||||
let scrollProgress = $state(0);
|
||||
|
||||
// Svelte 5: $derived für berechnete Werte
|
||||
let formattedDate = $derived(
|
||||
new Date(date).toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})
|
||||
);
|
||||
|
||||
let readingProgress = $derived(`${Math.round(scrollProgress)}%`);
|
||||
let categoryUrl = $derived(`/blog/category/${category.toLowerCase()}`);
|
||||
|
||||
// Svelte 5: $effect für Side-Effects
|
||||
$effect(async () => {
|
||||
// Autor-Daten laden
|
||||
if (author) {
|
||||
try {
|
||||
authorData = await getEntry<Author>('authors', author);
|
||||
} catch (e) {
|
||||
console.log('Author not found:', author);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
// Table of Contents extrahieren
|
||||
const extractHeadings = () => {
|
||||
const h2s = document.querySelectorAll('article h2, article h3');
|
||||
headings = Array.from(h2s).map((h) => ({
|
||||
id: h.id,
|
||||
text: h.textContent || '',
|
||||
level: h.tagName.toLowerCase(),
|
||||
}));
|
||||
};
|
||||
|
||||
// Warte auf DOM
|
||||
setTimeout(extractHeadings, 100);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
// Scroll Progress Tracking
|
||||
const handleScroll = () => {
|
||||
const winScroll = document.documentElement.scrollTop;
|
||||
const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
||||
scrollProgress = (winScroll / height) * 100;
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{seo.title || title} | uload Blog</title>
|
||||
<meta name="description" content={seo.description || excerpt} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={excerpt} />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="article:author" content={authorData?.name} />
|
||||
<meta property="article:published_time" content={new Date(date).toISOString()} />
|
||||
{#each tags as tag}
|
||||
<meta property="article:tag" content={tag} />
|
||||
{/each}
|
||||
{#if image}
|
||||
<meta property="og:image" content={image} />
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
<!-- Reading Progress Bar -->
|
||||
<div
|
||||
class="fixed left-0 top-0 z-50 h-1 bg-blue-600 transition-all"
|
||||
style="width: {readingProgress}"
|
||||
/>
|
||||
|
||||
<div class="mx-auto max-w-7xl px-4 py-8">
|
||||
<div class="grid grid-cols-1 gap-8 lg:grid-cols-4">
|
||||
<!-- Hauptinhalt -->
|
||||
<article class="prose prose-lg max-w-none lg:col-span-3">
|
||||
<header class="not-prose mb-8">
|
||||
{#if series}
|
||||
<div class="mb-2 text-sm text-blue-600">
|
||||
Serie: {series}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<h1 class="mb-4 text-4xl font-bold">{title}</h1>
|
||||
|
||||
<div class="flex items-center gap-4 text-gray-600">
|
||||
<time datetime={new Date(date).toISOString()}>
|
||||
{formattedDate}
|
||||
</time>
|
||||
<span>•</span>
|
||||
<a href={categoryUrl} class="hover:text-blue-600">
|
||||
{category}
|
||||
</a>
|
||||
<span>•</span>
|
||||
<span>{readingTime} Min. Lesezeit</span>
|
||||
</div>
|
||||
|
||||
{#if tags.length > 0}
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
{#each tags as tag}
|
||||
<a
|
||||
href="/blog/tag/{tag}"
|
||||
class="rounded-full bg-gray-100 px-3 py-1 text-sm transition hover:bg-gray-200"
|
||||
>
|
||||
#{tag}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if image}
|
||||
<img
|
||||
src={image}
|
||||
alt={title}
|
||||
class="mt-6 h-64 w-full rounded-lg object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<!-- MDX Content wird hier eingefügt -->
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<footer class="not-prose mt-12 border-t pt-8">
|
||||
<!-- Share Buttons -->
|
||||
<div class="mb-8 flex gap-4">
|
||||
<a
|
||||
href="https://twitter.com/intent/tweet?url={encodeURIComponent(
|
||||
$page.url.href
|
||||
)}&text={encodeURIComponent(title)}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="rounded bg-blue-400 px-4 py-2 text-white hover:bg-blue-500"
|
||||
>
|
||||
Twitter
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/sharing/share-offsite/?url={encodeURIComponent(
|
||||
$page.url.href
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="rounded bg-blue-700 px-4 py-2 text-white hover:bg-blue-800"
|
||||
>
|
||||
LinkedIn
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{#if authorData}
|
||||
<div class="rounded-lg bg-gray-50 p-6">
|
||||
<h3 class="mb-2 font-semibold">Über den Autor</h3>
|
||||
<div class="flex gap-4">
|
||||
{#if authorData.avatar}
|
||||
<img src={authorData.avatar} alt={authorData.name} class="h-16 w-16 rounded-full" />
|
||||
{/if}
|
||||
<div>
|
||||
<p class="font-medium">{authorData.name}</p>
|
||||
{#if authorData.bio}
|
||||
<p class="mt-1 text-sm text-gray-600">{authorData.bio}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="lg:col-span-1">
|
||||
<div class="sticky top-4 space-y-6">
|
||||
{#if headings.length > 0}
|
||||
<nav class="rounded-lg bg-white p-4 shadow">
|
||||
<h3 class="mb-3 text-sm font-semibold uppercase text-gray-600">Inhaltsverzeichnis</h3>
|
||||
<ul class="space-y-2">
|
||||
{#each headings as heading}
|
||||
<li class={heading.level === 'h3' ? 'ml-4' : ''}>
|
||||
<a
|
||||
href="#{heading.id}"
|
||||
class="text-sm text-gray-700 transition hover:text-blue-600"
|
||||
>
|
||||
{heading.text}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
||||
{/if}
|
||||
|
||||
<!-- Newsletter CTA -->
|
||||
<div class="rounded-lg bg-blue-50 p-6">
|
||||
<h3 class="mb-2 font-semibold">Newsletter</h3>
|
||||
<p class="mb-4 text-sm text-gray-600">Erhalte neue Artikel direkt in dein Postfach.</p>
|
||||
<a
|
||||
href="/register"
|
||||
class="block w-full rounded bg-blue-600 py-2 text-center text-white hover:bg-blue-700"
|
||||
>
|
||||
Abonnieren
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(.content .anchor-link) {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
:global(.content .anchor-link:hover::before) {
|
||||
content: '#';
|
||||
position: absolute;
|
||||
left: -1.5rem;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
:global(.content h2),
|
||||
:global(.content h3) {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue